[Proposal] TemplateRendererInterface

366 views
Skip to first unread message

Thomas Gnandt

unread,
Sep 23, 2017, 1:45:53 PM9/23/17
to PHP Framework Interoperability Group
With the upcoming http-middleware PSR which defines the RequestHandlerInterface it becomes possible to write framework independent modules that include ready-to-use actions (instances of RequestHandlerInterface). However this is not possible, until a common interface to render a template exists.
Any action that should work in multiple applications has to render the body of the response using templates, that are not part of the module itself. While example templates may be included in the module, these are generally not usable by the application that uses the module.
It therefore doesn't make sense to include a specific template engine as a requirement of the module. Since templates are written using engine specific syntax it is not feasible to use multiple template engines.
Consequently an application could only use modules that support the engine the application wants to use.

In order to make the used template engine interchangeable a simple interface could be defined:

interface TemplateRendererInterface
{
    public function render(ResponseInterface $response, string $templatePath, array $data = null): ResponseInterface;
}

This would render the defined template with any data provided and write it to the body of the response.
Alternatively the following interface could be defined:

interface TemplateRendererInterface
{
    public function render( string $templatePath, array $data = null): string;
}

This provides greater flexibilty while the first interface provides easier usage in the specified use case (for request handlers).

Hari K T

unread,
Sep 23, 2017, 1:53:53 PM9/23/17
to php...@googlegroups.com
Yes, we have something like this for quite sometime.

https://github.com/zendframework/zend-expressive-template

If every framework authors agrees it will be an easy move.

Regarding your suggestion I am for 2.

You can do what you need with the rendered string.

Eg : An email template being parsed etc.

Hari K T

You can ring me : +91 9388 75 8821

Skype  : kthari85
Twitter : harikt

--
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/16475a0c-f46b-4478-ac99-5e9003a97d19%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Xedin Unknown

unread,
Sep 24, 2017, 4:32:17 AM9/24/17
to PHP Framework Interoperability Group
Hi all,

This is a good idea, and we are already working on this for the project that is being built. Please take a look at dhii/output-renderer-interface. The readme should explain most of the things. I will try to describe how things will work below. Please note that this is a work in progress; however, many hours have been dedicated to making this standard as useful as possible. I hope that it will be a good inspiration for a PSR.

To put things simply, you have a `RendererInterface`, which is anything that can produce output. This requires it to already have access to all data that is necessary for rendering. A `BlockInterface` is a renderer that can be cast to string, which is possible if it has sufficient data. Interoperability is ensured via `StringableInterface`. A `ContextRendererInterface` is something that can use a context, i.e. additional data, for rendering. This is great for renderers, the instances of which can be re-used to render the same "template" multiple times with different data. The "aware" interfaces are for greater interop between consumers of the main interfaces. Exception interfaces provide access to aspects of the rendering, which allows obtaining information about the rendering process without being aware of the internals of the implementation, and without having prior reference to the instance, which is very helpful and is a rule that we use in all standards.

dhii/output-renderer-abstract is an abstract implementation, and a work in progress as well. Blocks are free to produce output in any way they wish, so this package has just the most abstract functionality. Right now, I am working on an `AbstractTemplateBlock`, which uses a template to get rendered. While designing this class, I realised (and this is a very important point) that very good SoC can be achieved by encapsulating templates in a class that represents a template. Without this, it doesn't seem practical to standardize block logic to work with different templates. With templates as a class, it's possible to render a template that works in any way inside a block (or elsewhere). A template IMHO is just a way of saying "a form for output that can be filled with values". In this case, a value is a rendering context, and there is already an interface that does this - the `ContextRendererInterface`. This is why a rename of this interface to `TemplateInterface` is pending - because this is exactly what it is, and it's more concise and expressive. I will be pushing the rename, as well as the `AbstractTemplateBlock`, in a couple of hours, so that you can see the usage.

If a workgroup is being assembled to work on this standard, I would very much like to be part of it. I have spent a lot of time standardizing output mechanisms, and other things, very much inspired by the spirit and letter of FIG. I will also gladly share my plans for the future standards and implementations, as well as present other standards which I have been working on. Many of them are being used in production by me and my team, and are working very well so far.

Xedin Unknown

unread,
Sep 24, 2017, 4:53:57 AM9/24/17
to PHP Framework Interoperability Group
Eventually, I plan to be able to write something like this:

// my-template.php
echo
"Hello, {$c->get('name')}!";

// Elsewhere
$path
= 'my-template.php';
$template
= new PhpTemplate($path); // TemplateInterface, currently ContextRendererInterface
$context
= new DataObject(['name' => 'Xedin']); // This is a ContainerInterface, see dhii/data-container-interface:dev-develop
$block
= new GreetingBlock($template, $context); // The context can of course be determined in a different way

$mainContext
= new DataObject('greeting' => $block);
$mainBlock
= new HtmlBlock($mainContext); // Some super block for an outer template, implementation detail really


// Outer template
<div class="greeting">
<?php echo $c->get('greeting') ?>
</div>

As shown here, this approach allows standardization of template mechanics, including what happens inside the template, and very importantly compilation. All data inside any similar template, be it a purely HTML template, or a Mustache one, is available via the context (in PHP, a `ContainerInterface`. Something like `{greeting}` (Mustache style) can very easily be interpreted as `$c->get('greeting')` in the engine, etc. This looks to me like excellent SoC and interoperability.

Xedin Unknown

unread,
Sep 24, 2017, 9:16:06 AM9/24/17
to PHP Framework Interoperability Group
The changes that were pending are now merged, and can be found on the `develop` branch of the repos I mentioned. I hope this helps.

The next step for be is to create package with base abstract implementations, which still lack the key logic, but save time for the implementors by including concrete logic for common things, like throwing exceptions, normalization, etc. It would be great to get an opinion from the honorable participants of this group :)


