Hey Alex,

Thinking more about this...

I have to wonder if this really brings any actual interoperability between 
template engines?

Here's the thing.

Let's say you have some sort of welcome email service, and it needs to 
render a template:

$mailer = new WelcomeMailService(new TwigRenderer( ..... ));

Your mailer instance can now call the abstraction, e.g. 
$view->render("something", [ ..... ])

But the PSR draft kind of explains why that doesn't work - about the 
template argument, it says:

"It MAY be a file path to the template file, but it can also be a virtual 
name or path supported only
by a specific template renderer. The template is not limited by specific 
characters by definition
but a template renderer MAY support only specific one."

In other words, the argument itself is implementation-specific - it sounds 
almost like the definition
of a "leaky abstraction".

(Which, just to recap, the term "leaky abstraction" refers to a situation 
in software development
where the abstraction layer, which is designed to hide the complexity of a 
lower-level system,
fails to completely insulate the higher-level software from the details of 
the underlying system.
In other words, the abstraction "leaks" details that it was supposed to 
hide.)

Net result, there is no real interoperability here - it needs to be a 
string, but those strings could
be wildly different types that just happen to be represented as a string. 
An absolute or relative
path is in no way compatible with, say, a logical template name, whatever 
that might mean to
a specific template engine.

To return to my previous example and explain with a real world scenario, 
your welcome email
service would need to accept an engine-specific template name via it's 
constructor anyway:

$mailer = new WelcomeMailService("templates/welcome.twig", new 
TwigRenderer( ..... ));

The welcome service needs a template name that works for the renderer 
implementation - and
it needs these dependencies only for one reason, so it can put them back 
together at run-time.

If we back up and think high-level about what the WelcomeMailService needs 
from the renderer,
it just needs it to render a template - the WelcomeMailService has no use 
for the template name
whatsoever, apart from passing it to the renderer.

If a template renderer is going to work only with a specific type of 
template name, why even
burden the consumer with knowledge of a template name that's meaningless to 
it anyway?

You might as well reduce the abstraction to this:

interface Template
{
    public function render(array $data): string;
}

And now your WelcomeMailService can be ignorant of how the template was 
located:

$mailer = new WelcomeMailService(new TwigTemplate( "templates/welcome.twig" 
));

