HTTP Message and streams

225 views
Skip to first unread message

Larry Garfield

unread,
Jul 26, 2014, 9:21:54 PM7/26/14
to php...@googlegroups.com
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

Paul M. Jones

unread,
Jul 26, 2014, 9:48:28 PM7/26/14
to php...@googlegroups.com

On Jul 26, 2014, at 8:21 PM, Larry Garfield <la...@garfieldtech.com> wrote:

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

You go to hell. ;-)


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

I have had an alternative in the back of my mind for a long time, and it's probably a little ugly, but I think it solves all the relevant cases, and I want to present it for consideration. (I apologize if this approach has been raised and dismissed earlier.)

To wit: A message body is anything that can be represented as a string *or* as a callable. There's no related typehint against MessageInterface::setBody().

This requires very little, if anything, from MessageInterface implementors. Implementors of "send" behavior are advised to check if the body is callable, and if so, call it; otherwise echo it as if a string. (Or alternatively, check if it is a string, and echo, otherwise call it as a callable.)

An off the cuff implementation:

class FileStreamer
{
public function __construct($filename)
{
$this->filename = $filename;
}

public function __invoke()
{
// use streams, an fread() loop, or whatever
// to deliver $this->filename
}
}

class Message
{
public function setBody($body)
{
$this->body = $body;
}

public function getBody()
{
return $this->body;
}
}


class Sender
{
public function send(Message $message)
{
// send headers, and then:

$body = $message->getBody();
if (is_callable($body)) {
$body();
} else {
echo $body;
}
}
}

$sender = new Sender;
$message = new Message;

// stream a file
$message->setBody(new FileStreamer('/path/to/file.jpg'));
$sender->send($message);

// echo rendered template html
$message->setBody('<html>...</html>');
$sender->send($message);


This completely sidesteps the issues whether or not to stream, how to stream, etc. It's all hidden behind the __invoke() of a body object, or any other callable for that matter.

I'm sure there are pitfalls that I have failed to consider; I trust everyone here will point them out to me soon enough. ;-)


--
Paul M. Jones
pmjo...@gmail.com
http://paul-m-jones.com

Modernizing Legacy Applications in PHP
http://mlaphp.com



Larry Garfield

unread,
Jul 27, 2014, 6:31:51 PM7/27/14
to php...@googlegroups.com
On 07/26/2014 08:48 PM, Paul M. Jones wrote:
> On Jul 26, 2014, at 8:21 PM, Larry Garfield <la...@garfieldtech.com> wrote:
>
>> 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>)
> You go to hell. ;-)

Love you, too. :-P
The callable use case is a valid one, definitely. I don't think either
the current PSR or my suggestion above would address it. :-/ Can one
route a callable through a stream...?

Generically speaking, I see three general cases that we should keep in mind:

1) The body is a string, and it's small enough to just be treated as a
normal PHP string. (This is the most common.)

2) The body is a stream resource, or something that can be easily
represented as such. A file on disk, a file on S3, etc.

3) A callable that will generate the body on-demand.

