New TemplateRendererInterface Proposal

75 views
Skip to first unread message

Alexander Schranz

unread,
May 31, 2022, 7:15:24 PMMay 31
to PHP Framework Interoperability Group
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 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 to latte templates 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

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

Alexander Schranz

unread,
May 31, 2022, 8:11:38 PMMay 31
to PHP Framework Interoperability Group
Quick Update,

was missing  another important library the yii view in my analysis:

Yii View

Repository: https://github.com/yiisoft/view
Current Version: 5.0.0


Supported PHP Version: ^7.4|^8.0
Template Type Hint: string

Context Type Hint: array
Return Type Hint: string
Supports Stream: false

// render to variable
$content = $view->render('template', ['optional' => 'value']);

So not match change. If somebody see any errors in my analysis please let me know.

Best Regards,
Alex

Woody Gilk

unread,
Jun 1, 2022, 9:28:21 AMJun 1
to PHP Framework Interoperability Group
Alexander,

This is really great work and I appreciate the effort you put into it.

I definitely support this continuation of this proposal.

Regards,


--
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+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/3d47a925-9f63-4477-8ca4-654d4cfec180n%40googlegroups.com.

Larry Garfield

unread,
Jun 1, 2022, 12:00:22 PMJun 1
to PHP-FIG
On Tue, May 31, 2022, at 6:12 PM, Alexander Schranz wrote:


> *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 was one of the people arguing in the past that a templating interface needs to be more robust than a dict->string mapper. It does seem that there is a de facto consensus on that model anyway, so even if it's a poor model there is potential value in standardizing that much.

My concern is that while there's obviously a straightforward way to make common render() methods *type* compatible, that doesn't mean they're *semantically* compatible. The template name string is, AFAIK, wildly variable between different systems right now, and the value part of the replacement array is also a huge ball of undefined. For a standard to actually be viable, those would need to be standardized at least enough that a library could use it and expect it to then "just work" whether it's plugged into Twig, Plates, Sulu, TYPO3 Fluid, or whatever. That also gets into questions of escaping, and what is escaped where.

Those are extremely non-trivial questions. I don't know how resolvable those are in practice, but they would all happen outside the type system, or else involve not passing primitives (eg, a template object instead of just a string name, and then we're right back where we started). If the major players in the template world are on board for a working group to sort out *that* part of the problem space, I'm happy to support them doing so. But it would need buy-in from the major players there, because otherwise it wouldn't be useful.

--Larry Garfield

Alexander Schranz

unread,
Jun 1, 2022, 3:12:56 PMJun 1
to PHP Framework Interoperability Group
Thank you Larry for your valuable Feedback and concerns about my proposal.

I understand your concern about primitive Types, but I think they are a little bit too much hated today.
Also I think we are creating bigger problems with less intercompatibility if we go with object for both the template or the context.
I would focus what the best and most developer friendly way is to get all under the hood.

In mostly all Templates engines I analysed the template itself is represented as a string.
Sure some template engines under the hood work with Template objects / Template Wrapper objects. But we are not on that Level we are on the Level above the caller.
So why is it that way that mostly all did end in that scenario of using a string.
I personally think it is because it is the most developer friendliest way.
Because of going with a object I would need to import a class, inject a factory or something like that unnecessary complex for that usecase.

A string is simple and very developer friendly in this way. The developer know the template they created and put the name into the method call and its done.
I also think that is also the case why Laravel got a lot of traction, why other including me trying to overcomplicate things with Object and Types.
You will find array and strings a lot in Laravel interfaces and methods, when you scroll through there Documentation.

Also asking where do templates coming from. They are mostly coming from 3 places.
Place 1: Configuration, Place 2: Database, Place 3 Hardcoded in all cases they are already a string.
There are no usecases to convert or manipulate them, I currently think of which would make them
usable as convert the string to object.

So now from the template name to the context. I mostly see the same problems for the context.
Why I see there more the usecase as mezzio mention there TemplateContainer.
I think it does overcomplicate the things and the array is still the better Developer Experience here.
The object will end us just in another rabbit hole.
And in the "big factory question", who is creating the object and providing it.
If we have directly a factory method on the TemplateRenderInterface::createView as example
which will mostly get string, array it is the question what value it does it really has then.
If we go the way of saying we do not care about who is creating that object. It requires
me as library author to create such an object or require a library which create it for me.
Which I think is the problem I would like to avoid.

With string and array I would not need to take care a lot and need to ask me that complicate question.
The Developer using my Library will give me the template they want to render and I will call the injected Render
with the data I will provide. I think really the best developer experience is keeping it simple with string, array.

Still I'm curious how the object you had in mind should look like.
Can you show an example what you had in mind?
Would help to understand maybe better what advantages it really would had.

Thx,
Alex

Carle Dev

unread,
Jun 1, 2022, 4:03:44 PMJun 1
to php...@googlegroups.com
Alexander,

This is really great work and I appreciate the effort you put into it.

I support this continuation of this proposal.

Regards,

--
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+u...@googlegroups.com.

Larry Garfield

unread,
Jun 1, 2022, 7:05:53 PMJun 1
to PHP-FIG
Please bottom-post if replying to someone who already bottom-posted.

Regarding the string for the template name, what mbabker replied on the GitHub thread is basically my point.

If I, as a random library dev that wants something rendered, want to call this method, then right now I would have to one of the following:

Twig:
$this->template->render('controller/welcome.html.twig', [...]);

Laravel Blade:
$this->template->render('welcome', [...]);

Smarty:
$this->template->render('welcome.tpl', [...]);

Latte:
$this->render('path/to/welcome.latte', [...]);

Just standardizing that the first argument is "a template name" isn't enough. Is that string a file name? File path? Relative to where? Lookup ID from some index maintained elsewhere? How do I then tell the framework my library is getting plugged into what template ID needs to be defined somewhere?

Those are all things that COULD be answered successfully, but MUST be answered consistently for this proposal to work. That's what a working group would be responsible for hammering out.

Also, how do I, as a random library author, provide a default template if that template is naturally going to be in one particular template system or another? Or am I not allowed to? If I don't, does that mean I cannot actually test my library without pulling in some template engine?

Again, there probably are solutions to that question, but they will only be found by bringing the right people to the table to make compromises and hash out those details.

In short, *do not focus on the spec right now*. The current proposal is very incomplete as it doesn't address these and other issues, but that's OK, it should be at this point. Focus on building the team who will think through finding these and other issues none of us have thought of yet, and figuring out a collective solution.

--Larry Garfield
Reply all
Reply to author
Forward
0 new messages