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.
On Saturday, September 30, 2017 at 1:47:18 PM UTC+2, Xedin Unknown wrote: > > 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 > <http://seregazhuk.github.io/2017/06/12/phpreact-streams/> 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: >> >> 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: >>> >>> 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); >>> >>> Notice that this is something that Zend is doing: >>> https://github.com/zendframework/zend-expressive-template/blob/master/src/TemplateRendererInterface.php#L23-L24 >>> >>> 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. >>> >>> >>> >>> Le dimanche 24 septembre 2017 15:16:06 UTC+2, Xedin Unknown a écrit : >>>> >>>> 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: >>>>> >>>>> 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 >>>>> <https://github.com/Dhii/output-renderer-interface/tree/develop>. 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 >>>>> <https://github.com/Dhii/output-renderer-abstract/tree/task/initial-classes> >>>>> 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. >>>>> >>>>> >>>>> >>>>> On Saturday, September 23, 2017 at 7:45:53 PM UTC+2, Thomas Gnandt >>>>> wrote: >>>>>> >>>>>> 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). >>>>>> >>>>> -- 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 [email protected]. To post to this group, send email to [email protected]. To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/dea65fc6-8ec5-4a23-a10a-8d81152f2ff1%40googlegroups.com. For more options, visit https://groups.google.com/d/optout.
