Latte redesign: that could easily be used standalone

Notice: This thread is very old.
David Grudl
Nette Core | 8129
+
0
-

The aim of this proposal is to change the API of Latte so that it can be used this way:

$latte = new Latte\Engine; // prepares Latte with default macros and helpers
$latte->addMacro(...); // install custom macros
$latte->addHelper(...); // or addModifier
$latte->setContentType(...);
$html = $latte->render($latteFile, $parameters);

for better performance:

$latte = new Latte\Engine; // we can find better name
$latte->onCompile[] = function($latte){
	$latte->addMacro(...);
};
$latte->addHelper(...); // or addModifier
$latte->setContentType(...);
$html = $latte->render($latteFile, $parameters);

Thats all.

Optionally you can change some internal objects, for testing or debugging purposes, via $latte->setParser() or setCompiler().

For macros {include} and {layout} you have to pass any “template-finding-strategy”, so there will be $latte->setTemplateLoader(ITemplateLoader ...). Or even better $latte->getMacro('include')->setLoader(...). (This means, that Presenter::formatTemplateFiles() and formatLayoutTemplateFiles() will be moved to separated classes implementing Latte\ITemplateLoader interface).

And there will be $latte->setTempDirectory(), because only caching to filesystem will be allowed (nothing else works with opcode cache), so this means that dependency on Nette\Caching will be removed.

And finally, it will mean merging Nette\Templating into Latte (without Template::extractPhp, it will live outside of Latte).

Filip Procházka
Moderator | 4668
+
0
-

The new api looks awesome.

Tomáš Votruba
Moderator | 1114
+
0
-

Love it. What could be first step?

David Grudl
Nette Core | 8129
+
0
-

The first step? Wait if no one will have strong arguments against it ;)

voda
Member | 561
+
0
-

Will be splitting compilation and rendering part of the redesign?

Honza Marek
Member | 1664
+
0
-

strongly disagree with Latte\Engine constructor having no arguments. This class has some dependencies like parser and compiler and these should be passed via constructor. You can always create factory for easy creating of Latte\Engine instances instead of polluting constructors with anti-DI mess ;)

Milo
Nette Core | 1283
+
0
-

@DavidGrudl Latte plays role of filter now. If it'll be combined with templates, what about other filters registration strategy?

Last edited by Milo (2014-02-13 10:40)

Jan Tvrdík
Nette guru | 2595
+
0
-

I strongly dislike the fact, that Latte\Engine should already have default macros and helpers (btw: Configurator has the very same design issue). Better solution would be to have Latte\Engine without any macros (except for low-level macros syntax and contentType) and helpers and add Latte\EngineFactory (or EngineConfigurator?) which would create Latte\Engine with default configuration.

$latte = (new Latte\EngineFactory)->create(); // prepares Latte with default macros and helpers
...

Edit: @Honza Marek: +1

Last edited by Jan Tvrdík (2014-02-13 12:35)

mkoubik
Member | 728
+
0
-

I agree that FileTemplate (and PhpFileStorage) should be part of latte, but there should also be some thin Nette\Templating layer for integration of other templating engines, the concept of filters is good IMHO. I have already proposed splitting Nette\Latte\Engine into Nette\Latte\CompilerFactory and Nette\Templating\LatteFilter (not sure about namespaces now, but the main idea should be clear). It makes LatteFilter smiple & sexy.

Also +1 for compiler and parser as constructor dependency of latte filter.

Edit: Maybe Nette\Templating should have more general API, not all templating engines have to compile templates to php.

Edit 2: Storing parameters in $template is also very strict requirement. Maybe something like

interface ITemplatingEngine
{
	public function render(ITemplateSpecification $template, array $parameters);
}

Edit 3: Implementing both ITemplatingEngine and LatteFilter would require some additional bridge between Nette\Latte and Nette\Templating, but it would allow simplify integration of almost any templating engine to Nette\Templating and integration of latte to almost any framework.

Last edited by mkoubik (2014-02-13 12:55)

David Grudl
Nette Core | 8129
+
0
-

2Honzas: Please note that Latte\Engine is only factory. Configurable factory for default Compiler/Parser/Macros.

If own parser or compiler will be passed via constructor or method is not important. Own instances will be used very rarely, so constructor should remain clean. (Nobody will register Parser/Compiler as service.)

Milo wrote:

@DavidGrudl Latte plays role of filter now. If it'll be combined with templates, what about other filters registration strategy?