All three are legit use cases. All three have different approaches that
make them fast. And, I fear, the usage patterns may differ between
request and response. :-(

For a raw string it's fairly easy. Get/set is fine and print or
fwrite() are equally capable of sending it performantly.

For a stream resource, stream_copy_to_stream() is the most performant
option. We can't not support that. Raw strings can be reasonably
emulated as stream resources.

For a callable, batched print/fwrite() is likely the best you're going
to do performance-wise. But how you'll actually map an arbitrary stream
to that easily is another matter...

Hard problems are hard. :-(

--Larry Garfield

Evert Pot

unread,
Jul 28, 2014, 3:20:34 PM7/28/14
to php...@googlegroups.com

>
> 1) The body is a string, and it's small enough to just be treated as a
> normal PHP string. (This is the most common.)
>
> 2) The body is a stream resource, or something that can be easily
> represented as such. A file on disk, a file on S3, etc.
>
> 3) A callable that will generate the body on-demand.
>
> All three are legit use cases. All three have different approaches
> that make them fast. And, I fear, the usage patterns may differ
> between request and response. :-(
>
> For a raw string it's fairly easy. Get/set is fine and print or
> fwrite() are equally capable of sending it performantly.
>
> For a stream resource, stream_copy_to_stream() is the most performant
> option. We can't not support that. Raw strings can be reasonably
> emulated as stream resources.
>
> For a callable, batched print/fwrite() is likely the best you're going
> to do performance-wise. But how you'll actually map an arbitrary
> stream to that easily is another matter...

Did anyone to a write-up of the perceived issues with simply exposing
PHP streams? All I hear is 'they are hard to work with', but with very
little background or proof.

Another question is, if they are indeed hard to work with in certain
use-cases.. is providing a 'pretty interface' to php internals really
something this group should be concerned with?

PHP streams already provide the interoperability we need. If someone is
interested in creating an OOP wrapper for PHP streams, the only way we
can really support this, is by providing raw php streams.

A better interface for PHP streams should be solved in PHP imho, but I'm
interested in hearing why php streams are perceived to be not usable here.

fwiw, sabredav solves it this way:

https://github.com/fruux/sabre-http/blob/master/lib/Sabre/HTTP/Message.php#L47

Evert

Paul M. Jones

unread,
Aug 1, 2014, 3:57:31 PM8/1/14
to php...@googlegroups.com

On Jul 27, 2014, at 5:31 PM, Larry Garfield <la...@garfieldtech.com> wrote:

> Generically speaking, I see three general cases that we should keep in mind:
>
> 1) The body is a string, and it's small enough to just be treated as a normal PHP string. (This is the most common.)
>
> 2) The body is a stream resource, or something that can be easily represented as such. A file on disk, a file on S3, etc.
>
> 3) A callable that will generate the body on-demand.
>
> All three are legit use cases. All three have different approaches that make them fast. And, I fear, the usage patterns may differ between request and response. :-(
>
> For a raw string it's fairly easy. Get/set is fine and print or fwrite() are equally capable of sending it performantly.
>
> For a stream resource, stream_copy_to_stream() is the most performant option. We can't not support that. Raw strings can be reasonably emulated as stream resources.
>
> For a callable, batched print/fwrite() is likely the best you're going to do performance-wise. But how you'll actually map an arbitrary stream to that easily is another matter...

After pondering this a while, it seems to me that there are exactly 2 primary functional elements to handle:

1. Get the entire body back as a string for echoing or further manipulation.

2. Tell the body to "send itself" without getting it back as a string.

Everything else, such as seeking forward and backward, is useful but secondary.

As such, the "message body as a stream" seems to be overkill to me. We could get away with something like __toString() and __invoke() as the only two methods on a hypothetical Http\Message\BodyInterface. That would cover both cases, and leave room for implementors to build streams or fread() loops or whatever else.

Chris Boden

unread,
Aug 29, 2014, 2:16:54 PM8/29/14
to php...@googlegroups.com
1) I agree with Paul that returning a raw stream wouldn't be the best idea. I think it's underkill as opposed to overkill. It's opening the end developer up to (IMO) too many possibilities and stream nuances to deal with. 

2) Paul, I don't quite understand your suggestion. Your first scenario I get, if it's a simple HTTP/1.0 response __toString() gets your the body. What would invoke do if called then?  In scenario 2 it's a streamed response. What does __toString() do, a single fread? What does __invoke() do in that case? Wouldn't this scenario break the abstraction? (actually asking, I have a feeling I don't understand).

3) At first glance Michael's StreamInterface and Guzzle's Stream implementation looks pretty elegant. I'll be spending more time looking into it soon. My *huge* problem with this though is that a StreamInterface definition does not belong in the HTTP Messages definition. Streams exist outside HTTP. If a StreamInterface is the route to take for PSR-7 I would advocate a set of stream interfaces be created first. 

This PR is an excellent suggestion IMO but the interfaces are missing/assumed. To be complete there should be:
ReadableStreamInterface
WritableStreamInterface
DuplexStreamInterface implements ReadableStreamInterface, WritableStreamInterface
(maybe more interfaces).
PSR7\ResponseInterface would then extend WritableStreamInterface

Jon Whelan

unread,
Oct 22, 2014, 12:07:23 PM10/22/14
to php...@googlegroups.com
As a completely naive client to an HTTP response object I would expect to be able to interact with the response as:

echo $response;

That line is the simplest possible and ideal interaction client code could have with a response object.

After all, an HTTP response object is just a message that I'm trying to output to the browser. Having the response object return either a string, callable, or stream makes the client have to handle all these cases so that you'll have to use something such as a sender object of sender function as mentioned above:

 class Sender 
    { 
        public function send(Message $message) 
        { 
            // send headers, and then: 

            $body = $message->getBody(); 
            if (is_callable($body)) { 
                $body(); 
            } else { 
                echo $body; 
            } 
        } 
    } 

But, how can we handle both cases where the HTTP response is either a string or a stream yet still allow our caller to be ignorant of that fact? We have to push it back into being an implemenation detail of the HTTP message itself. So if you just want to have a string response you'd use an object like this:

