On Thu, Dec 21, 2023, at 11:27 AM, Rasmus Schultz wrote:
> 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.

All of the above is correct, and is the main reason this proposal has so far 
gone nowhere. :-)

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

There's a subtle difference here in approach, which is significant.  What most 
(all?) engines today do is:

$engine->render($file, $args);

The problem is, as you note, $file is engine-specific, so non-portable.

Your earlier view-model suggestion would instead use:

$engine->render($view_model);

Where the type of the $view_model gets translated to a template file however 
the engine wants, and the properties of the $view_model are the $args.  This 
solves the genericity problem, at the cost of being unconventional.

Your latest suggestion with TwigTemplate above becomes:

new Service($templateDefObject);

But... there is no engine.  Presumably you would also have to provide an 
$engine to Service:

new Service($engine, $templateDefObject);

So that Service could internally do:

$engine->render($templateDefObject, $args);

Which is effectively isomorphic to your second suggestion above:

$engine->render($template_def_string, $args);

Just using a genericized string for the definition vs a carrier object.  The 
genericized string is, effectively, what I asked for a year ago for this 
proposal to have a chance of going anywhere. :-)  It never happened, so it 
never went anywhere.

Personally, I prefer the full-on view model approach.  PHP types can 
encapsulate quite a bit these days, which would (as in PSR-14) offer decent 
fallback support via parent types and interfaces, as well as be more 
self-debugging, etc.  It would also be template agnostic.  Additional context 
could be provided by optional named arguments (like $format for "html", "rss", 
"text", etc.), and those could be engine-specific-extended without breaking 
anything if we define a few base ones.

There's probably somewhere that would break, but the biggest blocker is, as 
noted, getting existing engines on board with this.  If we can do that, we have 
options.  If not, there's nothing to do.

--Larry Garfield

-- 
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/4344bbcf-6831-4b00-8b1f-320d869343d9%40app.fastmail.com.

Reply via email to