On Sunday, September 24, 2017 at 10:32:17 AM UTC+2, Xedin Unknown wrote:

David Négrier

unread,
Sep 24, 2017, 3:09:37 PM9/24/17
to PHP Framework Interoperability Group
Hey Thomas,

Absolutely +1 with you regarding the need of "renderer" PSR.

@Xedin: I just saw your work and I very much like the path you are following with the TemplateInterface (https://github.com/Dhii/output-renderer-interface/blob/develop/src/TemplateInterface.php)

@Thomas, @Hari, in the interfaces you propose, you introduce the notion of a "$templatePath" (or template name in Zend). That is, your objects are responsible for 2 things:
1 => fetching the template
2 => rendering the template

These are 2 separate concerns and when it comes to a PSR, I believe that we should only focus on the rendering part.

Let me explain.

Let's assume I have some code that renders a page using Twig:

$this->renderer->render('path/to/index.twig', $data);

If I want to switch my renderer implementation to a Plate template, I cannot simply switch the renderer, I also need to change the path to the template:

$this->renderer->render('path/to/index.plate', $data);

OR, I need to go in great details to invent a convention that automatically adds the file extension to the template....

Like this:

$this->renderer->render('path/to/index', $data);


I believe that instead of representing a "renderer", we should focus on objects that represents the template (like what Xedin Unknown is doing).

This way, you can switch from one template to another, using simple dependency injection!

The interface should therefore be:

interface TemplateInterface {
    public functiion render(array $context): string
}

Now, I have 2 additional comments to make.

By type-hinting to "array", we cannot use a context that would implement "ArrayAccess". This can be useful in order to lazily evaluate the context. Si I would switch this to;

interface TemplateInterface {
    /**
     * @param array|ArrayAccess $context
     */
    public functiion render($context): string
}

Finally, regarding the return type, Hari is completely right to say that we don't want a ResponseInterface (because this is not necessarily related to an HTTP response). We could very well be rendereding a static HTML page stored on disk, or rendering an email.
However, we could improve over a simple "string" by using PSR-7 StreamInterface. That would allow for big responses that do not fit in memory.

So my ideal interface would be:

interface TemplateInterface {
    /**
     * @param array|ArrayAccess $context
     */
    public functiion render($context): StreamInterface
}

++
David.

Xedin Unknown

unread,
Sep 24, 2017, 5:33:00 PM9/24/17
to PHP Framework Interoperability Group
Hi David,

I'm happy to hear that you like the approach, and honoured by it.

I have standardized data retrieval by key in all my work to use `ContainerInterface`, because IMHO this is what it is for, and it allows any other implementation that has some additional features. For example, it is possible to still fall back to `ArrayAccess` if necessary, although I would say that using `ContainerInterface#get()` is a better approach. Sure, maybe it's a small burden on the implementation; however, with a base implementation like dhii/data-container-base, and with a `DataObject` concrete type (coming soon) it is super easy to create standards-compliant contexts for use here and in all other similar places. Please bear in mind that this implementation is based on a an extension (new release coming soon) of the PSR-11, dhii/data-container-interface, which fixes two important problems: the lack of an interface that can allow to only determine the existence of a value in a container (e.g. read-only), and the inability to determine details of a failed operation from an exception.

Same thing can be said about the return type: by allowing a `StringableInterface` (essentially a standardization of `__toString()`, see dhii/stringable-interface) as well as `string` we would allow a generic algorithm that just works in all places, and more: `BlockInterface` extends `StringableInterface`, so it's entirely possible to render a template to a block. If a particular implementation needs to produce a lot of output and therefore needs streaming, it is entirely possible to create an object that both encapsulates a stream, and can be cast to string - in fact such streams are common. I have noticed however, and to my deepest regret, that `StreamInterface` is part of PSR-7, which is a huge, bloated standard that additionally completely disregards SOLID ISP, and therefore is extremely hard to use. I am already working to fix this problem in dhii/io-resource-interface, which is much more... concise. Of course, it's super easy, and often advisable, to create an implementation that is both a `ReadableResourceInterface` and a `StringableInterface`. Therefore, I believe this problem is also solved by my standard.

Would be happy to hear any feedback, or answer any questions.

Marco Perone

unread,
Sep 24, 2017, 5:39:15 PM9/24/17
to PHP Framework Interoperability Group
Hi all,

it would be really nice to have a standard template renderer interface.

Two quick ideas aroud what has already been proposed:

- having the `render` method receive an array as input looks too vague to me. I would expect it to receive a template and some data, so something like

public function render (TemplateInterface $template, array $data)

- IMHO it would be nicer to have ad hoc types/interfaces to represent all the instances involved, instead of using native data types. For example, `TemplateInterface` mentioned above could be a wrapper around a string, and different implementations could have different ways for providing that string (reading it from a file, retrieving it from standard input, from the database, ...). Similarly, for the return type, we could have a `RenderedTemplateInterface` (naming to be improved), which wraps `string` or `StreamInterface`. This would make the whole PSR much more type safe and allow freedom of implementation to all the different libraries.


On Sunday, September 24, 2017 at 9:09:37 PM UTC+2, David Négrier wrote:

Xedin Unknown

unread,
Sep 24, 2017, 5:40:31 PM9/24/17
to PHP Framework Interoperability Group
Oh, forgot to mention. `ReadableResourceInterface`, and this whole new standard, was originally created because I thought that it should be possible to stream templates for reading. Thanks for pointing out that it is a good idea to stream the output, too!

$file = new Stream('my-mustache-template');
$template
= new MustacheTemplate($file);

$context
= new DataObject(['name' => 'Xedin']);

$outputStream
= $template->render($context);


On Sunday, September 24, 2017 at 9:09:37 PM UTC+2, David Négrier wrote:

Xedin Unknown

unread,
Sep 24, 2017, 5:46:36 PM9/24/17
to PHP Framework Interoperability Group
Marco,

Please take a look at my examples, and the other interfaces in dhii/output-renderer-interface. Above I wrote about how it is very useful to have a template know how to render itself, which would allow for unified logic, and different implementations for different engines. If you need something on top of the template, perhaps you need something like `AbstractTemplateBlock`?

Rasmus Schultz

unread,
Sep 25, 2017, 1:45:34 AM9/25/17
to PHP Framework Interoperability Group
Granted, the majority of template engines use a template file-name and a set of name/value pairs.

This template engine uses neither:

https://github.com/mindplay-dk/kisstpl

I'm sure you won't care because it's not popular or well-known ;-)

We use this at work because:

1. code-sniffer inspections and plain PHP templates are a requirement - view data dictionaries get in the way of that, and

2. dynamic template-selection is a requirement (enabling template overrides across modules etc.) and hard-coding template paths gets in the way of that.

I know this is not the mainstream approach to templates ;-)

Nicolas Grekas

unread,
Sep 25, 2017, 2:15:27 AM9/25/17
to php...@googlegroups.com
> Granted, the majority of template engines use a template file-name and a set of name/value pairs

Twig also doesn't reference templates by file name.
It references them by logical name instead, and uses loaders to resolve that to whatever storage you'd like to use for the template strings.

Xedin Unknown

unread,
Sep 25, 2017, 2:21:35 AM9/25/17
to PHP Framework Interoperability Group
I believe that's a great argument for not including the template *source* in the description. A template instance can encapsulate `vsprintf()`, for example, and there's never going to be a file or a path resolution. This is all implementation detail that can be handled via IoC.

$template = new PrintfTemplate('Hello, %1$s!');
$context
= new DataObject(['Xedin']);
$greeting
= $template->render($context); // Hello, Xedin!

FGM at GMail

unread,
Sep 25, 2017, 5:48:24 AM9/25/17
to php...@googlegroups.com
Something else could be missing: metadata attached to the rendered result, like caching tags, contexts, which need to be gathered when assembling render results to build an actual HTTP response (coming from a Drupal "render array" background, as some can guess).

--
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Thomas Gnandt

unread,
Sep 25, 2017, 11:41:18 AM9/25/17
to PHP Framework Interoperability Group
I agree that it is a good idea to have a general TemplateInterface instead of a string representing the template. However I don't think we should then also have a TempleteRendererInterface to render this template: this would require that any implementation of TemplateInterface could be rendered by any implementation of TempleteRendererInterface which would definitely not be the case: a MustacheTemplate could not be rendered by a TwigRenderer.

Concerning the type of the data that is passed to the render function (aka context): If we don't use the native array and/or ArrayObject the defined interface should allow the possiblity to cast to an array. I don't think using ContainerInterface alone would be enough for two reasons:
Many template engines rely on extract() to allow access to the context.
Merging multiple contexts together would become impossible. Instead for every access multiple contexts would have to be searched, which would probably lead to performance problems.

I think using Xedin's StringableInterface as the return type is a good idea. Using StreamInterface is probably not necessary in a lot of cases, but since the two interfaces are not mutually exclusive it would be possible to return a StreamInterface if the implementor wants to do so.

@FGM Are you suggesting that the abilty to return metadata should be part of these interfaces? Or was this a concern if the render function returns a string? I'm not sure if it would be possible to define a common MetadataInterface.

mecha

unread,
Sep 25, 2017, 1:55:05 PM9/25/17
to PHP Framework Interoperability Group
@FGM If the template returns a StringableInterface, as Xedin suggested, then you can create your own return type that is standards-compliant and also contains other information.

<?php

interface MetaDataRenderInterface extends Dhii\Util\String\StringableInterface
{
   
public function getMetaData(); // Or whatever else you require
}

Your consuming code can check if the returned stringable result is an instance of `MetaDataRenderResult` and if so, process meta data.

The reason why we (Dhii) use `StringableInterface` is because it allows us to keep the API simple, while allowing us to use objects when we need more information. In fact, with our current dhii/output-renderer-interface package, it is possible to have a renderer return another renderer in the form of a BlockInterface, and the consuming code would never know because it only deals with string or string-like results.

On Monday, 25 September 2017 11:48:24 UTC+2, FGM wrote:
Something else could be missing: metadata attached to the rendered result, like caching tags, contexts, which need to be gathered when assembling render results to build an actual HTTP response (coming from a Drupal "render array" background, as some can guess).
2017-09-25 8:21 GMT+02:00 Xedin Unknown <xedin....@gmail.com>:
I believe that's a great argument for not including the template *source* in the description. A template instance can encapsulate `vsprintf()`, for example, and there's never going to be a file or a path resolution. This is all implementation detail that can be handled via IoC.

$template = new PrintfTemplate('Hello, %1$s!');
$context
= new DataObject(['Xedin']);
$greeting
= $template->render($context); // Hello, Xedin!



On Monday, September 25, 2017 at 8:15:27 AM UTC+2, Nicolas Grekas wrote:
> Granted, the majority of template engines use a template file-name and a set of name/value pairs

Twig also doesn't reference templates by file name.
It references them by logical name instead, and uses loaders to resolve that to whatever storage you'd like to use for the template strings.

--
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 post to this group, send email to php...@googlegroups.com.

David Lundgren

unread,
Sep 25, 2017, 7:34:22 PM9/25/17
to PHP Framework Interoperability Group
Greetings,

This may seem like shameless self-promotion, but I had an idea similar to this last year, that has sort of fallen by the wayside. I am happy that someone brought it up on this list, and I would love to move the idea forward.