class StringHttpResponse
{
    private $stringBody;

    public function __construct($body)
    {
         $this->stringBody = $body;
    }

    public function __toString()
    {
         return $this->stringBody;
    }
}

to use a stream you'd have something like this:

class StreamedHttpResponse
{
    private $resource;

    public function __construct($resource)
    {
         $this->resource = $resource;
    }

    public function __toString()
    {
         stream_copy_to_stream($this->resource, 'php://output');
         return ''; // empty string since our stream is sent straight to php://output
    }
}

This allows the client to handle both cases in the exact same way, using the simplest code possible from the clients perspective.

Also, since we're no longer varying our return type, we no longer have to concern ourselves with creating a StreamableInterface to go along with our HttpMessageInterface PSR which is undoubtedly scope creep.

Matthew Weier O'Phinney

unread,
Oct 22, 2014, 1:01:28 PM10/22/14
to php...@googlegroups.com
On Wed, Oct 22, 2014 at 11:07 AM, Jon Whelan <jon.m....@gmail.com> wrote:
> As a completely naive client to an HTTP response object I would expect to be
> able to interact with the response as:
>
> echo $response;
>
> That line is the simplest possible and ideal interaction client code could
> have with a response object.

All this would require is implementing __toString(); it would be up to
the implementation to determine how to do so. We have not defined that
in the interface at this point; I'm unsure if there have previously
been requests for this or not. Michael Dowling, if you're reading
this, can you answer?

I have no problem adding it; it's trivial.

However, it's not the same as what you're writing here:

> After all, an HTTP response object is just a message that I'm trying to
> output to the browser. Having the response object return either a string,
> callable, or stream makes the client have to handle all these cases so that
> you'll have to use something such as a sender object of sender function as
> mentioned above:
>
>> class Sender
>> {
>> public function send(Message $message)
>> {
>> // send headers, and then:
>>
>> $body = $message->getBody();
>> if (is_callable($body)) {
>> $body();
>> } else {
>> echo $body;
>> }
>> }
>> }

Yes and no.

Yes, you would need a sender object or function. The message
interfaces model the messages, NOT behavior. If we add __toString() to
the interfaces, at MOST, they should return a TEXTUAL REPRESENTATION
of the message -- which is not at all the same as _emitting_ the
message. And when you consider server-side applications, that
representation is NOT what you want -- because you need to emit
headers and echo the body content _separately_. On top of that, you
often will have output buffering concerns. As such, that sort of logic
DOES belong elsewhere. You WILL need logic for emitting a server-side
response. That's okay, and expected -- that sort of logic is out of
the scope of this proposal. (If you want a comprehensive example of
how to do it: https://github.com/phly/http/blob/master/src/Server.php#L151-L214
)

But you're wrong in another regard. The StreamableInterface DOES
define __toString() explicitly. As such, you CAN just do this to echo
the message content:

echo $message->getBody();

There's no need to branch based on the capabilities or type of the
message body; it can be echo'd directly, because
StreamableInterface::__toString() is defined explicitly. In fact, in
my profiling and benchmarking (reported on an earlier thread), it's
even the FASTEST way (vs. traversing the stream manually, or using
fpassthru). Additionally, Michael Dowling has already provided
examples from guzzle/streams that demonstrate how you can implement a
StreamableInterface to wrap a callback -- meaning that the interface
and interactions you have with the message body remain uniform.

> But, how can we handle both cases where the HTTP response is either a string
> or a stream yet still allow our caller to be ignorant of that fact?

I've answered this previously. You can treat strings as streams
already natively using PHP's native php://memory and php://temp. In
phly/http and phly/conduit, I've used exactly those, as they make
writing to the underlying content trivial:

$message->getBody()->write('a string');
echo $message->getBody();

Don't get hung up on the body content being a stream. The fact is,
it's a very malleable interface, and has already been proven to
accommodate use cases from the simple to the complex... using native
PHP functionality.

> We have
> to push it back into being an implemenation detail of the HTTP message
> itself. So if you just want to have a string response you'd use an object
> like this:
>
> class StringHttpResponse
> {
> private $stringBody;
>
> public function __construct($body)
> {
> $this->stringBody = $body;
> }
>
> public function __toString()
> {
> return $this->stringBody;
> }
> }
>
> to use a stream you'd have something like this:
>
> class StreamedHttpResponse
> {
> private $resource;
>
> public function __construct($resource)
> {
> $this->resource = $resource;
> }
>
> public function __toString()
> {
> stream_copy_to_stream($this->resource, 'php://output');
> return ''; // empty string since our stream is sent straight to
> php://output
> }
> }
>
> This allows the client to handle both cases in the exact same way, using the
> simplest code possible from the clients perspective.

