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
> <https://github.com/Dhii/output-renderer-interface/blob/develop/src/TemplateInterface.php#L31>.
> 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/to
> pic/php-fig/w1cugJ9DaFg/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to
> php-fig+unsubscr...@googlegroups.com.
> To post to this group, send email to php-fig@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/ms
> gid/php-fig/a9d36028-2354-43a9-83c2-7ca75f1b54eb%40googlegroups.com
> <https://groups.google.com/d/msgid/php-fig/a9d36028-2354-43a9-83c2-7ca75f1b54eb%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
>
> For more options, visit https://groups.google.com/d/optout.
>

-- 
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 post to this group, send email to php-fig@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/php-fig/CANUQDCjXnFpG5eWeV24Eeb9JU-Fn9RqzBf158mM9BP2%3Dr3HPMg%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to