In the same way that http-interop did, I created an output-interp organization on github, https://github.com/output-interop

The spec that is currently published was founded on the work that Giuseppe Mazzapica started with Foil PHP https://github.com/FoilPHP/Foil as I found it an excellent basis for a rendering system.

I do have some code that I wrote that allowed me to switch between Twig, Smarty, php, Blade, with the primary differences being render syntax of variables.

Dave

Nicolas Grekas

unread,
Sep 26, 2017, 1:01:35 AM9/26/17
to php...@googlegroups.com


Le 26 sept. 2017 01:34, "David Lundgren" <dlun...@syberisle.com> a écrit :
In the same way that http-interop did, I created an output-interp organization on github, https://github.com/output-interop

The spec that is currently published was founded on the work that Giuseppe Mazzapica started with Foil PHP https://github.com/FoilPHP/Foil as I found it an excellent basis for a rendering system.

May I also suggest to look at Symfony Templating component, which has almost the same goal. The common parts of several independent implementations may be a good way to bootstrap a new standard IMHO.



Xedin Unknown

unread,
Sep 26, 2017, 5:17:38 AM9/26/17
to PHP Framework Interoperability Group
`Template#file()` seems extra to me, because it breaks ISP. `Engine` also looks very non-segregated to me.
Could you elaborate on the usage of `Context`? I couldn't understand how it could be used. I use an interop context in the form of `ContainerInterface`.

David Lundgren

unread,
Sep 26, 2017, 9:55:03 AM9/26/17
to PHP Framework Interoperability Group

On Tuesday, September 26, 2017 at 4:17:38 AM UTC-5, Xedin Unknown wrote:
`Template#file()` seems extra to me, because it breaks ISP.

This method is used by the Context to be able to determine whether the data it contains actually applies to the template.
 
`Engine` also looks very non-segregated to me.

Engine is basically what ties everything together to give a single entry point in to the the rendering system. This would effectively be the Renderer.
 
Could you elaborate on the usage of `Context`? I couldn't understand how it could be used.

You used DataObject in an earlier email to describe the same thing, Context's hold the data that the templates will use. Context's in this case have additional functionality in that they can conditionally provide their data to the template based off of the templates name. This gives the ability to globally create data, and have a specific only get that data.
 
I use an interop context in the form of `ContainerInterface`.

I didn't want to tie any other interfaces in to this system if it could be  avoided, preferring to be self-contained. I feel like requiring a container, or logger, should be a decision that the frameworks should be making.

It seems like we may already be close to how it should work, mainly naming differences, and a few extraneous methods/classes.

My idea when originally building this spec was to do the smallest possible interface for a rendering system. I would then pitch this to the framework authors, and hopefully gain traction from them to support the interop project, before coming here. This would let us easily swap out rendering systems if they adopted this interface.

As I mentioned previously, here are the code examples I've written

Dave

Xedin Unknown

unread,
Sep 26, 2017, 12:55:24 PM9/26/17
to PHP Framework Interoperability Group
IMHO, having a template work with a file, or a string, or a stream - is implementation detail. Having a separate component responsible for rendering, e.g. a rendering engine, is also an implementation detail. All this can be DI'd into a template, which itself knows how to render itself, given a context. It may use an engine, or do the rendering itself, e.g. use a `printf()`-style format string. The template source may be a string, instead of a file; same example applies. What's important, IMHO, is that, given a context, it can produce output, regardless of how that is done.
As for context, `ContainerInterface` is an already existing standard that allows retrieval of arbitrary data by key. By having a `ContextInterface` such as yours, you are requiring the implementation to be compatible with a new standard, and be aware of the method of sourcing the template itself. Also, IMO it's not important what data the context provides, as long as it provides all the data that the template requires.

I feel like this approach is very non-SOLID, because the interfaces are not segregated, domain-specific instead of interoperable, and SoC is not maintained.

mecha

unread,
Sep 26, 2017, 1:04:27 PM9/26/17
to PHP Framework Interoperability Group
If I may give my two cents, I also find anything that has to do with files extra.

I think a renderer is, at its very core, something that simply renders. i.e. something which, when told to `render()`, simply renders no questions asked.
An `EnglishAlphabetRenderer` will quite simply give "abcdefghijklmnopqrstuvwxyz".

A template renderer is a renderer that renders a template. At this point, it becomes important to define what a "template" is. Me and Xedin had a not-so-short discussion about this topic when we came to create our own standard, because it is very un-intuitive at first and may sound quite backwards.

Definition in my own words: "a template is a representation of a set of possible results that can be narrowed done to a specific result if given context."

To use a non-dev example, a ruler can be considered to be a template - given different colors (context) you can draw red, blue or green lines - given different apparatus you can draw lines using a pen, marker, pencil or blood, if you're creepy like that.
But, what makes the ruler a template? Is it the shape, or the physical matter that makes it rigid enough to draw against? We believe its the matter that makes it a template - change its shape and it remains a ruler, just for different things. Square rulers with holes in them for various shapes are called stencils, but they are basically just rulers because what's different is just the configuration of that matter. The underlying concept is the same - drag a drawing apparatus across its rigid surface and you will draw shapes.

Similarly, a template can itself be considered a renderer. To re-use Xedin's previous example:
"Hello %s" is the configuration while `sprintf()` is the mechanism for rendering and `sprintf("Hello %s", $name)` is the template. Given context "Xedin", the result would be "Hello Xedin".

What we typically think is the template (ex. "Hello %s") is in fact the configuration. The only difference is that a template MAY need context when told to "render()".

Notice how I do not mention files - it's the whole point I'm trying to make. I think files are a very convenient way of storing the configuration of a template, but they are not required by any renderer.
As far as the API for a renderer or engine goes, I have yet to see valid reasons for anything other than a "render()" method, or a proxy thereof, to exist.

For reference to our own standard:

Xedin Unknown

unread,
Sep 27, 2017, 6:52:19 AM9/27/17
to PHP Framework Interoperability Group
Having given it quite a bit of thought, and passing this by @mecha, I would like to provide some feedback with regard to working with streams. As a disclaimer, I would like to mention that I do not make solving a specific problem the target of this standard; however, I do want to make sure that specific problems _can_ be solved. I believe streaming to be one of them, and I guess @David would agree with me.

I really dig the need for streaming, as the rendering output can become really really huge. It may certainly be preferable to "cache" the output to disk, and then send it to the server in a more appropriate manner. As an example, imagine that your software needs to print a very large confirmation document, such as a receipt, which contains a lot of detail about every item. Streaming would be extremely useful here, because it allows you to occupy only a small amount of memory at any point in time, while still handling the rendering like a boss using the same algorithm. Duh. Cool.

The problem is that, because the template is responsible for rendering, and the return value (stream) is responsible for retrieving the value in chunks (they are two separate things), the stream would need to be buffered. Buffered streams work by having something write to the stream at the same time as something else reads from it. And this can work really well in some cases. However, it only works in cases when rendering is done in steps. When you are rendering PHTML or Mustache, there are no steps, e.g. you cannot tell the engine to render only the first e.g. 256 bytes of the _template_. You can only buffer output in chunks, and this can easily be done by using the 2 parameters of `ob_start()`, e.g. `ob_start('function_that_writes_to_disk', 256)`. This means that the events are coming from the stream, instead of the consumer of the stream. This conflicts with the whole philosophy of the stream standard, whereby it provides the consumer a way to "request" a number of bytes from it. In this case, a buffered stream would still have to contain the complete output at some point. A sub-optimal solution to that could be using the `php://temp` stream, which would start writing "overflowing" bytes to disk. However, this is not the best solution, since it will make the application slower, and most importantly ignores the advantage of the output callback, which _should_ be used for writing to the destination.

The solution, in my opinion, lies in the assumption that a stream is something that `TemplateInterface#render()` should _return_. If, instead of returning a readable stream, `render()` could _accept_ a *writable* stream, the problem disappears on its own.

$sourcePath = 'my-template.php';
$destinationPath
= 'my-template.cache';
$destinationStream
= new WritableFileStream($destinationPath);
$template
= new PhpTemplate($sourcePath);
$template
->render($context = null, $destinationStream);

So, here we have a signature which suggests that in certain cases, a second parameter should be accepted by the `render()` method. I would say that
If the `$stream` parameter is provided, output will be written to it, and the returned value will evaluate to an empty string.

And this is OK, because due to stream being passed at call time, the consumer can predict the result. However, the dilemma here for me is whether this should become part of the _standard_, or maybe this can perhaps remain an implementation detail, in the sense that particular implementations (or proprietary interfaces) can _add_ the second optional parameter. Another alternative is to have an e.g. `StreamingTemplateInterface` which extends the signature to add the second parameter, without cluttering the main interface. LSP is also maintained. I would much prefer not to add the second parameter to the main interface. The fact that there's no `StreamInterface` outside of PSR-7 suggests that the second parameter should not be part of the proposed standard at all, at least in its first stable release. Meanwhile, I can continue working on my standalone `StreamInterface` standard, and when it's ready I would publish an extension standard, similarly to what I did with PSR-11, that would add the `StreamingTemplateInterface` and depend on `StreamInterface` there. Like this, we can take it step by step and keep the official standards clean, while still leaving the option to evolve. In fact, please correct me if I'm wrong, but I have not noticed much evolution with regard to any particular PSR. I would be happy to contribute to that.

On Sunday, September 24, 2017 at 9:09:37 PM UTC+2, David Négrier wrote:

David Lundgren

unread,
Sep 27, 2017, 9:12:56 AM9/27/17
to PHP Framework Interoperability Group
On Tuesday, September 26, 2017 at 12:04:27 PM UTC-5, mecha wrote:
If I may give my two cents, I also find anything that has to do with files extra.

Agreed, the engine spec shouldn't really be dealing with files, and I completely forgot about streams.

For reference to our own standard:
Thanks for the clarification. I misunderstood the intent of the renderer interfaces from earlier in the conversation.

With the engine spec targeting a different aspect of interoperability, it appears it could most likely be complimentary to a renderer interface PSR, and may sit on top of it. It is along the lines of Hari's and Nicolas' suggestions of using Symfony, Zend Framework, or even Twig's usage as an example. So I think it's out of scope for a pure renderer, as is being discussed.

With that in mind, I'll keep working on my engine spec, and look forward to seeing the results of the renderer interface.

Dave
 

Niklas Keller

unread,
Sep 28, 2017, 3:03:24 AM9/28/17
to PHP Framework Interoperability Group
Having given it quite a bit of thought, and passing this by @mecha, I would like to provide some feedback with regard to working with streams. As a disclaimer, I would like to mention that I do not make solving a specific problem the target of this standard; however, I do want to make sure that specific problems _can_ be solved. I believe streaming to be one of them, and I guess @David would agree with me.

I don't agree. There's no need for one solution fit them all. Always having to return an object might be bad for the developer experience. On the other hand, it's mostly some library that implements the interface, so it might not really matter.
 
I really dig the need for streaming, as the rendering output can become really really huge. It may certainly be preferable to "cache" the output to disk, and then send it to the server in a more appropriate manner. As an example, imagine that your software needs to print a very large confirmation document, such as a receipt, which contains a lot of detail about every item. Streaming would be extremely useful here, because it allows you to occupy only a small amount of memory at any point in time, while still handling the rendering like a boss using the same algorithm. Duh. Cool.

