Skip to content

Conversation

@mrtnzlml
Copy link
Contributor

@mrtnzlml mrtnzlml commented Sep 25, 2016

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.

This is more request for comments than real PR. Thank you.

$this->user = $user;
$this->cacheStorage = $cacheStorage;

\Nette\Utils\Validators::assert($templateClassName, 'string');

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.

Copy link
Contributor Author

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.

Copy link

@TomasVotruba TomasVotruba Sep 25, 2016

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.

Copy link
Contributor Author

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?

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().

Copy link
Contributor Author

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...

Copy link

@TomasVotruba TomasVotruba Sep 25, 2016

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

Copy link
Contributor Author

@mrtnzlml mrtnzlml Sep 25, 2016

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)

Copy link

@TomasVotruba TomasVotruba Sep 25, 2016

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?

Copy link
Contributor Author

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);

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.

Copy link
Contributor Author

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.

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?

Copy link
Contributor Author

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());

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() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: fix coding standards

@JanTvrdik
Copy link
Contributor

Setter (as suggested in #141) is IMHO better (template class name has a character of configuration not of dependency)

@mrtnzlml
Copy link
Contributor Author

mrtnzlml commented Sep 25, 2016

@JanTvrdik I love this idea (despite the naming - just idea) even more. But I agree with you.

@JanTvrdik
Copy link
Contributor

You mean the „TemplateClassFactory“ which would create just instance of Template without configuring it any further? That seems like a terrible idea to me.

Good solutions are either setter or splitting the instance creation and configuration to multiple methods to allow overriding part of the logic.

@TomasVotruba
Copy link

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.

@mrtnzlml mrtnzlml changed the title TemplateFactory: add support for custom Template implementation WIP: TemplateFactory: add support for custom Template implementation Sep 26, 2016
@mrtnzlml
Copy link
Contributor Author

mrtnzlml commented Sep 26, 2016

TODO for me: Change implementation back to the setter one. Make it configurable using DI extension if possible (because original usage is not nice). (done)

@mrtnzlml
Copy link
Contributor Author

Ok guys, what about this implementation?

public $defaults = [
'xhtml' => FALSE,
'macros' => [],
'templateClass' => \Nette\Bridges\ApplicationLatte\Template::class
Copy link
Contributor Author

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) {
Copy link
Contributor

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)

Copy link
Contributor

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

Copy link
Contributor Author

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)
Copy link
Contributor

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');
Copy link
Contributor

@JanTvrdik JanTvrdik Sep 26, 2016

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)

Copy link
Contributor Author

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?

Copy link
Contributor

@JanTvrdik JanTvrdik Sep 26, 2016

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
Copy link
Contributor

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 NULL and the setter call should only be emitted if the value is not NULL

@mrtnzlml
Copy link
Contributor Author

mrtnzlml commented Sep 26, 2016

Updated - I have no idea why is old conversation still here (what a mess in this discussion)... :-/
What changed:

  • it uses setter implementation
  • it's still configurable via Latte DI extension
  • it's still used validation in setTemplateClass because I strongly believe that we should check if passed class name is string (and not created instance for example)

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.");
Copy link
Contributor

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_a because is_a does 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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing space after if

@mrtnzlml
Copy link
Contributor Author

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.");
Copy link
Contributor

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 ... ."

@mrtnzlml mrtnzlml changed the title WIP: TemplateFactory: add support for custom Template implementation TemplateFactory: add support for custom Template implementation Sep 26, 2016

public function setTemplateClass($templateClass)
{
if (!class_exists($templateClass) || !is_a($templateClass, UI\ITemplate::class, TRUE)) {
Copy link
Member

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

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Member

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.
@mrtnzlml
Copy link
Contributor Author

mrtnzlml commented Nov 6, 2016

Rebased on top of master branch (because of failing tests).

@dg dg closed this in 2d565db Dec 20, 2016
@mrtnzlml
Copy link
Contributor Author

Thank you @dg! :)

@mrtnzlml mrtnzlml deleted the customTemplate2 branch December 20, 2016 10:42
dg pushed a commit that referenced this pull request Dec 20, 2016
…oses #159][Closes #141]

With this change it's possible to simply use custom Template implementation. Usage example in config.neon:

latte:
    templateClass: App\CustomTemplateImplementation

Custom template  MUST extend Nette\Bridges\ApplicationLatte\Template.
dg pushed a commit that referenced this pull request Dec 20, 2016
…oses #159][Closes #141]

With this change it's possible to simply use custom Template implementation. Usage example in config.neon:

latte:
    templateClass: App\CustomTemplateImplementation

Custom template  MUST extend Nette\Bridges\ApplicationLatte\Template.

public function setTemplateClass($templateClass)
{
if (!class_exists($templateClass) || !is_a($templateClass, Template::class, TRUE)) {
Copy link
Member

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.');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

without it


return $template;
}

Copy link
Member

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']]);
Copy link
Member

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']]);?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants