OK, this has finally floated to the top of my todo list.
This is mostly a response to Michael's post here. Michael, thank you
for the detailed writeup even if I don't entirely agree with you yet. :-)
http://mtdowling.com/blog/2014/07/03/a-case-for-higher-level-php-streams/
Summary:
Fully reimplementing PHP streams in user-space, while I can see the
arguments to doing so, is going too far, especially as only part of what
is supposed to be an HTTP spec. Let's do something a little less
aggressive instead and explicitly wrap PHP streams with a better API.
Detailed version:
I agree with Michael's arguments for raw strings and iterators being
insufficient. I don't think anything more needs to be said here.
Regarding auto-registering: Yes, it's annoying that PHP streams can't
lazy-register. It's also mind-bendingly dumb that you register a class,
not an object, as you pretty much can't do proper OO that way. But it's
not a fatal problem; Drupal has successfully been using streams for all
of its file handling, including custom streams, for 4 years now. From
Michael's post:
"The problem here is that you would need some kind of bootstrap script
to register your named stream wrappers and filters before they can be
utilized."
That's entirely true. However, I hardly think that's a fatal issue.
Virtually all PHP apps of meaningful size need some sort of bootstrap
process; even the modern "clean" frameworks have a bootstrap of some
kind. Such is life in PHP. Also, with Composer's ability to include a
file by default it is not at all hard to ship a composer-able stream
wrapper that gets registered when your autoloader boots, in which case
this problem is mostly eliminated.
Regarding Exceptions vs Warnings: I am hardly one to defend PHP's global
error system. I would much prefer to be using Exceptions. However,
welcome to PHP. Streams are hardly the only place that throws warnings
when it would be better to do something else. If we want to design
systems to avoid all error emittance we'd also have to blacklist
htmlspecialchars(), (I've lost way too much of my life to it throwing
warnings on inconsistent charcter encoding), htmlentities(), LDAP,
array_chunk(), array_combine(), and probably a whole bunch more. That's
just what I found off the top of my head. Any PHP system needs to have
an error handler set to account for those already. So no, I disagree
that the existence of warnings is a reason to blacklist streams.
Regarding casting to a string: This is a valid point, especially as the
common cases, I expect, will all be reasonably-sized in-memory strings.
For most cases, developers are used to working with ordinary PHP strings
which are completely adequate.
Regarding the native stream API functions: This falls into the "well it
was like that in C circa 1975, so why do we need anything better?"
bucket that is often brought up on PHP internals. You can probably tell
from the snark what I think of that argument. :-) I fully agree with
Michael that the native stream API is fugly in many ways and long-due
for an overhaul.
In short: "PHP streams do everything we want, but the API for them sucks."
The solution suggested by PSR-7 now is to, essentially, reimplement PHP
streams in user-space. While I can see the desire to do so given the
above, I still believe that's a bad idea and goes too far.
As the rest of Michael's post shows, detach() means that, in essence,
you can use PHP stream handling or you can use StreamInterface wrappers,
but not both. That is a recipe for incompatibility. Also, with some
streams returning something useful on detatch() and others not that's
another built-in incompatibility. Built-in incompatibilities are a bad
idea, especially for what we hope becomes a standard low-level tool of PHP.
Also, it requires doing all stream operations in user space. That's
guaranteed to be slower than doing them in native stream operations in
the engine for the same reason that an iterator with a next() method
firing for every character is slower. That includes the common case,
which is "here's a string, send it" and "gimmie the incoming data as a
string" (or a POST array). Those need to be performant. Forcing them
to be implemented entirely in user space is not performant.
What I would propose as an alternative is to treat StreamInterface as,
explicitly, a "nicer UI for PHP streams". That is, it should always
wrap a native stream. (Paul, does that make it a facade? <g>) Then
provide methods that emulate the stream API but suck less. Eg,
isReadable() is fine as-is. A utility trait (yes, we can/should have
those) can do the necessary dance around the native API silliness.
Rather than expect wrapping objects, though, we can expose an equivalent
to stream_filter_append(). That way we can still use all the existing
stream support in PHP (and gain automatic compatibility with most
existing stream filters and stream wrappers).
It also means that the synchronization issues between the underlying
stream and StreamInterface go away, as the Stream object has no state of
its own; it's just forwarding to the resource. So if you really really
want to do something native, go ahead, but you probably don't have to.
Because the object is still there, the common case can still work
because __toString() is still included. So print $response->getBody()
still works. However, you can also do direct stream copying. That is,
an implementing library could very easily encapsulate:
public function send($out = NULL) {
if (!$out) {
$out = 'php://output'; // STDOUT would work, too.
}
stream_copy_to_stream($this->resource, $out);
}
Which would then work for any size stream of whatever source, as long as
it's readable, and as fast as the PHP engine will allow. (Reading from
php://input would be just as easy.)
A "degenerate case" StreamInterface implementation for just a literal
string is also fairly easy, as you can just use a php://temp stream
(which will automatically handle memory vs. disk as the size of the
string requires; that's already baked in).
This approach would also mean that if/when PHP's underlying stream
handling improves (eg, we finally get lazy registration of stream
filter/wrapper objects, rather than explicit classes) no new work is
required on our part. That just works, and this spec improves along
with it without any further work on our part.
--Larry Garfield