Latte redesign: that could easily be used standalone
- David Grudl
- Nette Core | 8228
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).
- David Grudl
- Nette Core | 8228
The first step? Wait if no one will have strong arguments against it ;)
- Honza Marek
- Member | 1664
I 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 ;)
- Jan Tvrdík
- Nette guru | 2595
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
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 | 8228
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
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.
- Milo
- Nette Core | 1283
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
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
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 | 8228
@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
@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.
- Jan Tvrdík
- Nette guru | 2595
@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 | 1838
@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 | 8228
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
Changing of parser and compiler makes zero sense, installation of macros and
helpers is vital to the api of Latte\Engine
.
- mkoubik
- Member | 728
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
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 | 8228
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
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 beLatte\Engine
implemented is the second thing and is not part of this RFC.@milo: setParser() and setCompiler() will be normal public methods, of course.
- Latte\Engine is a weird name for factory.
- 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 | 8228
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
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
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
@mkoubik: 1) Parser
and
Compiler
must be initialized only when the template is not already
compiled.
Latte\Engine
must not register nette-specific macros.
- enumag
- Member | 2118
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.