Skip to content

Commit c3dc49e

Browse files
SKETCH: Introduce App class
The App class lets us reuse the same HtmlParser and JsExpressionParser across multiple components (note that JsExpressionParser returns an unevaluated expression, so it’s fine to cache its results even if they’re going to be evaluated with different data later), and also keeps a library of components. The components are loaded and compiled on-demand the first time they’re used, and reused afterwards; for this, Component now clones its root node before operating on it. For Component to work correctly, it’s important that the cloned node still has a parent node (otherwise it’s considered to have been removed from the DOM, and most of the processing for it is skipped); use a cloneOwner node for that purpose. (For now, it’s just the document’s document element, i.e. the <html>, but we could make it a separate node later if we prefer. It just has to be outside the root node.) (If you’re wondering, my motivation for making the components input either string or callable is that it probably makes sense not to load all the component files into strings upfront, but to only do that if the component is needed.) TODO: While all the existing tests pass unmodified, which is nice, we should also have some new tests for App – for instance, that the cloning actually does its job (i.e. evaluating the same component in the same app with different data yields the expected different results).
1 parent 5c214c1 commit c3dc49e

File tree

3 files changed

+89
-36
lines changed

3 files changed

+89
-36
lines changed

src/App.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
declare( strict_types = 1 );
4+
5+
namespace WMDE\VueJsTemplating;
6+
7+
use Exception;
8+
use WMDE\VueJsTemplating\JsParsing\BasicJsExpressionParser;
9+
use WMDE\VueJsTemplating\JsParsing\CachingExpressionParser;
10+
use WMDE\VueJsTemplating\JsParsing\JsExpressionParser;
11+
12+
class App {
13+
14+
/** @var HtmlParser */
15+
private $htmlParser;
16+
17+
/** @var JsExpressionParser */
18+
private $expressionParser;
19+
20+
/** @var (Component|string|callable)[] */
21+
private $components;
22+
23+
/**
24+
* @param (string|callable)[] $components The available components.
25+
* The key is the component name, and the value is either the HTML for the component,
26+
* or a callable that will return the HTML when called on demand.
27+
* @param callable[] $methods The available methods.
28+
* The key is the method name, the value is the corresponding callable.
29+
*/
30+
public function __construct( array $components, array $methods ) {
31+
$this->components = $components;
32+
$this->htmlParser = new HtmlParser();
33+
$this->expressionParser = new CachingExpressionParser( new BasicJsExpressionParser( $methods ) );
34+
}
35+
36+
public function evaluateExpression( string $expression, array $data ) {
37+
return $this->expressionParser->parse( $expression )
38+
->evaluate( $data );
39+
}
40+
41+
public function renderComponent( string $componentName, array $data ): string {
42+
return $this->getComponent( $componentName )
43+
->render( $data );
44+
}
45+
46+
private function getComponent( string $componentName ): Component {
47+
$component = $this->components[$componentName] ?? null;
48+
if ( $component === null ) {
49+
throw new Exception( "Unknown component: $componentName" );
50+
}
51+
52+
if ( !( $component instanceof Component ) ) {
53+
if ( is_callable( $component ) ) {
54+
$html = $component();
55+
} else {
56+
$html = $component;
57+
}
58+
/** @var string $html */
59+
$document = $this->htmlParser->parseHtml( $html );
60+
$rootNode = $this->htmlParser->getRootNode( $document );
61+
$component = new Component( $rootNode, $this );
62+
$this->components[$componentName] = $component;
63+
}
64+
65+
return $component;
66+
}
67+
68+
}

src/Component.php

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@
88
use DOMNode;
99
use DOMNodeList;
1010
use DOMText;
11-
use WMDE\VueJsTemplating\JsParsing\BasicJsExpressionParser;
12-
use WMDE\VueJsTemplating\JsParsing\CachingExpressionParser;
13-
use WMDE\VueJsTemplating\JsParsing\JsExpressionParser;
1411

