-
Notifications
You must be signed in to change notification settings - Fork 0
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
Introduce concept of scopes, with the engine simply being one. #12
base: master
Are you sure you want to change the base?
Conversation
This opens possibilities to render contexts in different scopes, e.g. a HTTP request scope, which then pass language and internationalization preferences See xp-forge/handlebars-templates#1
...which is essentially the same but offers greater flexibility
...to clarify purpose
Range of BC breaksDiff against handlebars library to restore compatibility:
Detailsdiff --git a/src/main/php/com/handlebarsjs/Decoration.class.php b/src/main/php/com/handlebarsjs/Decoration.class.php
index 2582c01..4cfce99 100755
--- a/src/main/php/com/handlebarsjs/Decoration.class.php
+++ b/src/main/php/com/handlebarsjs/Decoration.class.php
@@ -37,11 +37,11 @@ class Decoration {
* @return void
*/
public function enter($context) {
- if (isset($context->engine->helpers[$this->kind])) {
- $f= $context->engine->helpers[$this->kind];
+ if (isset($context->scope->helpers[$this->kind])) {
+ $f= $context->scope->helpers[$this->kind];
$f($this->fn, $context, $this->options);
} else {
- throw new MethodNotImplementedException('No such decorator '.$this->kind);
+ throw new MethodNotImplementedException('No such decorator', $this->kind);
}
}
}
\ No newline at end of file
diff --git a/src/main/php/com/handlebarsjs/HandlebarsEngine.class.php b/src/main/php/com/handlebarsjs/HandlebarsEngine.class.php
index aa3922c..d762fbc 100755
--- a/src/main/php/com/handlebarsjs/HandlebarsEngine.class.php
+++ b/src/main/php/com/handlebarsjs/HandlebarsEngine.class.php
@@ -41,7 +41,7 @@ class HandlebarsEngine {
return $options[0][$options[1]] ?? null;
},
'*inline' => function($node, $context, $options) {
- $context->engine->templates->declare($options[0]($node, $context, []), $node);
+ $context->scope->templates->declare($options[0]($node, $context, []), $node);
}
];
}
@@ -172,7 +172,7 @@ class HandlebarsEngine {
*/
public function evaluate(Template $template, $arg) {
$c= $arg instanceof Context ? $arg : new DataContext($arg);
- return $template->evaluate($c->withEngine($this));
+ return $template->evaluate($c->inScope($this));
}
/**
@@ -185,7 +185,7 @@ class HandlebarsEngine {
*/
public function write(Template $template, $arg, $out) {
$c= $arg instanceof Context ? $arg : new DataContext($arg);
- $template->write($c->withEngine($this), $out);
+ $template->write($c->inScope($this), $out);
}
/**
diff --git a/src/main/php/com/handlebarsjs/HashContext.class.php b/src/main/php/com/handlebarsjs/HashContext.class.php
index b75b6d3..82d1061 100755
--- a/src/main/php/com/handlebarsjs/HashContext.class.php
+++ b/src/main/php/com/handlebarsjs/HashContext.class.php
@@ -30,10 +30,11 @@ class HashContext extends Context {
* Returns a context inherited from this context
*
* @param var $result
+ * @param parent $parent
* @return self
*/
- public function asContext($result) {
- return new DataContext($result, $this);
+ public function asContext($result, $parent= null) {
+ return new DataContext($result, $parent ?? $this);
}
/**
diff --git a/src/main/php/com/handlebarsjs/ListContext.class.php b/src/main/php/com/handlebarsjs/ListContext.class.php
index 5de5269..c9e9bf6 100755
--- a/src/main/php/com/handlebarsjs/ListContext.class.php
+++ b/src/main/php/com/handlebarsjs/ListContext.class.php
@@ -30,10 +30,11 @@ class ListContext extends Context {
* Returns a context inherited from this context
*
* @param var $result
+ * @param parent $parent
* @return self
*/
- public function asContext($result) {
- return new DataContext($result, $this);
+ public function asContext($result, $parent= null) {
+ return new DataContext($result, $parent ?? $this);
}
/**
diff --git a/src/main/php/com/handlebarsjs/PartialBlockHelper.class.php b/src/main/php/com/handlebarsjs/PartialBlockHelper.class.php
index 2061f0d..0924bc3 100755
--- a/src/main/php/com/handlebarsjs/PartialBlockHelper.class.php
+++ b/src/main/php/com/handlebarsjs/PartialBlockHelper.class.php
@@ -31,27 +31,27 @@ class PartialBlockHelper extends BlockNode {
* @param io.streams.OutputStream $out
*/
public function write($context, $out) {
- $templates= $context->engine->templates();
+ $templates= $context->scope->templates();
// {{#> partial context}} vs {{> partial key="Value"}}
if (isset($this->options[0])) {
- $context= $context->newInstance($this->options[0]($this, $context, []));
+ $context= $context->asContext($this->options[0]($this, $context, []));
} else if ($this->options) {
$pass= [];
foreach ($context->asTraversable($this->options) as $key => $value) {
$pass[$key]= $value($this, $context, []);
}
- $context= $context->newInstance($pass);
+ $context= $context->asContext($pass);
}
$source= $templates->source($this->name);
if ($source->exists()) {
$this->fn->enter($context);
- $template= $context->engine->load($this->name, $this->start, $this->end, '');
+ $template= $context->scope->load($this->name, $this->start, $this->end, '');
$previous= $templates->register('@partial-block', $this->fn->block());
try {
- $context->engine->write($template, $context, $out);
+ $context->scope->write($template, $context, $out);
} finally {
$templates->register('@partial-block', $previous);
}
diff --git a/src/main/php/com/handlebarsjs/PartialNode.class.php b/src/main/php/com/handlebarsjs/PartialNode.class.php
index 7762aef..3582b07 100755
--- a/src/main/php/com/handlebarsjs/PartialNode.class.php
+++ b/src/main/php/com/handlebarsjs/PartialNode.class.php
@@ -92,16 +92,16 @@ class PartialNode extends Node {
// {{> partial context}} vs {{> partial key="Value"}}
if (isset($this->options[0])) {
- $context= $context->newInstance($this->options[0]($this, $context, []));
+ $context= $context->asContext($this->options[0]($this, $context, []));
} else if ($this->options) {
$pass= [];
foreach ($context->asTraversable($this->options) as $key => $value) {
$pass[$key]= $value($this, $context, []);
}
- $context= $context->newInstance($pass);
+ $context= $context->asContext($pass);
}
- $engine= $context->engine;
+ $engine= $context->scope;
$template= $engine->load($this->template->__invoke($this, $context, []), '{{', '}}', $this->indent);
$engine->write($template, $context, $out);
}
diff --git a/src/main/php/com/handlebarsjs/Templates.class.php b/src/main/php/com/handlebarsjs/Templates.class.php
index ecea071..b171738 100755
--- a/src/main/php/com/handlebarsjs/Templates.class.php
+++ b/src/main/php/com/handlebarsjs/Templates.class.php
@@ -1,9 +1,8 @@
<?php namespace com\handlebarsjs;
-use com\github\mustache\templates\{Compiled, NotFound, Source, Tokens};
-use com\github\mustache\{Node, Template, TemplateListing};
+use com\github\mustache\templates\{Compiled, NotFound, Source, Listing, InString};
+use com\github\mustache\{Node, Template};
use lang\{ClassLoader, IllegalArgumentException};
-use text\StringTokenizer;
/**
* Template loading implementation
@@ -18,7 +17,7 @@ class Templates {
/** @return lang.XPClass */
private function composite() {
if (null === self::$composite) {
- self::$composite= ClassLoader::defineClass('CompositeListing', TemplateListing::class, [], [
+ self::$composite= ClassLoader::defineClass('CompositeListing', Listing::class, [], [
'templates' => null,
'delegate' => null,
'__construct' => function($templates, $delegate) {
@@ -78,7 +77,7 @@ class Templates {
} else if ($content instanceof Node) {
$this->templates[$name]= new Compiled(new Template($name, $content));
} else {
- $this->templates[$name]= new Tokens($name, new StringTokenizer($content));
+ $this->templates[$name]= new InString($name, $content);
}
return $previous;
@@ -92,7 +91,7 @@ class Templates {
* @return com.github.mustache.templates.Source
*/
public function tokens($content, $name= '(string)') {
- return new Tokens($name, new StringTokenizer((string)$content));
+ return new InString($name, (string)$content);
}
/**
@@ -111,12 +110,12 @@ class Templates {
}
}
- /** @return com.github.mustache.TemplateListing */
+ /** @return com.github.mustache.templates.Listing */
public function listing() {
if ($this->delegate) {
return $this->composite()->newInstance($this->templates, $this->delegate->listing());
} else {
- return new TemplateListing('', function($package) { return array_keys($this->templates); });
+ return new Listing('', function($package) { return array_keys($this->templates); });
}
}
}
\ No newline at end of file
diff --git a/src/test/php/com/handlebarsjs/unittest/HelperTest.class.php b/src/test/php/com/handlebarsjs/unittest/HelperTest.class.php
index 5cab52a..94da851 100755
--- a/src/test/php/com/handlebarsjs/unittest/HelperTest.class.php
+++ b/src/test/php/com/handlebarsjs/unittest/HelperTest.class.php
@@ -1,19 +1,19 @@
<?php namespace com\handlebarsjs\unittest;
use com\github\mustache\InMemory;
-use com\github\mustache\templates\Templates;
+use com\github\mustache\templates\Sources;
use com\handlebarsjs\HandlebarsEngine;
/** Base class for all helper tests */
abstract class HelperTest {
/** Returns in-memory templates initialized from a given map */
- protected function templates(array $templates= []): Templates {
+ protected function templates(array $templates= []): Sources {
return new InMemory($templates);
}
/** Returns handlebars engine with specified templates */
- protected function engine(Templates $templates= null): HandlebarsEngine {
+ protected function engine(Sources $templates= null): HandlebarsEngine {
return (new HandlebarsEngine())->withTemplates($templates ?? $this->templates());
}
|
...and reduce reliance on reflection at the same time, see xp-framework/rfc#338
With all this, this is how Mustache and Handlebars engines compare: // MustacheEngine class extends Scope
public function evaluate(Template $template, $context) {
$c= $context instanceof Context ? $context : new DataContext($context);
return $template->evaluate($c->inScope($this));
}
// HandlebarsEngine class comes without parent, Transformation extends Scope
public function evaluate(Template $template, $context) {
$c= $context instanceof Context ? $context : new DataContext($context);
return $template->evaluate($c->inScope(new Transformation($this->templates, $this->helpers)));
} While in Mustache, the engine itself is the scope, in handlebars we create a new Transformation scope for each template evaluation. This is so we can support However, we now have to know these internal implementation details - that templates need to be passed a Transformation instance when using the Handlebars engine and on the other hand when using Mustache, we can simply pass the engine - if we use its $context= ['user' => '@test'];
$template= $engine->compile('Hello {{user}}');
// This is where it gets messy:
$c= $context instanceof Context ? $context : new DataContext($context);
$template->evaluate($c->inScope($engine));
$template->evaluate($c->inScope(new Transformation($engine->templates(), $engine->helpers()))); If we add a method to create a context, we could encapsulate these details: // On the engine:
public function context($context) {
$c= $context instanceof Context ? $context : new DataContext($context);
return $c->inScope($this); // or inScope(new Transformation(...))
}
// The above code then would read:
$template= $engine->compile('Hello {{user}}');
$template->evaluate($engine->context(['user' => '@test'])); Still, it's not easily possible to integrate the concept of something like a |
If we wrap scopes around their parents, it might work: // On the scope:
public function wrap(self $parent) {
$this->templates= $parent->templates;
$this->helpers= $parent->helpers;
return $this;
}
// On the engine:
public function context($context, Scope $scope= null) {
$c= $context instanceof Context ? $context : new DataContext($context);
return $c->inScope($scope ? $scope->wrap($this) : $this); // same with s/$this/new Transformation(...)/g
}
// The above code then would read:
$template= $engine->compile('Hello {{user}}');
$template->evaluate($engine->context(['user' => '@test'], new RequestScope($request))); ...but only for the top-most scope, as |
Another idea would be to rename scope helpers to variables (or globals) and then have the following possibility: yield 't' => function($in, $context, $options) {
$lang= $context->scope->globals['request']->values('user')['language'];
// ...
} ...or even registering the language beforehand: // In scope:
public function including($globals) {
$this->globals+= $globals;
return $this;
}
// Call
$template= $engine->compile('Hello {{user}}');
$template->evaluate($engine->context(['user' => '@test'])->including(['lang' => $request->values('user')['language']));
// Helper
yield 't' => function($in, $context, $options) {
$lang= $context->scope->globals['lang'];
// ...
} Alternatively, we could pass an object to globals: class LocalizationOf {
public $language;
public function __construct($request) {
$this->language= $this->negotiate($request->header('Accept-Language'));
}
}
// In scope:
public function including($globals) {
$this->globals= $this->helpers['globals']= $globals;
return $this;
}
// Call
$template= $engine->compile('Hello {{user}}');
$template->evaluate($engine->context(['user' => '@test'])->including(new LocalizationOf($request));
// Helper
yield 't' => function($in, $context, $options) {
$lang= $context->scope->globals->language;
// ...
} |
This opens possibilities to render contexts in different scopes, e.g. a HTTP request scope, which then pass language and internationalization preferences
Refactoring
com.github.mustache.Scope
classContext::inScope()
to replace::withEngine()
Templating
The templating mechanism consist of consists of template sources, which are responsible for loading the templates' code from a given source (e.g. the filesystem) and the template parser, which parses these tokens into a parse tree.
Template sources are represented by the Sources class. The following templates sources exist:
Their
source()
methods return Source instances:...which are then used to return the tokenized templates.
See xp-forge/handlebars-templates#1