Two things:

- You're modelling only the body CONTENT. Using the word "Response"
above is confusing. (I almost had an entirely different response for
you until I re-read this and really understood what you were doing.)
I'm not sure if that's what you intended or not; if it's not, it means
you're missing out on setting the response status and headers, which
are also an integral part of the defined response interface.
- The above does not allow for content _aggregation_ or _traversal_
*by* *default*, which is part of why the StreamableInterface exists.
Sure, YOU may never have needed to send large files or buffered
content back via PHP, but neither are uncommon - and they're a big
part of the reason for modelling the body content as a stream instead
of as a string. As noted in earlier threads, without this built-in to
the interfaces, you end up resorting to hacks whenever you want to
enable these features. (We have some ugly ones in ZF2 due to this --
our own message interfaces model the content as a string.... learn
from our mistakes!)

> Also, since we're no longer varying our return type, we no longer have to
> concern ourselves with creating a StreamableInterface to go along with our
> HttpMessageInterface PSR which is undoubtedly scope creep.

"Undoubtedly" you did not read the meta document, as everything you've
said here about streams has been said before.

Sorry if I'm turning ugly - but please, folks, do EVERYONE a favor,
and read the meta documents for proposals before starting threads --
better yet, read back through the group archives, too.
> --
> 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.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/php-fig/bc5bde66-c1a7-4194-a2c6-f36f3b2a3523%40googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.



--
Matthew Weier O'Phinney
mweiero...@gmail.com
https://mwop.net/

Michael Dowling

unread,
Oct 22, 2014, 2:09:53 PM10/22/14
to php...@googlegroups.com
All this would require is implementing __toString(); it would be up to
the implementation to determine how to do so. We have not defined that
in the interface at this point; I'm unsure if there have previously
been requests for this or not. Michael Dowling, if you're reading
this, can you answer?

IIRC, I had __toString() on the interface at the start, but removed it by request.

I think removing __toString() serialization from the interface was a good choice: different implementations may choose to implement this in various ways, and serializing a message is not what these interfaces are describing. They're describing how to represent the parts of a message that have been parsed from the serialized form (servers) or will be serialized in various ways when sent via a client (e.g., curl, sockets, PHP stream wrapper, etc.).

Jon Whelan

unread,
Oct 22, 2014, 6:59:36 PM10/22/14
to php...@googlegroups.com
Allow me to explain my reasoning...

I'm not suggesting that getBody() return a string... I'm suggesting treating the HttpMessage object as a string itself.

What does an actually HTTP message look like? From wikipedia:
 
HTTP/1.1 200 OK
Date: Mon, 23&nbsp;May 2005&nbsp;22:38:34 GMT
Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)
Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT
ETag: "3f80f-1b6-3e1cb03b"
Content-Type: text/html; charset=UTF-8
Content-Length: 131
Accept-Ranges: bytes
Connection: close

<html>
<head>
  <title>An Example Page</title>
</head>
<body>
  Hello World, this is a very simple HTML document.
</body>
</html>

All that is—is text. Dividing it up into headers and body is just an implementation detail. All the browser/client cares about is getting text like that. Whether or not it's sent all at once or streamed.. it ultimately is represented as just text. So shouldn't a model of an HTTP message in code be able to be treated just as if it were text?

This is why I think it's more appropriate for client code to treat an HttpMessage object as a string and thus not doing this:

echo $message->getBody();

but instead:

echo $message;

The client code treats it as just text.. exactly what it is essentially. As my examples showed previously, doing this still affords you to use streams when necessary.

This line of code:

echo $message;

is different from this line of code:

echo $message->getBody(); 

If our HttpMessage object represent an actual HTTP message (which is just text) then the client should be able to treat the message itself as a string and not have to getBody() then echo. Getting the body of an HttpMessage object is essentially exposing the implementation details of the message. A detail that needn't be known by the client code.
I should have been more thorough with my example. I was not meaning to only represent the body content. I used the word "Response" bc those objects represent HTTP response messages.

 So for instance an implementation could look like like this:

