TemplateFactory: add support for custom Template implementation#159
TemplateFactory: add support for custom Template implementation#159mrtnzlml wants to merge 1 commit intonette:masterfrom mrtnzlml:customTemplate2
Conversation
| $this->user = $user; | ||
| $this->cacheStorage = $cacheStorage; | ||
|
|
||
| \Nette\Utils\Validators::assert($templateClassName, 'string'); |
There was a problem hiding this comment.
This is not needed, as it is not convention in Nette.
Also PHP 7 is comming soon with type checks.
There was a problem hiding this comment.
It's not convention in Nette doesn't mean I cannot do that, does it? And it's not true that it's not needed (before PHP 7). Moreover PHP 7 is not used in Nette yet.
There was a problem hiding this comment.
Don't add it here please. Code would be inconsistent with other strings (scalar) types in methods.
Rather check if this class implements ITemplate interface. Because it would fail later and now it silently pass in here creating invalid object.
There was a problem hiding this comment.
I have to do both checks because without checking string it's possible to call new on integer for example. From my point of view validation is needed. Can you help me how to prevent calling $template = new $this->templateClassName($latte); (line 57) with wrong $this->templateClassName type?
There was a problem hiding this comment.
I see. If so, add them both here. It doesn't make sense splitting them.
Standalone method would be best, e.g. validateTemplateClassName().
There was a problem hiding this comment.
Ok, I'll improve it in next iteration. I am used to doing validations in constructor in my projects...
There was a problem hiding this comment.
Yes. This should be in constructor.
Something like this:
public function __construct($className = Tempalte::class)
{
$this->validateTemplateClassName($className);
$this->className = $className;
}
private function validateTemplateClassName($className)
{
// is string
// implements ITemplate
// throw exception otherwise
}So move this runtime check https://github.com/nette/application/pull/159/files#diff-0bc2c99180eba473648d41663a94e15cR58 there
There was a problem hiding this comment.
I don't see benefit here. It's only because of calling line Nette\Utils\Validators::assert($templateClassName, 'string'); here. I don't like it... :)
Waiting for more opinions...
(outdated comment)
There was a problem hiding this comment.
We missunderstood a bit. I've updated the code to explain better. Is it more clear now?
There was a problem hiding this comment.
Yeah, it cannot be done like that. It's because $template is created during createTemplate call (everytime you call it) and therefore you cannot test its instance in constructor. You don't have ITemplate in constructor. What implements ITemplate? The answer is: created $template.
| { | ||
| $latte = $this->latteFactory->create(); | ||
| $template = new Template($latte); | ||
| $template = new $this->templateClassName($latte); |
There was a problem hiding this comment.
This looks ok. Just wondering about use case, when custom class has different arguments.
There was a problem hiding this comment.
It should be in another PR if needed I think. It's not possible to add another arguments with current implementation as well.
There was a problem hiding this comment.
Ok, let's skip it now then.
Btw, do you need $latte as argument in your implementation?
There was a problem hiding this comment.
Not necessarily because I am extending Nette\Bridges\ApplicationLatte\Template and then calling $this->getLatte(). So the answer is no but original implementation needs it.
| Assert::same([], $template->flashes); | ||
| Assert::same('ok', $template->render()); | ||
| $template->setFile('bla'); | ||
| Assert::same('alb', $template->render()); |
| { | ||
| private $file = 'ko'; | ||
|
|
||
| public function render() { |
There was a problem hiding this comment.
TODO: fix coding standards
|
Setter (as suggested in #141) is IMHO better (template class name has a character of configuration not of dependency) |
|
@JanTvrdik I love this idea (despite the naming - just idea) even more. But I agree with you. |
|
You mean the „TemplateClassFactory“ which would create just instance of Good solutions are either setter or splitting the instance creation and configuration to multiple methods to allow overriding part of the logic. |
|
I agree with @JanTvrdik in setter as configuration. If you want to change class name, you might want to do it multiple times. With constructor dependency you would have to create new instance of TemplateFactorry. |
|
|
|
Ok guys, what about this implementation? |
| public $defaults = [ | ||
| 'xhtml' => FALSE, | ||
| 'macros' => [], | ||
| 'templateClass' => \Nette\Bridges\ApplicationLatte\Template::class |
There was a problem hiding this comment.
Is this coupling between bridges fine?
| $latte = $this->latteFactory->create(); | ||
| $template = new Template($latte); | ||
| $template = new $this->templateClass($latte); | ||
| if (!$template instanceof UI\ITemplate) { |
There was a problem hiding this comment.
This check should be moved to setTemplateClass (see is_a with third argument set to TRUE)
There was a problem hiding this comment.
You will also probably need to call class_exists on the $templateClass to load the class to memory
There was a problem hiding this comment.
Thanks - I didn't know the third argument of is_a.
| return $template; | ||
| } | ||
|
|
||
| public function changeTemplateClass($templateClass) |
There was a problem hiding this comment.
should be named setTemplateClass (due to consistency)
|
|
||
| public function changeTemplateClass($templateClass) | ||
| { | ||
| \Nette\Utils\Validators::assert($templateClass, 'string'); |
There was a problem hiding this comment.
should be removed (due to consistency)
There was a problem hiding this comment.
How would you prevent passing something different than string? I understand your note about consistency, but it's wrong I think. Or should I remove usage of Validators and use custom check?
There was a problem hiding this comment.
How would you prevent passing something different than string?
Here is the secret – you don't prevent passing something different because this is PHP and because it would kill code readability and performance. Pretty much every Nette method without typehint can be broken by passing the wrong type.
| public $defaults = [ | ||
| 'xhtml' => FALSE, | ||
| 'macros' => [], | ||
| 'templateClass' => \Nette\Bridges\ApplicationLatte\Template::class |
There was a problem hiding this comment.
- should be simplified to
Nette\Bridges\ApplicationLatte\Template::class - the usecase se imho too narrow to add option to the extension so I would remove this entirely
- if added, the default value should be
NULLand the setter call should only be emitted if the value is not NULL
|
Updated -
Thanks for help. |
| { | ||
| \Nette\Utils\Validators::assert($templateClass, 'string'); | ||
| if (!is_a($templateClass, UI\ITemplate::class, TRUE)) { | ||
| throw new \Nette\Utils\AssertionException('Template should be instance of ' . UI\ITemplate::class . ", $templateClass given."); |
There was a problem hiding this comment.
- should be Nette\InvalidArgumentException, imho
- you need to call
!class_exist($templateClass) ||before!is_abecauseis_adoes not trigger autoloading
| $templateFactory = $builder->addDefinition($this->prefix('templateFactory')) | ||
| ->setClass(Nette\Application\UI\ITemplateFactory::class) | ||
| ->setFactory(Nette\Bridges\ApplicationLatte\TemplateFactory::class); | ||
| if($config['templateClass'] !== NULL) { |
|
Updated according to comments. |
| public function setTemplateClass($templateClass) | ||
| { | ||
| if (!class_exists($templateClass) || !is_a($templateClass, UI\ITemplate::class, TRUE)) { | ||
| throw new \Nette\InvalidArgumentException('Template should be instance of ' . UI\ITemplate::class . ", $templateClass given."); |
There was a problem hiding this comment.
Nette\InvalidArgumentException- "Class '$templateClass' does not implement ... ."
|
|
||
| public function setTemplateClass($templateClass) | ||
| { | ||
| if (!class_exists($templateClass) || !is_a($templateClass, UI\ITemplate::class, TRUE)) { |
There was a problem hiding this comment.
It should be instance of Template::class
There was a problem hiding this comment.
Why not ITemplate? Can you please elaborate it more @dg? I don't fully understand... :) Thank you.
There was a problem hiding this comment.
My guess is that it's because TemplateFactory has @return Template annotation. However Nette itself does not seem to rely on the instance being Template.
There was a problem hiding this comment.
It has @return Template, it assumes __construct(Latte\Engine) and __set for variables (or fields).
With this change it's possible to simply use custom Template implementation. Before this commit Template object initialization was hard-coded into TemplateFactory (related issue: #141). Usage example (config.neon): latte: templateClass: App\CustomTemplateImplementation Custom template implementation MUST implement Nette\Application\UI\ITemplate interface.
|
Rebased on top of master branch (because of failing tests). |
|
Thank you @dg! :) |
|
|
||
| public function setTemplateClass($templateClass) | ||
| { | ||
| if (!class_exists($templateClass) || !is_a($templateClass, Template::class, TRUE)) { |
There was a problem hiding this comment.
IMHO is_a(..., ..., TRUE) triggers autoloader. Otherwise it is PHP bug.
| public function setTemplateClass($templateClass) | ||
| { | ||
| if (!class_exists($templateClass) || !is_a($templateClass, Template::class, TRUE)) { | ||
| throw new Nette\InvalidArgumentException("Class $templateClass does not extend " . Template::class . ' or it does not exist.'); |
| @@ -112,4 +115,12 @@ public function createTemplate(UI\Control $control = NULL) | |||
| return $template; | |||
| } | |||
|
|
|||
| ->setClass(Nette\Application\UI\ITemplateFactory::class) | ||
| ->setFactory(Nette\Bridges\ApplicationLatte\TemplateFactory::class); | ||
| if ($config['templateClass'] !== NULL) { | ||
| $templateFactory->addSetup('?->setTemplateClass(?)', ['@self', $config['templateClass']]); |
There was a problem hiding this comment.
Why not simply $templateFactory->addSetup('setTemplateClass', [$config['templateClass']]);?
With this change it's possible to simply use custom Template implementation. Before this commit Template object initialization was hard-coded into TemplateFactory (related issue: #141). Usage example (
config.neon):Custom template implementation MUST implement Nette\Application\UI\ITemplate interface.
This is more request for comments than real PR. Thank you.