-
-
Notifications
You must be signed in to change notification settings - Fork 116
TemplateFactory: add support for custom Template implementation #159
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
Conversation
| $this->user = $user; | ||
| $this->cacheStorage = $cacheStorage; | ||
|
|
||
| \Nette\Utils\Validators::assert($templateClassName, 'string'); |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We missunderstood a bit. I've updated the code to explain better. Is it more clear now?
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks ok. Just wondering about use case, when custom class has different arguments.
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, let's skip it now then.
Btw, do you need $latte as argument in your implementation?
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.
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()); |
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.
Awesome test, thank you.
| { | ||
| private $file = 'ko'; | ||
|
|
||
| public function render() { |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check should be moved to setTemplateClass (see is_a with third argument set to TRUE)
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks - I didn't know the third argument of is_a.
| return $template; | ||
| } | ||
|
|
||
| public function changeTemplateClass($templateClass) |
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.
should be named setTemplateClass (due to consistency)
|
|
||
| public function changeTemplateClass($templateClass) | ||
| { | ||
| \Nette\Utils\Validators::assert($templateClass, 'string'); |
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.
should be removed (due to consistency)
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- 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) { |
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.
missing space after if
|
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be instance of Template::class
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.
Why not ITemplate? Can you please elaborate it more @dg? I don't fully understand... :) Thank you.
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.'); |
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.
without it
|
|
||
| return $template; | ||
| } | ||
|
|
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.
missing empty line
| ->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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.