1512
class Component {
1613

@@ -20,31 +17,34 @@ class Component {
2017
private $rootNode;
2118

2219
/**
23-
* @var JsExpressionParser
20+
* @var DOMNode An arbitrary node to reparent cloned root nodes too,
21+
* so that they can still have a parent node.
22+
* (This is required for {@link self::isRemovedFromTheDom()}.)
2423
*/
25-
private $expressionParser;
24+
private $cloneOwner;
2625

27-
/**
28-
* @param DOMElement $rootNode
29-
* @param callable[] $methods
30-
*/
31-
public function __construct( DOMElement $rootNode, array $methods ) {
26+
/** @var App */
27+
private $app;
28+
29+
public function __construct( DOMElement $rootNode, App $app ) {
3230
$this->rootNode = $rootNode;
33-
$this->expressionParser = new CachingExpressionParser( new BasicJsExpressionParser( $methods ) );
31+
$this->app = $app;
32+
33+
$this->cloneOwner = $rootNode->ownerDocument->documentElement;
3434
}
3535

3636
/**
37-
* Note: this method is not currently safe to call repeatedly
38-
* (the internal root node is modified in-place).
39-
*
4037
* @param array $data
4138
*
4239
* @return string HTML
4340
*/
4441
public function render( array $data ) {
45-
$this->handleNode( $this->rootNode, $data );
42+
$rootNode = $this->rootNode->cloneNode( true );
43+
$this->cloneOwner->appendChild( $rootNode );
44+
$this->handleNode( $rootNode, $data );
45+
$this->cloneOwner->removeChild( $rootNode );
4646

47-
return $this->rootNode->ownerDocument->saveHTML( $this->rootNode );
47+
return $rootNode->ownerDocument->saveHTML( $rootNode );
4848
}
4949

5050
/**
@@ -94,8 +94,7 @@ private function replaceMustacheVariables( DOMNode $node, array $data ) {
9494
preg_match_all( $regex, $text, $matches );
9595

9696
foreach ( $matches['expression'] as $index => $expression ) {
97-
$value = $this->expressionParser->parse( $expression )
98-
->evaluate( $data );
97+
$value = $this->app->evaluateExpression( $expression, $data );
9998

10099
$text = str_replace( $matches[0][$index], $value, $text );
101100
}
@@ -114,8 +113,7 @@ private function handleAttributeBinding( DOMElement $node, array $data ) {
114113
continue;
115114
}
116115

117-
$value = $this->expressionParser->parse( $attribute->value )
118-
->evaluate( $data );
116+
$value = $this->app->evaluateExpression( $attribute->value, $data );
119117

120118
$name = substr( $attribute->name, 1 );
121119
if ( is_bool( $value ) ) {
@@ -146,7 +144,7 @@ private function handleIf( DOMNodeList $nodes, array $data ) {
146144
if ( $node->hasAttribute( 'v-if' ) ) {
147145
$conditionString = $node->getAttribute( 'v-if' );
148146
$node->removeAttribute( 'v-if' );
149-
$condition = $this->evaluateExpression( $conditionString, $data );
147+
$condition = $this->app->evaluateExpression( $conditionString, $data );
150148

151149
if ( !$condition ) {
152150
$nodesToRemove[] = $node;
@@ -214,16 +212,6 @@ private function handleRawHtml( DOMNode $node, array $data ) {
214212
}
215213
}
216214

217-
/**
218-
* @param string $expression
219-
* @param array $data
220-
*
221-
* @return bool
222-
*/
223-
private function evaluateExpression( $expression, array $data ) {
224-
return $this->expressionParser->parse( $expression )->evaluate( $data );
225-
}
226-
227215
private function removeNode( DOMElement $node ) {
228216
$node->parentNode->removeChild( $node );
229217
}

src/Templating.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,8 @@ class Templating {
1212
* @return string
1313
*/
1414
public function render( $template, array $data, array $methods = [] ) {
15-
$htmlParser = new HtmlParser();
16-
$document = $htmlParser->parseHtml( $template );
17-
$rootNode = $htmlParser->getRootNode( $document );
18-
$component = new Component( $rootNode, $methods );
19-
return $component->render( $data );
15+
$app = new App( [ 'root' => $template ], $methods );
16+
return $app->renderComponent( 'root', $data );
2017
}
2118

2219
}

0 commit comments

Comments
 (0)