Re: [PHP-DEV] RFC: Server-Side Request and Response Objects (v2)

This is only part of a thread. view whole thread
  108591
February 15, 2020 08:01 larry@garfieldtech.com ("Larry Garfield")
On Fri, Feb 14, 2020, at 10:47 AM, Benjamin Morel wrote:
> > > > What about $query and $body? That would be closer to the terminology > > used in HTTP RFCs. > > > > The problem is that $body is typically used to get the raw message body as > a string or stream. > I was thinking more something along the lines of $bodyParams, which is more > verbose but leaves no ambiguity: *$queryParams* and *$bodyParams*. > > — Benjamin
Data point: In PSR-7, the names used are: - queryParams: The query string values. - parsedBody: The body of the message, converted to a meaningful value. If the request type is a form, then it MUST be equivalent to $_POST. If not, it's up to the particular implementation to determine what "parsed" means. (Eg, parsing a JSON body of a POST into some domain object, or whatever.) - The raw body is a stream called "body", or rather an OOP wrapper around a stream since PHP's native stream interface is fugly. - There's specific handling for uploadedFiles, too. cf: https://www.php-fig.org/psr/psr-7/ To the earlier point about existing implementations, while there are a myriad of older, custom implementations of abstractions around superglobals, there's only two that are not decade-old proprietary implementations: HttpFoundation and PSR-7. Those are, realistically, the only implementations that matter. Anything else would be on the same order of magnitude effort to port to one of those as to port to this proposal. In a meaningful sense, those are the only "existing competition". Both also have robust ecosystems that make leveraging them in an entirely custom app pretty straightforward. (Whatever your feelings of the technical merits of either design, that's the current state-of-the-market.) Which therefore begs the question, is this proposal intended to supplant HttpFoundation and PSR-7, or to become a common underpinning that both of them wrap, or to be a third cohabitating implementation in the ecosystem? It doesn't seem robust enough to supplant both of them entirely, there's little value to either HttpFoundation or PSR-7 to rewrite their guts to wrap this object (though it's easier for PSR-7, as an interface, for someone to write a new implementation of it than for HttpFoundation), which would mean we'd end up with a 3rd in-the-wild implementation for user space to keep track of. I am unclear how that is a market win. PDO was mentioned previously as a model. Yes, there were many user-space implementations prior to PDO. PDO effectively supplanted and obsoleted them. However... PDO was also never thought-through enough or robust enough to be used directly, spawning a whole new ecosystem of PDO++ libraries that people actually use (Doctrine, Eloquent, Drupal's DBTNG, Aura..sql, to name but a few). So, not the full win people were hoping for. If that pattern holds, we'd end up with... a new generation of this-RFC++ wrappers that still abstract it away yet aren't compatible with each other. That said, PDO did have the advantage of at least partially unifying disparate SQL APIs. There really aren't multiple incompatible HTTPs to abstract over the way there is for SQL backends, so the analogy is somewhat weak either way. --Larry Garfield
  108607
February 15, 2020 20:10 pmjones@pmjones.io ("Paul M. Jones")
Hi all,

> On Feb 15, 2020, at 02:01, Larry Garfield <larry@garfieldtech.com> wrote: > > ... is this proposal intended to supplant HttpFoundation and PSR-7 ... ?
This is question is answered in the RFC introduction; quoting from there: The SQLite “about” page says, “Think of SQLite not as a replacement for Oracle but as a replacement for fopen().” https://www.sqlite.org/about.html Likewise, think of this RFC not as a replacement for HttpFoundation or PSR-7, or as a model of HTTP messages, but as an object-oriented alternative to superglobals, header(), setcookie(), setrawcookie(), and so on.
> PDO was mentioned previously as a model.
I did not mention PDO as "a model". I mentioned PDO (along with other extensions) to illustrate a counter-argument to objections based on the availability and comparability of userland implementations. The counter-argument summary was: That's not to say "because PDO was allowed into core, this RFC must therefore be allowed into core" but to say "those objections alone were not a barrier to PDO, so they alone should not be a barrier to this RFC". The argument, and my counter-argument, are here: <https://externals.io/message/108436#108493> -- Paul M. Jones pmjones@pmjones.io http://paul-m-jones.com Modernizing Legacy Applications in PHP https://leanpub.com/mlaphp Solving the N+1 Problem in PHP https://leanpub.com/sn1php
  108613