I think that concept of filters is obsolete since Latte exists. Now it is only used for combination of PHP + Latte and this may be somehow preserved.

Filip Procházka
Moderator | 4668
+
0
-

Theoretically, we could have

class Engine
{
	function __construct(Parser $parser = NULL, Compiler $compiler = NULL)
	{
		$this->parser = $parser ?: new Parser;
		$this->compiler = $compiler ?: new Compiler;
	}

Engine factory sounds nice, it could add the default macros, or the template factory could (should) add them.

$engine = (new Nette\Application\UI\LatteEngineFactory)->create();

If the default macros were added in TemplateFactory and not in Latte\Engine, the LatteEngineFactory doesn't make sense to me anymore.

David Grudl
Nette Core | 8129
+
0
-

Default macros will be added in factory, in Latte\Engine.

Milo
Nette Core | 1283
+
0
-

David Grudl wrote:
Own instances will be used very rarely.

I agree with it. But if so, typehint Latte\Engine will be on many places and Engine will have to be inherited only because own Compiler/Parser need.

Filip's constructor propose Parser $parser = NULL, Compiler $compiler = NULL seems as good compromise to me. Or pronounce methods setParser() and setCompiler() as normal (not debugging only purpose) methods.

Btw. I vote for lazy $latte->onCompile[] variation.

Milo
Nette Core | 1283
+
0
-

David Grudl wrote:

Milo wrote:

@DavidGrudl Latte plays role of filter now. If it'll be combined with templates, what about other filters registration strategy?

I think that concept of filters is obsolete since Latte exists. Now it is only used for combination of PHP + Latte and this may be somehow preserved.

Agree. Personally I used that in one application only for templates content search indexing. Maybe events onBeforeCompiele[] and onAfterCompile[] would solve that.

Jan Tvrdík
Nette guru | 2595
+
0
-

Please note that Latte\Engine is only factory

That is not true. It is too smart to be “only factory”. It is a “smart factory” (yeah, difference!) like Nette\Configurator which contains its own general-purpose logic AND sets default extensions (that is the flaw). Latte\Engine has the same issue. Both general-purpose and specific-purpose logic. I'll need to see the actual implementation of Latte\Engine to determine whether the amount of general purpose logic is a reason to create an extra Latte\EngineFactory but now I think it is.

David Grudl
Nette Core | 8129
+
0
-

@honzaT: The (only) important is API. $latte = new Latte\Engine; is great, is the best. Nobody wants to write $latte = (new Nette\Application\UI\LatteEngineFactory)->create();. How will be Latte\Engine implemented is the second thing and is not part of this RFC.

@milo: setParser() and setCompiler() will be normal public methods, of course.

Jan Tvrdík
Nette guru | 2595
+
0
-

@David Grudl: If API is the only thing that matters to you, then what about creating sth. like Latte\BareEngine with general purpose logic and Latte\Engine extends BareEngine with default macros and helpers. We both know that it is not ideal, but the end-user API will remain the same and design would be cleaner.

mkoubik
Member | 728
+
0
-

In case I don't want to use default macros latte factory will create one needless instance of compiler.

$mycompiler = ...; // <- one instance created
$latte = new Latte\Engine; // <- another instance created
$latte->setCompiler($myCompiler);
Jan Tvrdík
Nette guru | 2595
+
0
-

@mkoubik: constructor in Latte\Engine should definitely not create instance of Compiler due to performance, because Compiler and Parser are required only when the renderer template is not yet compiled. Current solution in Nette is not creating Latte\Engine instance at all when templates are already compiled.

hrach
Member | 1834
+
0
-

@dg: How will be Latte\Engine implemented is the second thing and is not part of this RFC.

Must be, commenting only API doesn't make sense. The implementation gives sense to API, not the otherwise.

Just now there is a problem I can't change auto registered macros and it leads to this: https://github.com/…te/pull/1122

David Grudl
Nette Core | 8129
+
0
-

Ufff, please be more pragmatic :-) Who of you did ever changed parser of compiler? I bet that nobody. So do not propose ideas like parser and compiler in constructor, it is nonsense. No, it is dependency on dependency container :-) And we are talking about standalone Latte.

Filip Procházka
Moderator | 4668
+
0
-

Changing of parser and compiler makes zero sense, installation of macros and helpers is vital to the api of Latte\Engine.

mkoubik
Member | 728
+
0
-

David Grudl wrote:

Ufff, please be more pragmatic :-) Who of you did ever changed parser of compiler?

Ok, I did, but this is not important.

If default Latte\Engine would not install any macros, I would agree. But what if my framework doesn't even support generation of links, what if I don't wanna allow users to insert php code to templates, etc.
Good compromise would be to have preinstalled some core macros, that do depend only on built-in PHP functions and classes in runtime (not true for current CoreMacros and definitely not true for UIMacros). Good compromise would be API

$latte = new Latte\Engine; // some essential macros predefined
$latte->addMacros(new UIMacros);

instead of

public function __construct()
{
	$this->parser = new Parser;
	$this->compiler = new Compiler;
	Macros\CoreMacros::install($this->compiler);
	Macros\UIMacros::install($this->compiler);
	\\ ...
}

and

MyMacros::install($latte->getCompiler());

If the API would be like in the first case and installing macros would be responsibility of latte engine itself, then compiler as a dependency of latte engine would be completely uninteristing for the end user.

BTW: Constructor IS a static factory method by definition. The only distinction is that it is the default one.

Last edited by mkoubik (2014-02-14 01:00)

Filip Procházka
Moderator | 4668
+
0
-

Note that the default Latte\Engine will in future contain ONLY core macros. Links, forms, controls and other nette-specific macros will be provided in some sort of template factory or inheritor of default Latte\Engine

David Grudl
Nette Core | 8129
+
0
-

If default Latte\Engine would not install any macros, I would agree

Yes, but in this case $res = $latte->render($file, $parameters) is exactly the same as $res = file_get_contents($file), just a little bit slower :-))

Honza Marek
Member | 1664
+
0
-

David Grudl wrote:

@honzaT: The (only) important is API. $latte = new Latte\Engine; is great, is the best. Nobody wants to write $latte = (new Nette\Application\UI\LatteEngineFactory)->create();. How will be Latte\Engine implemented is the second thing and is not part of this RFC.

@milo: setParser() and setCompiler() will be normal public methods, of course.

  1. Latte\Engine is a weird name for factory.
  2. Ability to write $latte = new Latte\Engine; is not important at all. Almost no one will use it directly. There is no problem with
$latte = Nette\Application\UI\LatteEngineFactory::create();
// or
$latte = $latteEngineFactory->create();
David Grudl
Nette Core | 8129
+
0
-

ad point two: topic is Latte redesign: that could easily be used standalone


($pdo = new PDO($dsn, $user, $pass)? Too simple, Anton Pavlovich. What about $pdo = (new PdoFactory)->create($dsn, $user, $pass) ;-) )

pave.kucera
Member | 122
+
0
-

If Latte\Engine is supposed to be only a factory, I'd appreciate its name and behaviour to reflect it. I don't consider messing up with naming conventions a good idea.

From user point of view, I don't see any difference between following two:

$latte = new Latte\Engine();
// and
$latte = Latte\EngineFactory::create(); // not all static methods are evil

In my opinion writing 14 more characters to instantine Latte\Engine is not such a big drawback if it keeps the design clean™.

mkoubik
Member | 728
+
0
-

What's your opinion on this?

class Engine
{
	private $parser;
	private $compiler;

	public function __construct(Parser $parser = NULL, Compiler $compiler = NULL)
	{
		$this->parser = $parser ?: new Parser;
		$this->compiler = $compiler ?: new Compiler;

	}

	public static function create()
	{
		$engine = new self();
		$engine->addMacro(new Macros\CoreMacros());
		$engine->addMacro(new Macros\CacheMacro(), 'cache');
		$engine->addMacro(new Macros\UIMacros());
		$engine->addMacro(new Macros\FormMacros());
		return $engine;
	}
}

Or at least move the configuration from constructor to protected method so I can change it in child.

Last edited by mkoubik (2014-02-28 10:02)

Jan Tvrdík
Nette guru | 2595
+
0
-

@mkoubik: 1) Parser and Compiler must be initialized only when the template is not already compiled.

  1. Latte\Engine must not register nette-specific macros.
enumag
Member | 2118
+
0
-

I think UIMacros will have to be split too. Macros like {block} and {include} are not nette-specific. However {link} and {snippet} are. Which may be a little problem because snippets are actually blocks as well and they are using the same code.

David Grudl
Nette Core | 8129
+
0
-

Done in v2.2