But not worth the effort in 99% of all cases.
 
The problem is that, because the template is responsible for rendering, and the return value (stream) is responsible for retrieving the value in chunks (they are two separate things), the stream would need to be buffered. Buffered streams work by having something write to the stream at the same time as something else reads from it. And this can work really well in some cases. However, it only works in cases when rendering is done in steps. When you are rendering PHTML or Mustache, there are no steps, e.g. you cannot tell the engine to render only the first e.g. 256 bytes of the _template_. You can only buffer output in chunks, and this can easily be done by using the 2 parameters of `ob_start()`, e.g. `ob_start('function_that_writes_to_disk', 256)`. This means that the events are coming from the stream, instead of the consumer of the stream. This conflicts with the whole philosophy of the stream standard, whereby it provides the consumer a way to "request" a number of bytes from it. In this case, a buffered stream would still have to contain the complete output at some point. A sub-optimal solution to that could be using the `php://temp` stream, which would start writing "overflowing" bytes to disk. However, this is not the best solution, since it will make the application slower, and most importantly ignores the advantage of the output callback, which _should_ be used for writing to the destination.

The solution, in my opinion, lies in the assumption that a stream is something that `TemplateInterface#render()` should _return_. If, instead of returning a readable stream, `render()` could _accept_ a *writable* stream, the problem disappears on its own.

$sourcePath = 'my-template.php';
$destinationPath
= 'my-template.cache';
$destinationStream
= new WritableFileStream($destinationPath);
$template
= new PhpTemplate($sourcePath);
$template
->render($context = null, $destinationStream);

So, here we have a signature which suggests that in certain cases, a second parameter should be accepted by the `render()` method. I would say that
If the `$stream` parameter is provided, output will be written to it, and the returned value will evaluate to an empty string.

Sorry, but what? There should be a defined return type that's not changing based on the parameters passed. Returning a readable stream is just fine, you just need to figure out how to handle backpressure. You might want to have a look at https://amphp.org/byte-stream/, which is async, but the same thing applies to sync, just that you can skip the promises.

Regards, Niklas

Xedin Unknown

unread,
Sep 29, 2017, 9:03:48 AM9/29/17
to PHP Framework Interoperability Group
Hi Niklas,

Always having to return an object might be bad for the developer experience

Which is why I allow an object that can be cast to string. Please take another look at the code. Also, only allowing a primitive type is probably also very bad.

There's no need for one solution fit them all
But not worth the effort in 99% of all cases.

FIG is widely regarded as the de-facto PHP standards body. The solution should at least try to fit all. IMHO, if you release enterprise-grade standards to be used by hundreds of thousands of people, you should at least make sure that common serious tasks can be performed, instead of understanding 6 months down the line that the new standard is useless for the big players.

There should be a defined return type that's not changing based on the parameters passed

That's right, the type doesn't change. The returned value simply evaluates to an empty string, like I wrote. An empty string is also a string.

Returning a readable stream is just fine, you just need to figure out how to handle backpressure

I feel like there's a lack of understanding here somewhere. Allow me to re-iterate.

The way to use readable streams is to request a certain amount of data from them by using e.g. `read()`. This makes the stream's consumer the initiator of the "event". `read()` needs something to take the data from, whether an open connection or a variable. But output of templates such as PHTML does not happen like that. You cannot request a certain number of characters from it; instead, you request it to be rendered, and *handle* a certain number of characters of output at a time. This is how you handle the "backpressure" - in a kind of event-driven way.
But like this, the template is the initiator of the event, instead of the consumer. Therefore, they must sync somewhere, presumably in the stream's internal buffer. However, the operations with it do not happen in a "write - read - write - read" way. Instead, in order to utilize this approach, the whole output would need to be buffered, and then read - perhaps in chunks. But the buffer's size can grow unpredictably large.
Also, the stream that writes to e.g. a file handle must already have that file handle when output is available to the buffer. But the `render()` method doesn't know where to write to. Given a stream, it would be able to write to any destination in an abstract way. Without this, it just doesn't seem possible. If you believe it is, perhaps you could demonstrate?

Xedin Unknown

unread,
Sep 30, 2017, 3:23:07 AM9/30/17
to PHP Framework Interoperability Group
Another possible approach that helps avoid contaminating `TemplateInterface#render()`, but allows writing via a stream. Although not strictly part of the "template" standard, but definitely solves the problem.

Being permitted to return an stringable object, the implementation may choose to return a `BlockInterface`. This object can implement another interface, e.g. some `WriterInterface` with `write()`, and be configured post-initialization to write the output to a stream when `write()` is called. This has two problems:

1. Mutability. The object's configuration will change after initialization. I try very hard to avoid that. Besides the obvious, it feels like code which depends on a standard should not have to call e.g. `setOutput($stream)`.
2. Bad SoC. It's the result of `TemplateInterface#render()` that will have to do the rendering, instead of the template itself. This, of course, is just implementation detail, but still feels wrong and convoluted.

The solution whereby one could specify the output stream during a call to `TemplateInterface#render()` is much cleaner, concerns are properly separated, standards are being maintained. Thoughts?

Xedin Unknown