> class StreamedHttpResponse
> {
>     private $resource;
       private $headers;
>
>     public function __construct($headers, $resource)
>     {
            $this->headers = $headers;
>          $this->resource = $resource;
>     }
>
>     public function __toString()
>     {
            // Loop headers here and send

            // now send the rest of the message (aka body)

>          stream_copy_to_stream($this->resource, 'php://output');
>          return ''; // empty string since our stream is sent straight to
> php://output
>     }
> }
- The above does not allow for content _aggregation_ or _traversal_
*by* *default*, which is part of why the StreamableInterface exists.

I'm not sure why the above example would not allow such a thing. You can still use streams as an implementation of the http message object. Perhaps one of us is misunderstanding?
 
Sure, YOU may never have needed to send large files or buffered
content back via PHP, but neither are uncommon - and they're a big
part of the reason for modelling the body content as a stream instead
of as a string. As noted in earlier threads, without this built-in to
the interfaces, you end up resorting to hacks whenever you want to
enable these features. (We have some ugly ones in ZF2 due to this --
our own message interfaces model the content as a string.... learn
from our mistakes!)

> Also, since we're no longer varying our return type, we no longer have to
> concern ourselves with creating a StreamableInterface to go along with our
> HttpMessageInterface PSR which is undoubtedly scope creep.

"Undoubtedly" you did not read the meta document, as everything you've
said here about streams has been said before.

I have read the meta document—several times. I'm not disparaging or dismissing the use of streams whatsoever. I'm merely suggesting an alternative of having them just be an implementation detail. If returning a stream from getBody() is part of the interface then it forces implementors to use streams even in the simplest of cases when they are not necessary. However with my approach (i.e. getting rid of a getBody() method and just treating the entire message as a string—since that's all an HTTP message is according to the actual HTTP spec) then it gives implementors freedom to either use streams or not use streams. It also provides a simpler interface.


Sorry if I'm turning ugly - but please, folks, do EVERYONE a favor,
and read the meta documents for proposals before starting threads --
better yet, read back through the group archives, too.

I have also read through the archive threads, and I have not seen what I am suggesting here said in any of them. If you can provide a reference then I stand corrected.
 

--
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/G4V8HClc4fI/unsubscribe.
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.

Evert Pot

unread,
Oct 23, 2014, 2:24:36 PM10/23/14
to php...@googlegroups.com
> since that's all an HTTP message is according to the actual HTTP spec) then it gives implementors freedom to
> either use streams or not use streams. It also provides a simpler interface.

Yes, the  *entire* http message is represented as a string, or more specifically, a series of bytes.
So is literally every other protocol on the planet, or anything represented in memory is expressed as a series of bytes.

The fact that these things are sent back and forward as a series of bytes, does not imply that the best way to interact with the series of bytes, is to cast it as as tring. If that were true, why would be create a class at all? We can just send strings back and forward.

However, you are not just exposing the entire HTTP message when casting to a string, you are ignoring headers, protocol version, methods, the uri, status codes and everything else. You may not see the importance of this, but all this information is not just 'meta-data', they are an integral part of the HTTP message.

You are also ignoring the fact that we must be able to represent a http message as a stream, which also implies that casting the same http message to a string twice, will not yield the same results (you cannot read every stream more than once).

In general, I would always want to suggest avoiding __toString(). It's non-obvious, and it doesn't allow you to correctly do error handling.

However, in the cases where __toString() is used, I think it's  good design to only use it where the *entire* object can be represented as a string. What you are proposing, is to just return the body, ignoring all the other data, _and_ ignoring features that are important in real use-cases such as streams.

Evert

Matthew Weier O'Phinney

unread,
Oct 23, 2014, 3:09:41 PM10/23/14
to php...@googlegroups.com
One more clarification here: When writing PHP-based HTTP clients
(which is what the original post in the thread was clarified to
indicate), being able to represent the entire HTTP message as a string
has limited utility anyways.

- If using PHP's native stream layer to make an HTTP request, the
method, URL, headers, and body are all added to the stream using
different mechanisms.
- If using ext/http or ext/http2, you will add the various elements separately.
- Using cURL... they're added separately.

Essentially, my point is that "(string) $message" won't work even to
provide you with a message to send; you're still going to need the
various message PARTS, which is what the interfaces are modelling.
Since the body can already be cast to a string, the argument made is
largely moot.

When you get a response back, you will typically want to introspect
the status code (did it succeed or not?), potentially one or more
headers (primarily to determine how to deserialize the message body),
and then the body. The body itself you will either process as a
stream... or cast as a string. Which the interfaces already allow for.

Essentially, I'm not seeing any arguments that would require changes
in the interfaces as they currently stand (though I have some notes
from threads in the past few days for other potential changes).
Reply all
Reply to author
Forward
0 new messages