February 16, 2020 01:37 rowan.collins@gmail.com (Rowan Tommins)
On 15 February 2020 20:10:30 GMT+00:00, "Paul M. Jones" <pmjones@pmjones.io> wrote:
>Hi all, > >> On Feb 15, 2020, at 02:01, Larry Garfield <larry@garfieldtech.com> >wrote: >> >> ... is this proposal intended to supplant HttpFoundation and PSR-7 >... ? > >This is question is answered in the RFC introduction
You've cut Larry's question in half there, and in doing so made it seem like a repeat, when it is not. The second half of the sentence is this:
> ...or to become a common underpinning that both of them wrap, or to be a third cohabitating implementation in the ecosystem?
I haven't seen you answer that part yet: do you expect existing userland libraries to migrate from wrapping $_GET etc to using these built-in wrappers. If so, what benefit does it bring those libraries? If not, who is its intended audience? You said previously:
> PDO did not (to my knowledge) "add capabilities which cannot exist in userland or cannot exist in a reasonably performant way".
I think this is a misjudgement, and a relevant one. PDO didn't take functionality that existed purely in userland and migrate it to an extension; it took functionality that was scattered across a dozen different vendor-specific extensions with different naming and calling conventions, and centralised it into one more-or-less consistent interface. In doing so, it made (or at least tried to make) life easier both for database vendors, who can provide a PDO driver and fit into the ecosystem, and library developers, who can use PDO as a base and have less vendor-specific code. Your other examples - date, phar, and session - took common problems that were possible to solve in userland but tricky to solve well, and provided a standard out-of-the-box implementation. We already have a unified out-of-the-box implementation for the problem "get data out of HTTP request", in the form of superglobals, so neither comparison seems apt. A better comparison might be to features which have been reimplemented multiple times, to fix fundamental problems. A recent example is __serialize, but interestingly $_GET et al are themselves the third implementation of the feature, after "register globals" and the $HTTP_* arrays. As far as I can see, the RFC mentions two things it fixes about the current implementation: - The current implementation is not OO. That's not really surprising, since PHP is not a purely OO language, and treats OO as a matter of style - to the extent of providing hybrid object-procedural APIs like date and mysqli. - The current implementation is based on global state. This is definitely something that would be good to fix, but you can do almost as much in that direction as the RFC by writing "$get=$_GET; unset($_GET);" The hard problem is that the entry point for a request is in global scope, not a main() or handleRequest() function. Introducing these objects as part of a new calling convention for PHP scripts would definitely add value, and make them a true replacement for the superglobals, but that would be a very different RFC. However well designed this extension is within itself, I think it needs a stronger description of who should use it and why. Regards, -- Rowan Tommins [IMSoP]
  108650
February 17, 2020 15:55 pmjones@pmjones.io ("Paul M. Jones")
Hi Rowan,

I apologize in advance for the length of this email. I hate reading walls-of-text, but the answers are necessarily long. I have tried to break it up into bullets where possible for easier reading.


> On Feb 15, 2020, at 19:37, Rowan Tommins collins@gmail.com> wrote: > > On 15 February 2020 20:10:30 GMT+00:00, "Paul M. Jones" <pmjones@pmjones.io> wrote: > >> Hi all, >> >>> On Feb 15, 2020, at 02:01, Larry Garfield <larry@garfieldtech.com> >>> wrote: >>> >>> ... is this proposal intended to supplant HttpFoundation and PSR-7 >>> ... ? >> >> This is question is answered in the RFC introduction > > You've cut Larry's question in half there, and in doing so made it seem like a repeat, when it is not. The second half of the sentence is this: > >> ...or to become a common underpinning that both of them wrap, or to be a third cohabitating implementation in the ecosystem? > > I haven't seen you answer that part yet: do you expect existing userland libraries to migrate from wrapping $_GET etc to using these built-in wrappers. If so, what benefit does it bring those libraries? If not, who is its intended audience?
I really did think the answers to these were obvious, or easily-inferred, but obviously I was wrong. I will attempt to expand. Q: "Do you expect existing userland libraries to migrate ... ?" A: To be clear, I don't *expect* anything from existing published library authors; however ... - I *suspect* that some will choose to ignore this extension, - that others will decorate or extend it, - and that still others may find their own work so close to this extension that they migrate over to it entirely. Further ... - I suspect that developers of in-house unpublished request/response objects may find this useful for their own purposes, - and that developers who are using $_GET, header(), etc. will be pleased to find an OO-ish system that operates much like PHP itself already does, easing their transition away from global state. Finally ... - I suspect that some *consumers* of existing libraries will feel this extension is not their preferred way of working, and continue on with whatever libraries they already use, - while other consumers of those libraries will prefer this extension in their place. On reading over this, I suppose I do have an "expectation" of library authors and their consumers: that as good engineers they will evaluate this extension in reference to their own circumstances, and choose to use it (or not use it) based on the tradeoffs they find for their situation. Q: "What benefit does it bring those libraries?" A: Whatever benefits their authors happen to see in it. For myself, and as noted by Jan Schneider and others, those benefits center around having a built-in OO-ish request/response object set that does pretty much just what PHP itself already does, and that is well-suited to our daily work, without needing to incorporate comparatively large libraries into our projects for what we consider to be relatively simple purposes -- those purposes being "reading the request inputs" and "sending the response outputs". Q: "Who is its intended audience?" A: I always thought of the "intended audience" as the much the same as for any RFC: that is, developers working on a website who want PHP to provide a reasonable set of functionality for doing so, request/response objects being part of that set in a language as closely related to the web as PHP is. (As a fun side note: for all its apparent simplicity, this is an extraordinary question. Unless I have missed something, I don't recall it being asked directly of any RFC before. A Google search against wiki.php.net and externals.io reveals a double-handful of results on the terms "intended audience" and "target audience", but the only even indirect question about it on an RFC is in relation to the JIT, here: <https://externals.io/message/103903#103995> -- and it's by you. :-) * * * Rowan, you quoted Larry asking if this extension was ...
>> to be a third cohabitating implementation in the ecosystem?
.... that is, third after HttpFoundation and PSR-7. (By way of preamble, and to reiterate what I've said before: I would prefer to discuss this RFC on its own terms. But, as you and others want some discussion in context of other projects, I will do so. My apologies if I sound negative or critical toward those projects.) It turns out the question is kind of a funny way of describing the situation, in that PSR-7 is not an implementation of anything. (Full disclosure: I was a sponsor on the PSR-7 proposal.) PSR-7 is instead a set of interfaces; that is, they "are abstractions around HTTP messages and the elements composing them." <https://www.php-fig.org/psr/psr-7/> The meta document notes, "the goal of this proposal is not to obsolete the current interfaces utilized by existing PHP libraries. This proposal is aimed at interoperability between PHP packages for the purpose of describing HTTP messages." <https://www.php-fig.org/psr/psr-7/meta/> So, to refer to PSR-7 as a "cohabiting implementation" is a bit mistaken (but a very easy mistake to make -- I imagine I have made it myself occasionally). If we are going to talk about implementations in an ecosystem, then, there are at least four for PSR-7: - Cake - Guzzle - Slim - Zend/Laminas There are others as well, with varying ranges of adoption; and, as PSR-7 is an interoperability specification, I predict there will be more implementations later. The above implementations are partially but not always fully interchangeable; while they adhere to the PSR-7 method interfaces, they each have their own behavioral idiosyncrasies. In some cases they offer additional methods and behaviors that the others do not have. (This is not a criticism, merely an observation.) Thus, if one wishes to speak in terms of "cohabiting implementations", ext/request would have to be counted as one of at least six (i.e., itself, HttpFoundation, and the above four). I don't know if that strengthens or weakens the ext/request case, but it does help to clarify the world in which it exists. * * * Rowan, you wrote:
> You said previously: > >> PDO did not (to my knowledge) "add capabilities which cannot exist in userland or cannot exist in a reasonably performant way". > > I think this is a misjudgement, and a relevant one. PDO didn't take functionality that existed purely in userland and migrate it to an extension; it took functionality that was scattered across a dozen different vendor-specific extensions with different naming and calling conventions, and centralised it into one more-or-less consistent interface. In doing so, it made (or at least tried to make) life easier both for database vendors, who can provide a PDO driver and fit into the ecosystem, and library developers, who can use PDO as a base and have less vendor-specific code.
That is one valid way of looking at it; but then, on exactly the same terms, it is likewise valid to say that AdoDB "took functionality that was scattered across a dozen different vendor-specific extensions with different naming and calling conventions, and centralised it into one more-or-less consistent interface." So did PEAR DB, Metabase, MDB, and so on. PDO did it as an extension, instead of in userland, but the goals and outcomes noted were identical. I find this more confirmatory than not regarding my earlier counter-argument; i.e., that (in hindsight) objections based on the existence and availability of userland projects would not themselves have been a barrier to PDO, and so should not themselves be a barrier to this RFC.
> Your other examples - date, phar, and session - took common problems that were possible to solve in userland but tricky to solve well, and provided a standard out-of-the-box implementation. > > We already have a unified out-of-the-box implementation for the problem "get data out of HTTP request", in the form of superglobals, so neither comparison seems apt.
Perhaps; working by analogy is always a difficult and imperfect task.
> A better comparison might be to features which have been reimplemented multiple times, to fix fundamental problems. A recent example is __serialize, but interestingly $_GET et al are themselves the third implementation of the feature, after "register globals" and the $HTTP_* arrays. > > As far as I can see, the RFC mentions two things it fixes about the current implementation: > > - The current implementation is not OO. That's not really surprising, since PHP is not a purely OO language, and treats OO as a matter of style - to the extent of providing hybrid object-procedural APIs like date and mysqli.
"As a matter of style" is a good line! In support of that line, this RFC does not seek to remove $_GET and the response-related functions, but rather to provide an additional OO-ish approach that honors the existing (as you say, the third-time's-the-charm) language-level approaches. FWIW, I had considered mentioning the dual OO+procedural APIs of mysqli, date, etc. but it seemed too much in an already-long RFC; I'm glad you brought it up, as I did have it in mind.
> - The current implementation is based on global state. This is definitely something that would be good to fix, but you can do almost as much in that direction as the RFC by writing "$get=$_GET; unset($_GET);" The hard problem is that the entry point for a request is in global scope, not a main() or handleRequest() function.
Another "hard" problem is carrying those values around in the system once they are decoupled from global state; the objects in this RFC purport to do so.
> Introducing these objects as part of a new calling convention for PHP scripts would definitely add value, and make them a true replacement for the superglobals, but that would be a very different RFC.
That does seem rather ambitious. If you feel that's a valuable thing to add to PHP, perhaps it could be part of a future RFC, maybe even one that uses the objects in this RFC as a starting point. * * * Again, my apologies for the wall-of-text. Please let me know if you find those answers are sufficient or not. -- Paul M. Jones pmjones@pmjones.io http://paul-m-jones.com Modernizing Legacy Applications in PHP https://leanpub.com/mlaphp Solving the N+1 Problem in PHP https://leanpub.com/sn1php
  108661
February 18, 2020 20:21 rowan.collins@gmail.com (Rowan Tommins)
On 17/02/2020 15:55, Paul M. Jones wrote:
> I apologize in advance for the length of this email. I hate reading walls-of-text, but the answers are necessarily long. I have tried to break it up into bullets where possible for easier reading.
No, thank you for taking the time to respond. I've cherry-picked rearranged the quotes in this reply a bit so I can try not to repeat myself too much; let me know if I've taken your words too far out of context.
> ...it is likewise valid to say that AdoDB "took functionality that was scattered across a dozen different vendor-specific extensions with different naming and calling conventions, and centralised it into one more-or-less consistent interface." So did PEAR DB, Metabase, MDB, and so on. PDO did it as an extension, instead of in userland, but the goals and outcomes noted were identical.
That's a fair point, everything PDO does could be and has been done in userland. There is one thing that couldn't be done outside of an extension, though, which is that PDO doesn't rely on the legacy extensions, it (in theory at least) replaces them. What I think is slightly more relevant, is that PDO makes writing wrappers like ADODB easier, because it contains non-trivial functionality that those wrappers would otherwise have to write.
> I don't *expect* anything from existing published library authors > ... > I always thought of the "intended audience" as the much the same as > for any RFC
I think perhaps my choice of words has caused a bit of confusion. Perhaps "hope" would have been better than "expect", and "use cases" better than "intended audience". I was trying to draw out two pieces of information: - What's the sales pitch - what do YOU think is great about these classes? - When working out the details, what code should we be picturing using the new classes? I wasn't around when PDO was proposed, but an imaginary "sales pitch" might have gone something like this: - Developers should be able to implement multiple database systems without learning the quirks of each vendor's extension - Library authors shouldn't need to implement the basic functionality of every driver from scratch when the vendor could do it for them - Vendors shouldn't need to decide which API to follow, when we can normalise everything internally - We can offer different result formats and error-handling models out of the box ....and so on
> - some [library authors] may find their own work so close to this extension that they migrate over to it entirely.
So, there is at least some hope that this will entirely replace some people's existing libraries, even if it doesn't replace the more powerful ones like HttpFoundation. That's probably reasonable. (Note that I've been thinking of "library" fairly loosely - a single class used in a completely private monolithic repo, but which is written to be generic functionality, has much the same role as a public composer package in this case.)
> For myself, and as noted by Jan Schneider and others, those benefits center around...
This is what I was looking for. Sell it to me! :)
> ...a built-in OO-ish request/response object set...
"OO-ish" is a wise choice of words here. One of the reasons I'm not terribly keen on this proposal - particularly the request half - is that I'm a bit of an OO purist. By that I mean that I prefer objects that have a strong responsibility, and encapsulate useful behaviour, rather than just spitting out what went in. The response part of the proposal is closer to "real" OO, IMO, because it includes behaviour like manipulating individual headers and cookies. The lack of behaviour also makes it less useful to people writing their own request and response objects: if I have a copy of $_POST and want to put it in my own object property, why would I first pass it to a ServerRequest object, and then get it straight back out again, if the object isn't helping me do anything with that data? That said, I know some people are happy with OO-ish code, some even to the extent of preferring stdClass to an array. So it's not unreasonable to say that this will appeal to developers who are less puritanical about objects than me.
> ...that does pretty much just what PHP itself already does...
As you say elsewhere, this is useful for helping people migrate to it. The flipside of that is that it ties us into past decisions, rather than evaluating whether those decisions are still what we want. The approach in providing both $files and $uploads arrays is a good compromise - provide the new better way, but also an easy-to-migrate way. I'd love to see them named more distinctly, though, maybe even calling one of them "legacy". I'd probably also make them methods so that the data can be stored once (in the new format) and re-formatted on demand (again, objects as behaviour rather than data).
> ...easing their transition away from global state
This I find less convincing. To quote from further down the e-mail, I wrote:
>> ...you can do almost as much in that direction as the RFC by writing "$get=$_GET; unset($_GET);" The hard problem is that the entry point for a request is in global scope, not a main() or handleRequest() function.
and you replied:
> Another "hard" problem is carrying those values around in the system once they are decoupled from global state; the objects in this RFC purport to do so.
If all the object does is "carry around" a copy of the superglobal arrays, I can write it in about a dozen lines of code. Admittedly, the implementation wouldn't match anyone else's, but if I wanted interoperability, I would need to follow a standard like PSR-7, or match a popular existing library; this does neither. Again, the response object is more powerful in this regard, because we can't currently pass around a list of prepared cookies and trivially output it to the client. Along the lines of my previous message about decoupling concerns, I would personally like the response parts of the proposal split out and re-cast as a kind of "response buffer". That could be useful directly, and it could provide useful functionality to people implementing a full PSR-7 or similar Response object.
> I had considered mentioning the dual OO+procedural APIs of mysqli, date, etc. but it seemed too much in an already-long RFC; I'm glad you brought it up, as I did have it in mind.
This is an interesting comparison to consider. Just as those extensions have procedural options for people who can't stand objects, the current proposal has objects for people who like "OO-ish".
>> Introducing these objects as part of a new calling convention for PHP scripts would definitely add value, and make them a true replacement for the superglobals, but that would be a very different RFC. > That does seem rather ambitious. If you feel that's a valuable thing to add to PHP, perhaps it could be part of a future RFC, maybe even one that uses the objects in this RFC as a starting point.
One of the things I don't like about the current proposal is that it's modelled so closely around the current superglobals that I fear it would actually be *harder* to replace them. I find this constraint particularly awkward:
> N.b.: It is up to you to make sure the various content-related header > values in |$GLOBALS| match the custom |$content| string.
Parsing a request body from an arbitrary source into arrays that match the structure of $_POST and $_FILES would be a really useful feature. It would be something userland libraries could make use of right now, and would fit in nicely with a future where those arrays aren't populated by default. I also really want to see the day when I never have to interact with $_SERVER again. Other than renaming it, this object's design makes that less likely, not more. Imagine if in future we're able to redesign the internals so that there is a dedicated and reliable field for the requested URL; the current object doesn't have anywhere we can put that. If we add it later, it will be too late, code will be written that passes around ServerRequest objects, but relies on the full array of CGI variables to reconstruct the URL. If this object took a more opinionated view of what behaviour to encapsulate, we could simply hide the "server" array completely. Common use cases would be exposed via methods, and rarer use cases would have to be added in userland with their own copy of $_SERVER. Then my dream of deprecating $_SERVER could mean something other than moving it into an object. I hope I haven't left any unfinished sentences as I went back and forth through this! And thanks again for taking the time to engage with me. Regards, -- Rowan Tommins (né Collins) [IMSoP]
  108689
February 19, 2020 20:21 pmjones@pmjones.io ("Paul M. Jones")
Hi Rowan,

Again, thanks for your time and effort in evaluating this proposal.


> On Feb 18, 2020, at 14:21, Rowan Tommins collins@gmail.com> wrote: > > - What's the sales pitch - what do YOU think is great about these classes? > > ... > > Sell it to me! :)
I don't do sales; occasionally I can present a good narrative. Maybe the narrative below will help.
> - When working out the details, what code should we be picturing using the new classes?
Not to be flippant, but: request-reading and response-writing code?
>> ...a built-in OO-ish request/response object set... > > "OO-ish" is a wise choice of words here. One of the reasons I'm not terribly keen on this proposal - particularly the request half - is that I'm a bit of an OO purist.
Heh -- yes, I am aware of the impurities (both perceived and actual) of ServerRequest. ;-) However ...
> The lack of behaviour also makes it less useful to people writing their own request and response objects: if I have a copy of $_POST and want to put it in my own object property, why would I first pass it to a ServerRequest object, and then get it straight back out again, if the object isn't helping me do anything with that data?
.... in this case, ServerRequest does quite a bit that is easy to take for granted, since it does so quickly and quietly. Among other things: 1. It "bundles" several related pieces of data; not just $_POST but $_GET, $_COOKIES, etc., so they can be carried around together. 2. It separates that data from global state. 3. It parses some of that data into commonly-needed structures (eg. the `$accept*` arrays). 4. Its copies of that data are read-only and immutable, so that once set they cannot be modified at a distance when shared around; this keeps them stable throughout the system. So while it is true that ServerRequest "lacks behavior" in the sense that it doesn't "do" anything *after* construction, but it also true that it does quite a bit *at* construction. Indeed, the vast majority of uses (in practice) for a ServerRequest type of object are "reading" or "getter" uses. There is comparatively little need to write to a ServerRequest instance. Almost all of the time, all you need is to read from it post-construction. Further, there's no need to optimize for calculation-on-demand, since the calculations are so speedy in the first place. And since it will happen only once in the request lifespan, you might as well build all the properties at construction-time. At that point, getter-type methods are just returning what has already been calculated. And at *that* point, read-only properties do the trick quite nicely. For the comparatively-rare (but still necessary) times when you need to write to a ServerRequest type of object, it is open to extension in userland for those application-specific cases.
>> ...that does pretty much just what PHP itself already does... > > As you say elsewhere, this is useful for helping people migrate to it. The flipside of that is that it ties us into past decisions, rather than evaluating whether those decisions are still what we want.
Your comment jogged my memory of an older conversation, <https://externals.io/message/100087>, in which you figure prominently, and in which I pointed out the existence of the prior version of this RFC. Many things there are reminiscent here: - Should superglobals be made read-only? - Should superglobals be made into objects instead of arrays? - Should superglobals be replaced entirely with some new system? - Are additional superglobals needed? - Should there be a layer of indirection between superglobals and their use in applications? This RFC answers from (if you'll pardon the word) a conservative position: no, no, no, maybe, and yes. In the last case, this RFC can be construed to provide ServerRequest as that layer of indirection for some of the superglobals.
> The approach in providing both $files and $uploads arrays is a good compromise - provide the new better way, but also an easy-to-migrate way. I'd love to see them named more distinctly, though, maybe even calling one of them "legacy". I'd probably also make them methods so that the data can be stored once (in the new format) and re-formatted on demand (again, objects as behaviour rather than data).
I appreciate that concession on your part, and thanks for saying. I think $uploads is a pretty distinct name, while being clearly related to files. And, as I mentioned earlier, the cost of reformatting the $_FILES structure to $uploads is so low that it might as well occur at construction time, instead of adding a method to calculate-and-retain the reformatted structure.
>> ...easing their transition away from global state > > This I find less convincing. To quote from further down the e-mail, I wrote: > >>> ...you can do almost as much in that direction as the RFC by writing "$get=$_GET; unset($_GET);" The hard problem is that the entry point for a request is in global scope, not a main() or handleRequest() function. > > and you replied: > >> Another "hard" problem is carrying those values around in the system once they are decoupled from global state; the objects in this RFC purport to do so. > > If all the object does is "carry around" a copy of the superglobal arrays, I can write it in about a dozen lines of code.
I answered this above, but to reiterate: "carrying around" is *one* thing ServerRequest does, but not *all* it does.
> Again, the response object is more powerful in this regard, because we can't currently pass around a list of prepared cookies and trivially output it to the client. > > Along the lines of my previous message about decoupling concerns, I would personally like the response parts of the proposal split out and re-cast as a kind of "response buffer".
I admit I considered this. However, it makes more sense to me in terms of symmetry/complementarity, and in terms of "what we need on a daily basis", to provide both the request object and the response object together.
>> I had considered mentioning the dual OO+procedural APIs of mysqli, date, etc. but it seemed too much in an already-long RFC; I'm glad you brought it up, as I did have it in mind. > > This is an interesting comparison to consider. Just as those extensions have procedural options for people who can't stand objects, the current proposal has objects for people who like "OO-ish".
Yes, I think so too.
>>> Introducing these objects as part of a new calling convention for PHP scripts would definitely add value, and make them a true replacement for the superglobals, but that would be a very different RFC. >> >> That does seem rather ambitious. If you feel that's a valuable thing to add to PHP, perhaps it could be part of a future RFC, maybe even one that uses the objects in this RFC as a starting point. > > One of the things I don't like about the current proposal is that it's modelled so closely around the current superglobals that I fear it would actually be *harder* to replace them.
Maybe? I can similarly imagine that if new-and-different superglobals appear, the ServerRequest object can evolve to contain and/or translate between them.
> Parsing a request body from an arbitrary source into arrays that match the structure of $_POST and $_FILES would be a really useful feature.
Yes, although one can do at least the $_POST portion with ServerRequest as it is now. For example, a naive userland factory might do this: class ServerRequestFactory { public function new(array $globals, ?string $content = null) : ServerRequest { if ($this->isJson($globals['_SERVER']['CONTENT_TYPE'] ?? '') { $globals['_POST'] = json_decode( $content ?? file_get_contents('php://input'), true ); } return new ServerRequest($globals, $content); } protected function isJson(string $contentType) : bool { return $contentType === 'application/json' || substr($contentType, -5) === '+json'; } } Call `$request = (new ServerRequestFactory())->new();` and voila: the equivalent of `$_POST`, populated from JSON content, stored as $request->input.
> I also really want to see the day when I never have to interact with $_SERVER again. Other than renaming it, this object's design makes that less likely, not more. > > Imagine if in future we're able to redesign the internals so that there is a dedicated and reliable field for the requested URL; the current object doesn't have anywhere we can put that. If we add it later, it will be too late, code will be written that passes around ServerRequest objects, but relies on the full array of CGI variables to reconstruct the URL. > > If this object took a more opinionated view of what behaviour to encapsulate, we could simply hide the "server" array completely. Common use cases would be exposed via methods, and rarer use cases would have to be added in userland with their own copy of $_SERVER. Then my dream of deprecating $_SERVER could mean something other than moving it into an object.
Yes, the internals thread I pointed to earlier makes that clear. And though I understand where you're coming from, especially regarding "a dedicated and reliable field for the requested URL", this RFC does not have that kind of ambition, nor do I expect it to in the future. This proposal is satisfied to take only a smaller series of steps to ease a more limited set of problems, rather than revise major and wide-ranging elements of PHP overall. I presume you will find that unsatisfactory, but, well, there it is. -- Paul M. Jones pmjones@pmjones.io http://paul-m-jones.com Modernizing Legacy Applications in PHP https://leanpub.com/mlaphp Solving the N+1 Problem in PHP https://leanpub.com/sn1php
  108691
February 19, 2020 22:43 rowan.collins@gmail.com (Rowan Tommins)
On 19/02/2020 20:21, Paul M. Jones wrote:
>> Sell it to me! :) > I don't do sales; occasionally I can present a good narrative. Maybe the narrative below will help.
Hah! Fair enough. :)
>> - When working out the details, what code should we be picturing using the new classes? > Not to be flippant, but: request-reading and response-writing code?
The reason I keep trying to pin you down on this is that I am concerned about the "jack of all trades" effect - that a feature which lacks focus can end up being less useful than if it was refined for one job. From the point of view of a reviewer, it's also hard to answer the question "do you think this is a good design?" when you don't know what it's the design for. To take a specific example, if an aim is for this class to be used as an underpinning for higher-level libraries, we could look at some existing examples. We can think about what new functionality they would gain, or what old code they could delete, by using this class. We can also look at whether the interface as currently proposed would be "comfortable" to integrate, or if there are changes that would make it easier. That doesn't mean you have to declare there to be one single goal, but it's easier to get specific about a design fitting a goal than making an abstract judgement about it.
> >> The lack of behaviour also makes it less useful to people writing their own request and response objects > 1. It "bundles" several related pieces of data; not just $_POST but $_GET, $_COOKIES, etc., so they can be carried around together. > > 2. It separates that data from global state. > > 4. Its copies of that data are read-only and immutable, so that once set they cannot be modified at a distance when shared around; this keeps them stable throughout the system.
You see, here's an example where specifying the goal matters. In that particular comment, I specifically talked about the use case of wrapping a larger object around this one. The boilerplate to wrap this object in a different interface is probably *larger* than the boilerplate to wrap the superglobals directly, so *for that use case* these three features are not relevant.
> 3. It parses some of that data into commonly-needed structures (eg. the `$accept*` arrays).
This, however, is the kind of thing I mean by "behaviour", and the kind of thing I'd like to see more of. Going back to the goal of "libraries could wrap this object and extend it", we can directly see how it would make their lives easier because they wouldn't have to reimplement it. The particular selection of fields feels rather arbitrary, though - for instance, I've never heard of "Content-MD5", but would have expected at least some URL-related properties, like Host.
> Further, there's no need to optimize for calculation-on-demand, since the calculations are so speedy in the first place. And since it will happen only once in the request lifespan, you might as well build all the properties at construction-time. At that point, getter-type methods are just returning what has already been calculated. And at *that* point, read-only properties do the trick quite nicely.
My preference for methods over properties is partly just OO purism, but I do think they have real benefits. One of them is the ability to evolve the implementation over time. For instance, the current design has two arrays of uploaded files, one in $_FILES format, and one in a newer format. What if in future, we want to add a third, where each item is an object? With properties, we'd have to pre-populate all three arrays on construct; but with methods, we could refactor the internals to pull all three from some common reference point. Similarly, methods can have parameters, and that can be a great way of introducing opt-in behaviour. For instance, maybe in future someone requests that getMethod() should have a $allow_override flag, which set to false would ignore the X-HTTP-METHOD-OVERRIDE header. With properties, you need a separate property for every possible combination, and you have to come up with names for them all.
> I think $uploads is a pretty distinct name, while being clearly related to files.
Perhaps "descriptive" would be a better word than "distinct". I can tell $files and $uploads apart at a glance, but the names tell me nothing about why both exist, or which I should be using.
>>> Another "hard" problem is carrying those values around in the system once they are decoupled from global state; the objects in this RFC purport to do so. > I answered this above, but to reiterate: "carrying around" is *one* thing ServerRequest does, but not *all* it does.
I'm not disputing that; I'm disputing that that particular feature is in any way a hard problem.
> I admit I considered this. However, it makes more sense to me in terms of symmetry/complementarity, and in terms of "what we need on a daily basis", to provide both the request object and the response object together.
One oddity of the proposal is that the two objects aren't actually very symmetrical.  For instance, to copy a particular header from a request to a response, I'd read a plain array $request->headers[$name], but write via a method $response->setHeader($name, $value). (It's also not clear whether this would work correctly if the header had multiple values in the request.)
> Maybe? I can similarly imagine that if new-and-different superglobals > appear, the ServerRequest object can evolve to contain and/or > translate between them.
Although we can't predict the future, there are things we can do to make it more likely we could adapt to such a change. We should make a conscious decision whether this is a goal, or something we're happy not to focus on.
>> Parsing a request body from an arbitrary source into arrays that match the structure of $_POST and $_FILES would be a really useful feature. > Yes, although one can do at least the $_POST portion with ServerRequest as it is now. > ... > Call `$request = (new ServerRequestFactory())->new();` and voila: the equivalent of `$_POST`, populated from JSON content, stored as $request->input.
I was actually thinking of the opposite: given a request body which didn't come from global state, but which contains data in multipart/form-data format, extract the key-value pairs and attached files. This is a non-trivial piece of functionality, which would have real-world uses; here it is implemented in ReactPHP: https://github.com/reactphp/http/blob/121abe0558465cc7f2cecdb3027dae959f348409/src/Io/MultipartParser.php Rather than accepting the content body as an optional constructor parameter, what if there were two named constructors: - fromSuperGlobalArrays($server, $cookie, $post, $get, $files) - fromRawRequestBody($server, $cookie, $body) In the first case, accessing the raw body on the object could give null, because none is known - defaulting to global state seems like a mistake if decoupling from global state is an explicit aim. That said, perhaps a third constructor could be a short-hand for just that: fromCurrentRequest, which would be equivalent to fromRawRequestBody($_SERVER, $_COOKIE, file_get_contents('php://input'));
> Yes, the internals thread I pointed to earlier makes that clear. And > though I understand where you're coming from, especially regarding "a > dedicated and reliable field for the requested URL", this RFC does not > have that kind of ambition, nor do I expect it to in the future.
I agree that re-designing the way web servers pass in the URL is a much harder problem, but if you look at pretty much any existing Request wrapper, it will make some attempt to extract a URL from the $_SERVER array. That really feels like a missing feature of this one right now. (I appreciate it's not simple, but that's why it's so useful to have someone else write it!) I hope my comments aren't coming across as too negative. Maybe our visions for what this should be are just too different, but some mixture of hope and stubbornness makes me want to keep nudging it somewhere "better". Regards, -- Rowan Tommins (né Collins) [IMSoP]
  108701
February 20, 2020 14:56 pmjones@pmjones.io ("Paul M. Jones")
Hi Rowan,

> On Feb 19, 2020, at 16:43, Rowan Tommins collins@gmail.com> wrote: > >>> - When working out the details, what code should we be picturing using the new classes? >> >> Not to be flippant, but: request-reading and response-writing code? > > The reason I keep trying to pin you down on this is that I am concerned about the "jack of all trades" effect - that a feature which lacks focus can end up being less useful than if it was refined for one job. From the point of view of a reviewer, it's also hard to answer the question "do you think this is a good design?" when you don't know what it's the design for. > > ... it's easier to get specific about a design fitting a goal than making an abstract judgement about it.
The purpose of the extension is as stated in the RFC: that is, to provide "an object-oriented approach around request and response functionality already existing in PHP ... an object-oriented alternative to superglobals, header(), setcookie(), setrawcookie(), and so on." At this point, I get the impression that either one of use is underthinking things, or the other one is overthinking them -- or perhaps we just have different premises and visions.
> The particular selection of fields feels rather arbitrary, though - for instance, I've never heard of "Content-MD5",
That's fair; once some of the content-related header fields came into play, it seemed reasonable to bring all of them in.
> but would have expected at least some URL-related properties, like Host.
Aha! If nothing else, then, this conversation has revealed a documentation flaw: specifically, my failure to document the ServerRequest $url property. That failure is now remedied: <https://github.com/pmjones/ext-request#the-url-array>
> Similarly, methods can have parameters, and that can be a great way of introducing opt-in behaviour. For instance, maybe in future someone requests that getMethod() should have a $allow_override flag, which set to false would ignore the X-HTTP-METHOD-OVERRIDE header. With properties, you need a separate property for every possible combination, and you have to come up with names for them all.
I get the need for future extension, which is why the method space is left open for consumers. If they want to provide their own getter methods, calculating from the existing properties or other values, they are in the clear to do so.
>> I think $uploads is a pretty distinct name, while being clearly related to files. > > Perhaps "descriptive" would be a better word than "distinct". I can tell $files and $uploads apart at a glance, but the names tell me nothing about why both exist, or which I should be using.
Which is the purpose of the documentation; it describes the differences between $files and $uploads.
>>>> Another "hard" problem is carrying those values around in the system once they are decoupled from global state; the objects in this RFC purport to do so. >> I answered this above, but to reiterate: "carrying around" is *one* thing ServerRequest does, but not *all* it does. > I'm not disputing that; I'm disputing that that particular feature is in any way a hard problem.
Your disputation is noted.
>> I admit I considered this. However, it makes more sense to me in terms of symmetry/complementarity, and in terms of "what we need on a daily basis", to provide both the request object and the response object together. > > One oddity of the proposal is that the two objects aren't actually very symmetrical.
That's fair; strike "symmetry" and retain "complementarity."
>> Maybe? I can similarly imagine that if new-and-different superglobals appear, the ServerRequest object can evolve to contain and/or translate between them. > > Although we can't predict the future, there are things we can do to make it more likely we could adapt to such a change. We should make a conscious decision whether this is a goal, or something we're happy not to focus on.
The RFC states, under Future Scope, "This extension acts as an object-oriented wrapper around existing PHP request and response functionality; as the scope of that PHP functionality expands, this extension should expand with it."
>>> Parsing a request body from an arbitrary source into arrays that match the structure of $_POST and $_FILES would be a really useful feature. >> Yes, although one can do at least the $_POST portion with ServerRequest as it is now. >> ... >> Call `$request = (new ServerRequestFactory())->new();` and voila: the equivalent of `$_POST`, populated from JSON content, stored as $request->input. > > I was actually thinking of the opposite: given a request body which didn't come from global state, but which contains data in multipart/form-data format, extract the key-value pairs and attached files.
Is that something PHP "itself" already does? If not, I have to consider it out-of-scope for this RFC.
> Rather than accepting the content body as an optional constructor parameter, what if there were two named constructors: > > - fromSuperGlobalArrays($server, $cookie, $post, $get, $files) > - fromRawRequestBody($server, $cookie, $body)
If consumers/users of ext-request wish to create factories or builders to instantiate a ServerRequest instance, it is within their purview to do so.
> In the first case, accessing the raw body on the object could give null, because none is known - defaulting to global state seems like a mistake if decoupling from global state is an explicit aim.
Your point on global state is well-taken; I will try to remember in future to phrase it as "global *mutable* state." (AFAICT, php://input is not mutable, though as you correctly point out, it is global.)
> if you look at pretty much any existing Request wrapper, it will make some attempt to extract a URL from the $_SERVER array. That really feels like a missing feature of this one right now.
Yeah, my bad on not documenting it earlier -- please consider the "missing" feature "found." ;-)
> I hope my comments aren't coming across as too negative.
Not "negative", but as you say ...
> Maybe our visions for what this should be are just too different
.... I think your goals are loftier here than proposal's. -- Paul M. Jones pmjones@pmjones.io http://paul-m-jones.com Modernizing Legacy Applications in PHP https://leanpub.com/mlaphp Solving the N+1 Problem in PHP https://leanpub.com/sn1php
  108618
February 16, 2020 11:49 mike@newclarity.net (Mike Schinkel)
> On Feb 15, 2020, at 3:01 AM, Larry Garfield <larry@garfieldtech.com> wrote: > > Data point: > > In PSR-7, the names used are: > > - queryParams: The query string values. > - parsedBody: The body of the message, converted to a meaningful value. If the request type is a form, then it MUST be equivalent to $_POST. If not, it's up to the particular implementation to determine what "parsed" means. (Eg, parsing a JSON body of a POST into some domain object, or whatever.) > - The raw body is a stream called "body", or rather an OOP wrapper around a stream since PHP's native stream interface is fugly. > - There's specific handling for uploadedFiles, too. > > cf: https://www.php-fig.org/psr/psr-7/ > > To the earlier point about existing implementations, while there are a myriad of older, custom implementations of abstractions around superglobals, there's only two that are not decade-old proprietary implementations: HttpFoundation and PSR-7. Those are, realistically, the only implementations that matter. Anything else would be on the same order of magnitude effort to port to one of those as to port to this proposal. In a meaningful sense, those are the only "existing competition". Both also have robust ecosystems that make leveraging them in an entirely custom app pretty straightforward. > > (Whatever your feelings of the technical merits of either design, that's the current state-of-the-market.) > > Which therefore begs the question, is this proposal intended to supplant HttpFoundation and PSR-7, or to become a common underpinning that both of them wrap, or to be a third cohabitating implementation in the ecosystem? > > It doesn't seem robust enough to supplant both of them entirely, there's little value to either HttpFoundation or PSR-7 to rewrite their guts to wrap this object (though it's easier for PSR-7, as an interface, for someone to write a new implementation of it than for HttpFoundation), which would mean we'd end up with a 3rd in-the-wild implementation for user space to keep track of. > > I am unclear how that is a market win.
The win is it allows developer to provide a simple to use object oriented interface *in core*. IOW, without having to bring in an external PHP-code implementation that may or may not break in the future. Another big win would be to allow developers to deprecate use of superglobals in their own apps. However Paul do not want to add an ini setting to disable the use of superglobals. But how could disabling superglobals be done in HttpFoundation or PSR-7? Maybe Paul will reconsider adding such an capability to his RFC because it is something we cannot get with HttpFoundation and PSR-7. Or maybe we should be looking at is a core implementation of PSR-7 instead; one that would allow us to disable access to the superglobals? One that people could subclass, of course. If we did that, we might hash out why some developers do not use PSR-7 and possibly fix its (perceived?) faults in a new PSR to amend PSR-7. -Mike