-
-
Notifications
You must be signed in to change notification settings - Fork 146
refactor(view)!: improved view component rendering #1980
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 3.x
Are you sure you want to change the base?
Changes from all commits
825ef9e
ce3d13a
2ad96e7
e42223d
d0b983a
30e6a57
726acb8
f3c317e
48df9b9
805b02e
41fda70
a8989b5
01b11d9
e854cbc
f2ec21b
924eea8
17598b1
8e9fc90
21d69ab
43b49e1
841f83b
557c523
86afb0a
3e4c0da
ac3c999
6d951ad
f162dcf
5424184
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Tempest\View; | ||
|
|
||
| final readonly class CompiledView | ||
| { | ||
| /** | ||
| * @param array<int, array{compiledStartLine: int, compiledEndLine: int, sourcePath: string, sourceStartLine: int}> $lineMap | ||
| */ | ||
| public function __construct( | ||
| public string $content, | ||
| public ?string $sourcePath, | ||
| public array $lineMap, | ||
| ) {} | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ | |
| use Tempest\View\Parser\Token; | ||
| use Tempest\View\Parser\TokenType; | ||
| use Tempest\View\Slot; | ||
| use Tempest\View\ViewCache; | ||
| use Tempest\View\ViewConfig; | ||
|
|
||
| final class ElementFactory | ||
|
|
@@ -22,6 +23,7 @@ final class ElementFactory | |
| public function __construct( | ||
| private readonly ViewConfig $viewConfig, | ||
| private readonly Environment $environment, | ||
| private readonly ViewCache $viewCache, | ||
| ) {} | ||
|
|
||
| public function setViewCompiler(TempestViewCompiler $compiler): self | ||
|
|
@@ -40,15 +42,7 @@ public function withIsHtml(bool $isHtml): self | |
| return $clone; | ||
| } | ||
|
|
||
| public function make(Token $token): ?Element | ||
| { | ||
| return $this->makeElement( | ||
| token: $token, | ||
| parent: null, | ||
| ); | ||
| } | ||
|
|
||
| private function makeElement(Token $token, ?Element $parent): ?Element | ||
| public function make(Token $token, Element $parent): ?Element | ||
| { | ||
| if ( | ||
| $token->type === TokenType::OPEN_TAG_END | ||
|
|
@@ -59,44 +53,46 @@ private function makeElement(Token $token, ?Element $parent): ?Element | |
| return null; | ||
| } | ||
|
|
||
| $attributes = $token->htmlAttributes; | ||
|
|
||
| foreach ($token->phpAttributes as $index => $content) { | ||
| $attributes[] = new PhpAttribute((string) $index, $content); | ||
| } | ||
|
|
||
| if ($token->type === TokenType::CONTENT) { | ||
| $text = $token->compile(); | ||
|
|
||
| if (trim($text) === '') { | ||
| return null; | ||
| } | ||
|
|
||
| return new TextElement(text: $text); | ||
| } | ||
|
|
||
| if ($token->type === TokenType::WHITESPACE) { | ||
| return new WhitespaceElement($token->content); | ||
| } | ||
|
|
||
| if (! $token->tag || $token->type === TokenType::COMMENT || $token->type === TokenType::PHP) { | ||
| return new RawElement(token: $token, tag: null, content: $token->compile()); | ||
| } | ||
|
|
||
| $attributes = $token->htmlAttributes; | ||
|
|
||
| foreach ($token->phpAttributes as $index => $content) { | ||
| $attributes[] = new PhpAttribute((string) $index, $content); | ||
| } | ||
|
|
||
| if ($token->tag === 'code' || $token->tag === 'pre') { | ||
| return new RawElement( | ||
| $element = new TextElement(text: $text); | ||
| } elseif ($token->type === TokenType::WHITESPACE) { | ||
| $element = new WhitespaceElement($token->content); | ||
| } elseif ($token->type !== TokenType::PHP && (! $token->tag || $token->type === TokenType::COMMENT)) { | ||
| $element = new RawElement( | ||
| token: $token, | ||
| tag: null, | ||
| content: $token->compile(), | ||
| ); | ||
| } elseif ($token->tag === 'code' || $token->tag === 'pre') { | ||
| $element = new RawElement( | ||
| token: $token, | ||
| tag: $token->tag, | ||
| content: $token->compileChildren(), | ||
| attributes: $attributes, | ||
| ); | ||
| } | ||
|
|
||
| if ($viewComponentClass = $this->viewConfig->viewComponents[$token->tag] ?? null) { | ||
| } elseif ($token->type === TokenType::PHP) { | ||
| $element = new PhpElement( | ||
| token: $token, | ||
| content: $token->compile(), | ||
| ); | ||
| } elseif ($viewComponentClass = $this->viewConfig->viewComponents[$token->tag] ?? null) { | ||
| $element = new ViewComponentElement( | ||
| token: $token, | ||
| environment: $this->environment, | ||
| compiler: $this->compiler, | ||
| viewCache: $this->viewCache, | ||
| viewComponent: $viewComponentClass, | ||
| attributes: $attributes, | ||
| ); | ||
|
|
@@ -120,23 +116,15 @@ private function makeElement(Token $token, ?Element $parent): ?Element | |
| ); | ||
| } | ||
|
|
||
| $children = []; | ||
| $element->setParent($parent); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Refactored parent/child setting because not all elements have children, but all have a parent. |
||
|
|
||
| foreach ($token->children as $child) { | ||
| $childElement = $this->clone()->makeElement( | ||
| $this->clone()->make( | ||
| token: $child, | ||
| parent: $parent, | ||
| parent: $element, | ||
| ); | ||
|
|
||
| if ($childElement === null) { | ||
| continue; | ||
| } | ||
|
|
||
| $children[] = $childElement; | ||
| } | ||
|
|
||
| $element->setChildren($children); | ||
|
|
||
| return $element; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -121,6 +121,8 @@ public function setParent(?Element $parent): self | |
| { | ||
| $this->parent = $parent; | ||
|
|
||
| $this->parent->setChildren([...$this->parent->getChildren(), $this]); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See before: not all elements have children but all have a parent |
||
|
|
||
| return $this; | ||
| } | ||
|
|
||
|
|
@@ -141,10 +143,7 @@ public function setChildren(array $children): self | |
| $previous = null; | ||
|
|
||
| foreach ($children as $child) { | ||
| $child | ||
| ->setParent($this) | ||
| ->setPrevious($previous); | ||
|
|
||
| $child->setPrevious($previous); | ||
| $previous = $child; | ||
| } | ||
|
|
||
|
|
@@ -170,4 +169,13 @@ public function unwrap(string $elementClass): ?Element | |
|
|
||
| return null; | ||
| } | ||
|
|
||
| public function getImports(): array | ||
| { | ||
| if ($this->parent) { | ||
| return $this->parent->getImports(); | ||
| } | ||
|
|
||
| return []; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Tempest\View\Elements; | ||
|
|
||
| use Tempest\View\Element; | ||
| use Tempest\View\Parser\Token; | ||
| use Tempest\View\WithToken; | ||
|
|
||
| final class PhpElement implements Element, WithToken | ||
| { | ||
| use IsElement; | ||
|
|
||
| public function __construct( | ||
| public readonly Token $token, | ||
| private readonly string $content, | ||
| ) {} | ||
|
|
||
| public function compile(): string | ||
| { | ||
| return $this->content; | ||
| } | ||
|
|
||
| public function getImports(): array | ||
| { | ||
| preg_match_all('/^\s*use .*;/m', $this->content, $matches); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently we're still using a regex, we could improve if needed once this refactor works |
||
|
|
||
| return $matches[0] ?? []; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| <?php | ||
| declare(strict_types=1); | ||
|
|
||
| namespace Tempest\View\Elements; | ||
|
|
||
| use Tempest\View\Element; | ||
|
|
||
| final class RootElement implements Element | ||
| { | ||
| use IsElement; | ||
|
|
||
| private array $inheritedImports = []; | ||
|
|
||
| public function compile(): string | ||
| { | ||
| $compiled = []; | ||
|
|
||
| foreach ($this->children as $element) { | ||
| $compiled[] = $element->compile(); | ||
| } | ||
|
|
||
| return implode($compiled); | ||
| } | ||
|
|
||
| public function getImports(): array | ||
| { | ||
| $imports = []; | ||
|
|
||
| $this->mergeImports($imports, $this->inheritedImports); | ||
|
|
||
| foreach ($this->children as $child) { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We only want to resolve imports from |
||
| if ($child instanceof PhpElement) { | ||
| $this->mergeImports($imports, $child->getImports()); | ||
| } | ||
| } | ||
|
|
||
| return array_values($imports); | ||
| } | ||
|
|
||
| public function setInheritedImports(array $imports): self | ||
| { | ||
| $this->inheritedImports = $imports; | ||
|
|
||
| return $this; | ||
| } | ||
|
|
||
| private function mergeImports(array &$imports, array $candidates): void | ||
| { | ||
| foreach ($candidates as $import) { | ||
| $import = trim($import); | ||
|
|
||
| if ($import === '') { | ||
| continue; | ||
| } | ||
|
|
||
| $imports[$import] = $import; | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ElementFactorynow always requires a parent/root element