The WelcomeMailService still achieves everything it needs to: it's able to 
render the template
when it needs, passing the template data (which isn't engine specific) to 
the template and
get back the rendered content. It doesn't need to know anything about a 
template name,
which wouldn't do it any good anyway, unless it knew which renderer was 
being used, what
or the syntax of the template name is, etc. - things it isn't supposed to 
know about.

I see why it would "feel good" to put template engines behind a similar 
abstraction... but when
the abstraction leaks the only important implementation detail -- which 
template engine you're
using -- it's difficult to see what exactly this buys you.

I think perhaps you're trying to erase a difference that can't really be 
erased.

Unless perhaps you were to have a PSR-specific definition of "template 
name" - something like:

"the template name identifies the logical template to render - it consists 
of filename-compatible
characters separated by a forward slash, which the Renderer implementation 
may resolve to an
actual template, usually a path/filename specific to conventions used by 
the Renderer in question."

This wouldn't leak anything - the WelcomeMailService can use an 
engine-independent call, such as:

$view->render("WelcomeMailService/welcome", [ .... ]);

A TwigRenderer might map this to "WelcomeMailService/welcome.twig", while a 
PHP renderer
might map this to "WelcomeMailService/welcome.php", and so on.

If you were to switch engines, you'd end up with missing template errors, 
rather than engine A
attempting to render a template written in engine B syntax.

I'm not sure which approach is better.

But I don't think the current approach works as-is?

On Wednesday, June 1, 2022 at 1:15:24 AM UTC+2 Alexander Schranz wrote:

> Hi my name is Alex,
>
> I want to bring a new proposal to PHP-Fig, which could be interesting 
> specially for Frameworks and CMSs, as I'm one of the core developers of 
> Sulu CMS we have todo much with template renderers.
>
> *PSR Template Renderer Proposal* 
>
> A proposal for psr <https://www.php-fig.org/psr/> for rendering templates.
> *Goal* 
>
> It is common that a library, application or CMSs need to have a template 
> renderer / engine for rendering data for their websites or emails.
>
> More and more application are going here the data provider way. This 
> application are the one which would benifit from the 
> TemplateRendererInterface as they not only can provide them headless over 
> an API but also make it possible that somebody can render the data via a 
> Template Engine.
>
> As a library author I want to make it free that my service can be used 
> with any template engine the developer want to use. Typical usecases are 
> PHP rendered CMSs like Sulu, Typo3, Drupal, Contao which maybe could 
> benifit from this. But also all other data provider based libraries which 
> ship configureable controller or have template to render like email tools / 
> libraries.
>
> Also for projects when somebody wants maybe switch in future from twig 
> <https://twig.symfony.com/> to latte templates <https://latte.nette.org/> 
> as it consider better safety for xss a common interface can benifit here 
> and avoid refractorings.
> *Defining the scope* 
>
> The scope of the TemplateRenderer is only on rendering a given template 
> with a given context. The template render interface will not take care of 
> registering template paths or how to configure the template engine to find 
> the templates. Similar how PSR-18 HttpClient does not care how the client 
> is created or configured.
> *Analysis* 
>
> In this section I did analyse the following existing template engines and 
> added example how the render there templates.
>
>    - Twig <https://github.com/php-fig/fig-standards/pull/1280#twig> 
>    (Symfony, Sulu CMS, Drupal, Contao (experimental))
>    - Smarty <https://github.com/php-fig/fig-standards/pull/1280#smarty>
>    - Latte <https://github.com/php-fig/fig-standards/pull/1280#latte> 
>    (Nette)
>    - Laminas View 
>    <https://github.com/php-fig/fig-standards/pull/1280#laminas-view>
>    - Blade <https://github.com/php-fig/fig-standards/pull/1280#blade> 
>    (Laravel)
>    - Fluid <https://github.com/php-fig/fig-standards/pull/1280#fluid> 
>    (Typo3)
>    - Contao <https://github.com/php-fig/fig-standards/pull/1280#contao> 
>    (Contao CMS)
>    - Mezzio <https://github.com/php-fig/fig-standards/pull/1280#mezzio> 
>    (Laminas) (already abstract Twig and Plates)
>    - Plates <https://github.com/php-fig/fig-standards/pull/1280#plates> 
>    (PHP League)
>    - Mustache 
>    <https://github.com/php-fig/fig-standards/pull/1280#mustache>
>
> *Twig* 
>
> Repository: https://github.com/twigphp/Twig
> Current Version: v3.4.1
> Supported PHP Version: >=7.2.5
> Template Type Hint: string|TemplateWrapper
> Context Type Hint: array
> Return Type Hint: string or output to buffer
> Supports Stream: true
>
> Render a template:
> // render to variable
> $content = $twig->render('test.template.twig', ['optional' => 'key-value'
> ]);
> // render to output buffer
> $twig->display('template.html.twig', ['optional' => 'value']);
>
> *Smarty* 
>
> Repository: https://github.com/smarty-php/smarty
> Current Version: v3.4.1
> Supported PHP Version: ^7.1 || ^8.0
> Template Type Hint: string
> Context Type Hint: array
> Return Type Hint: none
> Supports Stream: true (only)
>
> Render a template:
> // render to output buffer $smarty->assign('optional', 'value'); $smart->
> display('template.tpl');
>
> *Latte* 
>
> Repository: https://github.com/nette/latte
> Current Version: v3.0.0
> Supported PHP Version: >=8.0 <8.2
> Template Type Hint: string
> Context Type Hint: object|mixed[]
> Return Type Hint: string or output to buffer
> Supports Stream: true
>
> Render a template:
> // render to variable
> $latte->renderToString('template.latte', ['optional' => 'value']);
> // render to output buffer
> $latte->render('template.latte', ['optional' => 'value']);
>
> *Laminas View* 
>
> Repository: https://github.com/laminas/laminas-view
> Current Version: ^2.20.0
> Supported PHP Version: ^7.4 || ~8.0.0 || ~8.1.0
> Template Type Hint: string
> Context Type Hint: ViewModel<null|array|Traversable|ArrayAccess>
> Return Type Hint: null|string
> Supports Stream: false
> // render to variable
> $viewModel = new ViewModel(['headline' => 'Example']);
> $viewModel->setTemplate('index');
> $content = $this->view($viewModel)->render();
>
> *Blade* 
>
> Repository: https://github.com/illuminate/view
> Current Version: v9.15.0
> Supported PHP Version: ^8.1
> Template Type Hint: string
> Context Type Hint: array
> Return Type Hint: string
> Supports Stream: false ?
>
> Render a template:
> // render to variable
> $content = view('welcome', ['name' => 'Samantha']);
> // same as: $content = $viewFactory->make($view, $data, $mergeData)->
> render();
>
> *Fluid* 
>
> Repository: https://github.com/illuminate/view
> Current Version: 2.7.1
> Supported PHP Version: >=5.5.0
> Template Type Hint: string
> Context Type Hint: array
> Return Type Hint: string
> Supports Stream: false ?
>
> Render a template:
> // render to variable
> $view = new StandaloneView();
> $view->setTemplatePathAndFilename('template.html');
> $view->assignMultiple(['optional' => 'key-value']);
> $content = $view->render();
>
> *Contao* 
>
> Repository: https://github.com/TYPO3/Fluid
> Current Version: 4.13.4
> Supported PHP Version: ^7.4 || ^8.0
> Template Type Hint: string
> Context Type Hint: object<string, mixed> via dynamic properties
> Return Type Hint: string
> Supports Stream: false ?
>
> Render a template:
> // render to variable
> $template = new FrontendTemplate('template');
> $template->optional = 'value';
> $content = $template->parse();
>
> *Mezzio* 
>
> Repository: https://github.com/mezzio/mezzio
> Current Version: 3.10.0 
> Supported PHP Version: ~7.4.0||~8.0.0||~8.1.0
> Template Type Hint: string
> Context Type Hint: array|object
> Return Type Hint: string
> Supports Stream: false
>
> Render a template:
> // render to variable
> $content = $templateRenderer->render('template', ['optional' => 'value']);
>
> *Plates* 
>
> Repository: https://github.com/thephpleague/plates
> Current Version: v3.4.0
> Supported PHP Version: ^7.0|^8.0
> Template Type Hint: string
> Context Type Hint: array
> Return Type Hint: string
> Supports Stream: false
>
> Render a template:
> // render to variable
> $content = $plates->render('template', ['optional' => 'value']);
>
> *Mustache* 
>
> Repository: https://github.com/bobthecow/mustache.php
> Current Version: v2.14.1
> Supported PHP Version: >=5.2.4
> Template Type Hint: string
> Context Type Hint: array
> Return Type Hint: string
> Supports Stream: false
>
> Render a template:
> // render to variable
> $content = $mustache->render('template', ['optional' => 'value']);
>
> *The proposal* 
>
> The interface for a TemplateRender I would recommend is the following 
> based on my analysis of exist template engines and what is the easiest way 
> to put them together and have maximum interoperability:
> /**
>  * Render the template with the given context data.
>  *
>  * @param string $template
>  * @param array<string, mixed> $context
>  *
>  * @return string
>  *
>  * @throw TemplateNotFoundExceptionInterface
>  */
> public function render(string $template, array $context = []): string;
>
> For maximum compatibility we even could consider to publish 2 version of 
> the template renderer v1 without typehints so exist template engine still 
> supporting old php version can already implement it and v2 with typehints.
> *Exist TemplateRenderer Discussion* 
>
> There was already an exist disussion about implementing a 
> TemplateRendererInterface here: 
> https://groups.google.com/g/php-fig/c/w1cugJ9DaFg/m/TPTnYY5LBgAJ.
>
> The discussion goes over several topics just to mention the main parts:
>
>    - Template should be objects
>    - Context should be objects
>    - TemplateRender should stream to output even asynchronity
>
> To target this specific points. I would focus in this PSR on exist 
> solution as we see most work for template with logical string based names 
> and do not require an object.
>
> I want mention here also developer experience as example in the past why 
> there was created PSR 16 (Simple Cache) where we did still have PSR 6.
>
> So the name of the proposal should maybe be "Simple TemplateRenderer" and 
> not try to reinventing the wheel.
>
> By analysing exist template engine, not everybody support to have an 
> object as a context so I would keep the interface to array for context 
> only. This way it is easy to make exist template engine compatible with PSR 
> interface.
>
> For streaming the output I must say I did not see one project since 7 
> years which did use for example the streaming functionality of twig and for 
> maximum compatibility I would also remove that requirement from the PSR as 
> the analysis give there are template engines which do not support that 
> functionality.
>
>
> The proposal I did write can be found here: 
> https://github.com/php-fig/fig-standards/pull/1280/files. I know I did 
> skip here some process by already writing something, still I hope we can 
> put most of the template engine creators / maintainers on one table to 
> dicuss if they are willing to add such an interface or for which we maybe 
> can provide a bridge, so system like CMSs, Library, Newsletter Tools, can 
> make use of it. I will also bring this topic on the table for our next CMS 
> Garden <https://www.cms-garden.org/en> call which we have every month 
> where several members of different CMS have a call together discussion 
> common topics. So maybe I can reach there a CMS which maybe did not yet 
> part of this mailing list / github discussion.
>
> Best Regards,
> Alex
>

-- 
You received this message because you are subscribed to the Google Groups "PHP 
Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to php-fig+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/php-fig/c06fd286-2877-43c9-ad61-64c455de6204n%40googlegroups.com.

Reply via email to