unread,
Sep 30, 2017, 7:47:18 AM9/30/17
to PHP Framework Interoperability Group
Some more alternatives for streaming (also, the other methods described so far for completeness):

  1. Async stream (new). Unlike the buffered stream, this stream can respond to events. The streams of ReactPHP are an example of that.
    Pros: Allows returning a sort of "promise", and read in an event-driven way without contaminating the simple `TemplateInterface#render()`.
    Cons: Poor SoC, due to the mechanics of evaluation and output buffering. Requires yet another complicated standard.
  2. Context responsibility (new). Implementations can detect whether context also implements e.g. `WritableStreamInterface`, and write to it if applicable.
    Pros: Allows specifying an output target without contaminating the simple `TemplateInterface#render()`.
    Cons: Poor SoC due to making the context do more than what a context should do, i.e. provide input values. Requires another standard.
  3. Additional parameter. A proprietary interface, or a future interop interface, may add a writable stream as an optional parameter. To maintain LSP, the following behaviour is recommended: if the stream parameter is detected and used, the return value of `render()` evaluates to an empty string.
    Pros: Directly allows simple way to stream to the destination without buffering in or additional API to support events. Good SoC.
    Cons: Contaminates `TemplateInterface#render()` (or descendant) with additional optional parameter.
  4. Buffered stream. The template internally creates a stream, and writes data to it as it is rendered, filling the buffer. That stream is then returned, and the data can be read from the buffer.
    Pros: Simple, clear interface; no change of signature or optional parameters. Good SoC and LSP easily maintained. 
    Cons: Requires a buffer, which means that the whole output will be stored in memory at some point, defeating the point of using streams.
@Niklas, I've caught up with the usage of generator `yield` and `send()` that can be used for this purpose. Truth be told, this looks like an elegant solution. IMO, there are 2 problems:
  1. Requires PHP >= PHP 7. Not so much of a problem, really, because those who write cool efficient implementations, such as AMP, have no trouble running recent PHP versions. However, read below.
  2. Non-interop consumption. The approach is actually similar to the above "Async stream" one, just with better, more elegant syntax. The key here is that e.g. `WritableStreamInterface#write()` will resolve the promise, I imagine, or something like that. Of course, the problem is that there's a promise to be resolved, e.g. `read()` does not yield data. With promise-less sync approach, AFAIU, there's no way around PHP 7 syntax, which is not something I would prefer a PSR depend on. And still, it kinda looks like the stream would have to render the template, which seems wrong to me.
On Wednesday, September 27, 2017 at 12:52:19 PM UTC+2, Xedin Unknown wrote:

Xedin Unknown

unread,
Sep 30, 2017, 7:58:54 AM9/30/17
to PHP Framework Interoperability Group
Oh yes, and also we could, of course, standardize the signature of `render()` to always accept a writable resource. This solves all problems, but makes the API less straight-forward.

Xedin Unknown

unread,
Oct 1, 2017, 4:36:11 PM10/1/17
to PHP Framework Interoperability Group
@David,

It appears that there is a need for a standard for interop stream consumption. I would say that this standard would be useful even outside of the scope of this thread. Would you suggest that I make a new proposal thread? My take on it is in dhii/io-resource-interface; please take a look.


On Sunday, September 24, 2017 at 9:09:37 PM UTC+2, David Négrier wrote:

Niklas Keller

unread,
Oct 2, 2017, 1:52:51 AM10/2/17
to php...@googlegroups.com
Sorry for the late response, sent the mail from another address earlier, but that one isn't subscribed to the group and got rejected.
 
Hi Niklas,

Always having to return an object might be bad for the developer experience

Which is why I allow an object that can be cast to string. Please take another look at the code. Also, only allowing a primitive type is probably also very bad.

Let's not repeat the failure of PSR-7 using __toString(). It can't throw exceptions and it's impossible to handle errors in a sane way.
 
There's no need for one solution fit them all
But not worth the effort in 99% of all cases.

FIG is widely regarded as the de-facto PHP standards body. The solution should at least try to fit all. IMHO, if you release enterprise-grade standards to be used by hundreds of thousands of people, you should at least make sure that common serious tasks can be performed, instead of understanding 6 months down the line that the new standard is useless for the big players.

There should be a defined return type that's not changing based on the parameters passed

That's right, the type doesn't change. The returned value simply evaluates to an empty string, like I wrote. An empty string is also a string.

An empty string is worthless here. The return value doesn't matter and could also be null. If you pass a stream you change the behavior from returning to writing to a stream passed as a parameter. While it's true that a consumer always knows what will be returned, it's not possible for static analysis tools to know that. A separate method would be a better choice.
 
Returning a readable stream is just fine, you just need to figure out how to handle backpressure

I feel like there's a lack of understanding here somewhere. Allow me to re-iterate.

The way to use readable streams is to request a certain amount of data from them by using e.g. `read()`. This makes the stream's consumer the initiator of the "event". `read()` needs something to take the data from, whether an open connection or a variable. But output of templates such as PHTML does not happen like that. You cannot request a certain number of characters from it; instead, you request it to be rendered, and *handle* a certain number of characters of output at a time.

If you can handle a certain number of characters of output at a time, you can also implement it with a `read()` API. Just start rendering if it didn't start yet and return "a certain number of characters of output", continue rendering on the next `read()` request like you would after writing to the stream like you suggested.
 
This is how you handle the "backpressure" - in a kind of event-driven way.

Where's the event in a writable destination stream approach?
 
But like this, the template is the initiator of the event, instead of the consumer. Therefore, they must sync somewhere, presumably in the stream's internal buffer. However, the operations with it do not happen in a "write - read - write - read" way. Instead, in order to utilize this approach, the whole output would need to be buffered, and then read - perhaps in chunks. But the buffer's size can grow unpredictably large.
Also, the stream that writes to e.g. a file handle must already have that file handle when output is available to the buffer. But the `render()` method doesn't know where to write to. Given a stream, it would be able to write to any destination in an abstract way. Without this, it just doesn't seem possible. If you believe it is, perhaps you could demonstrate?

The implementation will probably involve a layer of indirection. It's probably simpler to go with a `WritableStream` as you suggested, but I prefer separate methods for that as mentioned above. It's trivial for an implementation that supports streaming to provide an implementation that renders to a string and the other way around.

Regards, Niklas
 
On Thursday, September 28, 2017 at 9:03:24 AM UTC+2, Niklas Keller wrote:
Having given it quite a bit of thought, and passing this by @mecha, I would like to provide some feedback with regard to working with streams. As a disclaimer, I would like to mention that I do not make solving a specific problem the target of this standard; however, I do want to make sure that specific problems _can_ be solved. I believe streaming to be one of them, and I guess @David would agree with me.

I don't agree. There's no need for one solution fit them all. Always having to return an object might be bad for the developer experience. On the other hand, it's mostly some library that implements the interface, so it might not really matter.
 
I really dig the need for streaming, as the rendering output can become really really huge. It may certainly be preferable to "cache" the output to disk, and then send it to the server in a more appropriate manner. As an example, imagine that your software needs to print a very large confirmation document, such as a receipt, which contains a lot of detail about every item. Streaming would be extremely useful here, because it allows you to occupy only a small amount of memory at any point in time, while still handling the rendering like a boss using the same algorithm. Duh. Cool.

But not worth the effort in 99% of all cases.
 
The problem is that, because the template is responsible for rendering, and the return value (stream) is responsible for retrieving the value in chunks (they are two separate things), the stream would need to be buffered. Buffered streams work by having something write to the stream at the same time as something else reads from it. And this can work really well in some cases. However, it only works in cases when rendering is done in steps. When you are rendering PHTML or Mustache, there are no steps, e.g. you cannot tell the engine to render only the first e.g. 256 bytes of the _template_. You can only buffer output in chunks, and this can easily be done by using the 2 parameters of `ob_start()`, e.g. `ob_start('function_that_writes_to_disk', 256)`. This means that the events are coming from the stream, instead of the consumer of the stream. This conflicts with the whole philosophy of the stream standard, whereby it provides the consumer a way to "request" a number of bytes from it. In this case, a buffered stream would still have to contain the complete output at some point. A sub-optimal solution to that could be using the `php://temp` stream, which would start writing "overflowing" bytes to disk. However, this is not the best solution, since it will make the application slower, and most importantly ignores the advantage of the output callback, which _should_ be used for writing to the destination.

The solution, in my opinion, lies in the assumption that a stream is something that `TemplateInterface#render()` should _return_. If, instead of returning a readable stream, `render()` could _accept_ a *writable* stream, the problem disappears on its own.

$sourcePath = 'my-template.php';
$destinationPath
= 'my-template.cache';
$destinationStream
= new WritableFileStream($destinationPath);
$template
= new PhpTemplate($sourcePath);
$template
->render($context = null, $destinationStream);

So, here we have a signature which suggests that in certain cases, a second parameter should be accepted by the `render()` method. I would say that
If the `$stream` parameter is provided, output will be written to it, and the returned value will evaluate to an empty string.

Sorry, but what? There should be a defined return type that's not changing based on the parameters passed. Returning a readable stream is just fine, you just need to figure out how to handle backpressure. You might want to have a look at https://amphp.org/byte-stream/, which is async, but the same thing applies to sync, just that you can skip the promises.

Regards, Niklas

--
You received this message because you are subscribed to a topic in the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/php-fig/w1cugJ9DaFg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to php-fig+unsubscribe@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

Xedin Unknown

unread,
Oct 2, 2017, 4:20:48 AM10/2/17
to PHP Framework Interoperability Group
Let's not repeat the failure of PSR-7 using __toString(). It can't throw exceptions and it's impossible to handle errors in a sane way.

An excellent point, actually. If an implementation wants to go in that direction, it is free to implement `__toString()` and pass itself. But in other cases, the contents would have to be explicitly retrieved first. Unless, of course, the consumer, such as the renderer, supports streams specifically. Which of course complicates things somewhat, but it's probably still better than having an exception printed into your PDFs :D
@Niklas, would you like to take a look at my resource interfaces in dhii/io-resource-interface? I am thinking to propose it as a standard here.

An empty string is worthless here. The return value doesn't matter and could also be null. If you pass a stream you change the behavior from returning to writing to a stream passed as a parameter. While it's true that a consumer always knows what will be returned, it's not possible for static analysis tools to know that. A separate method would be a better choice.

Also good point about static analysis tools. But a separate method that does the same thing... is not something that I really like. What do you think about always requiring a writable stream, instead, now that you agree that this is a simpler approach?

If you can handle a certain number of characters of output at a time, you can also implement it with a `read()` API. Just start rendering if it didn't start yet and return "a certain number of characters of output", continue rendering on the next `read()` request like you would after writing to the stream like you suggested.
 
"continue rendering" what exactly? I already explained twice that you cannot tell PHP to only evaluate a certain number of chars of the template, so I assume that you do not mean to continue reading the template. If you mean the output, on the other hand, then that output would first have to be stored in a buffer, which defeats the point of using streams in this case. The only way to do that is to do a sort of inverse generator, such as yours, where consuming code is actually the generator. In this case, it looks like a loop implementation would still be necessary. Am I right?

Where's the event in a writable destination stream approach?

Not quite sure I understand the question. In a writable stream approach, the output buffering callback is the initiator. Is this what you mean?

The implementation will probably involve a layer of indirection. It's probably simpler to go with a `WritableStream` as you suggested, but I prefer separate methods for that as mentioned above. It's trivial for an implementation that supports streaming to provide an implementation that renders to a string and the other way around.

As I mentioned before, it could also be a solution to always require a writable stream. Unified logic can then, instead of `$output .= $newOutput`, do `$output->write($newOutput)`, which is trivial, like you said. I like trivial. All of the projects I architect are made out of small and trivial parts. This really helps understanding and debugging the code, not to mention testing. Thoughts? Would be nice to hear the opinion of somebody from FIG, such as @David Négrier.


To unsubscribe from this group and all its topics, send an email to php-fig+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages