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

This is only part of a thread. view whole thread
  108584
February 14, 2020 16:44 weirdan@gmail.com (Bruce Weirdan)
On Fri, Feb 14, 2020 at 5:47 PM Paul M. Jones <pmjones@pmjones.io> wrote:
>
> - rename $get to $query, populating it from `$globals['_GET']`, on the basis stated above > - rename $post to $input, populating it from `$globals['_POST']`, on the basis that it typically relates to the parsed form of php://input
What about $query and $body? That would be closer to the terminology used in HTTP RFCs. -- Best regards, Bruce Weirdan mailto:weirdan@gmail.com
  108585
February 14, 2020 16:47 benjamin.morel@gmail.com (Benjamin Morel)
> > 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
  108589
February 14, 2020 22:12 pmjones@pmjones.io ("Paul M. Jones")
Hi all,

> On Feb 14, 2020, at 10:47, Benjamin Morel morel@gmail.com> 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*.
I get the desire to disambiguate. But as an added consideration, there's a desire for consistency; when adding a -Params suffix to those names, it might then make sense to have $serverParams, $cookieParams, etc. Looking at it that way, I don't think a -Params suffix is necessary. I would think $query would be enough. As for the other name, the one for the $_POST equivalent, $body doesn't seem quite right to me; it seems a little close to $content. I've also been thinking about $values, $params, $parsedContent, $contentValues, $bodyValues, $contentArray, and other variations with and without prefixes and suffixes, but $input is the one that feels like the least-terrible alternative to $post for me, esp. given the connection to php://input. -- 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
  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
  108746
February 24, 2020 21:47 rowan.collins@gmail.com (Rowan Tommins)
Hi Paul,

I left this thread to settle a bit, because I felt we were going round 
in circles a bit. I think there's some fundamental differences in 
outlook that no amount of discussion is going to resolve, so I've 
trimmed this reply to the more specific points.



On 20/02/2020 14:56, Paul M. Jones wrote:
> 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>
Aha indeed! When I was talking about "selling it to me", this is exactly the kind of thing I was looking for. This kind of functionality is much more interesting to me than copying half a dozen arrays from constructor parameters into read-only properties.
> Which is the purpose of the documentation; it describes the differences between $files and $uploads.
That's no reason not to *try* for descriptive names, though. I presume the idea is that one array is expected to be more useful for new code, and the other is mostly there for compatibility with old code? If so, perhaps the names could reflect that somehow. Incidentally, the current documentation doesn't describe the differences particularly well, just saying one is "more like $_POST". Some details of the structure, or examples comparing the two arrays, would be useful.
>> 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.
a) Yes: Every time you submit a form as multipart/form-data, PHP parses it into the global state. If this object is aiming to abstract away from global state, then having a non-global-state parser for that seems consistent. b) No: There is no function which currently takes a string and returns an array for this scenario. However, that's true of other features you have included, such as parsing accept headers, or even extracting just HTTP headers from a copy of the $_SERVER array.
> 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.)
This distinction seems unnecessary. Once created, the object avoids global state because it is self-contained, and the fact that it's read-only is a separate attribute. We're really just talking about ways to construct that self-contained state, and "everything from global state" or "nothing from global state" seem like more natural options than "one thing from global state, everything else not". Regards, -- Rowan Tommins (né Collins) [IMSoP]
  108771
February 26, 2020 16:42 pmjones@pmjones.io ("Paul M. Jones")
Hi Rowan,

> On Feb 24, 2020, at 15:47, Rowan Tommins collins@gmail.com> wrote: > > Hi Paul, > > I left this thread to settle a bit, because I felt we were going round in circles a bit. I think there's some fundamental differences in outlook that no amount of discussion is going to resolve, so I've trimmed this reply to the more specific points.
I appreciate you taking the trouble; thanks.
>> Which is the purpose of the documentation; it describes the differences between $files and $uploads. > > That's no reason not to *try* for descriptive names, though. I presume the idea is that one array is expected to be more useful for new code, and the other is mostly there for compatibility with old code? If so, perhaps the names could reflect that somehow.
Your presumption is correct! And your point on trying for better names is well-taken -- though I think these are "expected" names, based on my research into existing implementations. The most-common names are ... - the word "files" for unparsed or unmodified $_FILES values, leading me to think $files is well-understood - the words "upload(s)", "fileUpload(s)", or "uploadedFile(s)" for parsed, transformed, or restructured $_FILES values, leading me to think $uploads is well-understood And the current users of the 1.x version have not reported confusion or trouble with the names as they are. Having said that, I am open to suggestion here. What names do you think would be better than the ones presented, contra pre-existing work from other authors?
> Incidentally, the current documentation doesn't describe the differences particularly well, just saying one is "more like $_POST". Some details of the structure, or examples comparing the two arrays, would be useful.
Good call! On your suggestion, I have added details at <https://github.com/pmjones/ext-request#the-uploads-array>. Suggestions for improvement are welcome.
>>> 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. > > > a) Yes: Every time you submit a form as multipart/form-data, PHP parses it into the global state. If this object is aiming to abstract away from global state, then having a non-global-state parser for that seems consistent. > > b) No: There is no function which currently takes a string and returns an array for this scenario. However, that's true of other features you have included, such as parsing accept headers, or even extracting just HTTP headers from a copy of the $_SERVER array.
Quite a lot packed into four sentences; I'll try to address all of it. - First off, "yes and no" is a great answer, the most-accurate one possible. It highlights what I'm getting at: PHP transforms the content body into $_POST under some circumstances but not others. The RFC proposes to honor that existing PHP behavior. - "If this object is aiming to abstract away from global state" -- well, global *mutable* state, anyway, per our last exchange (and noted again below). - "There is no function which currently takes a string and returns an array for this scenario." -- True, though (and not to undermine my own case) there is json_decode(), which can return an array. But then the trouble is how to decide when to apply it, and with what parameters, and how to trap & report errors, etc., all of which adds complexity to what I think ought to be a simple object. And then once special treatment is given to JSON, what about XML? Etc., etc. Given all that, it strikes me that the cases not already served by PHP for parsing the body content into $_POST are best left to consumers of ServerRequest. - "However, that's true of other features you have included" -- also true, though those are informed by research into existing implementations, and provide what appear to be commonly- or frequently-needed values and structures. (You may opine this is contradictory, in which case I will respond as Whitman: "Do I contradict myself? Very well, then I contradict myself. I am large, I contain multitudes." ;-)
>> 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.) > > This distinction seems unnecessary. Once created, the object avoids global state because it is self-contained, and the fact that it's read-only is a separate attribute. We're really just talking about ways to construct that self-contained state, and "everything from global state" or "nothing from global state" seem like more natural options than "one thing from global state, everything else not".
I agree that the default $content value is an exception to everything else in ServerRequest. While it stays read-only, it does get read from php://input each time you refer to it. The alternative is to read from php://input once at construction time (when the construct $content argument is null) and retain that value in $content from the start. But in that case, when you have very large content bodies (as when there are PUT file uploads or other large payloads), then ServerRequest takes up a lot of memory, when it might not ever be used directly. Yes, "it might never be used" could be true of every element on ServerRequest, but the maximum memory use for those other elements tends to be rather smaller. In the end, letting $content read from php://input on the fly is a reasonable tradeoff; an exception that tests the rule, if you will. Over to you! -- 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
  108774
February 26, 2020 18:55 rowan.collins@gmail.com (Rowan Tommins)
On Wed, 26 Feb 2020 at 16:42, Paul M. Jones <pmjones@pmjones.io> wrote:

> Your presumption is correct! And your point on trying for better names is > well-taken -- though I think these are "expected" names, based on my > research into existing implementations. The most-common names are ... > > - the word "files" for unparsed or unmodified $_FILES values, leading me > to think $files is well-understood > > - the words "upload(s)", "fileUpload(s)", or "uploadedFile(s)" for parsed, > transformed, or restructured $_FILES values, leading me to think $uploads > is well-understood >
That's a reasonable justification. Just to check, are there other implementations that have both of these names side by side, or do most implementations have one or the other, but using this naming? The main confusion I can see is having to remember which two of these is an error without having to read the docs each time: isset( $request->files['attachment']['name'][0] ); isset( $request->files['attachment'][0]['name'] ); isset( $request->uploads['attachment']['name'][0] ); isset( $request->uploads['attachment'][0]['name'] );
> Having said that, I am open to suggestion here. What names do you think > would be better than the ones presented, contra pre-existing work from > other authors? >
Looking at the examples, the difference is rather similar to the PREG_PATTERN_ORDER and PREG_SET_ORDER options to preg_match_all. If getUploads was a method, it could take a similar behaviour switch - GROUP_BY_ITEM vs GROUP_BY_ATTRIBUTE or similar. For a property, that would have to be part of the name, like $uploadsByItem and $uploadsByAttribute, which are a bit ugly. Alternatively, you could lean more heavily on the legacy aspect, and have $uploads and $uploadsLegacyFormat or something like that.
> Incidentally, the current documentation doesn't describe the differences > particularly well, just saying one is "more like $_POST". Some details of > the structure, or examples comparing the two arrays, would be useful. > > Good call! On your suggestion, I have added details at < > https://github.com/pmjones/ext-request#the-uploads-array>. Suggestions > for improvement are welcome. >
Thanks, that makes it a lot clearer. :)
> - "There is no function which currently takes a string and returns an > array for this scenario." -- True, though (and not to undermine my own > case) there is json_decode(), which can return an array. But then the > trouble is how to decide when to apply it, and with what parameters, and > how to trap & report errors, etc., all of which adds complexity to what I > think ought to be a simple object. And then once special treatment is given > to JSON, what about XML? Etc., etc. Given all that, it strikes me that the > cases not already served by PHP for parsing the body content into $_POST > are best left to consumers of ServerRequest. >
Again, any mention of JSON or XML is drifting away from what I'm asking for. What I'm asking for (or rather, suggesting would be a useful and consistent addition) is a way to do *exactly what PHP does right now*, but based on a given input string, rather than the initial request. I am totally happy with users needing to add any support for JSON, XML, etc, just like they would have to populate $_POST manually.
> I agree that the default $content value is an exception to everything else > in ServerRequest. While it stays read-only, it does get read from > php://input each time you refer to it. > > The alternative is to read from php://input once at construction time > (when the construct $content argument is null) and retain that value in > $content from the start. But in that case, when you have very large content > bodies (as when there are PUT file uploads or other large payloads), then > ServerRequest takes up a lot of memory, when it might not ever be used > directly. Yes, "it might never be used" could be true of every element on > ServerRequest, but the maximum memory use for those other elements tends to > be rather smaller. >
That's a reasonable justification. I guess that's why PSR-7 exposes it as a stream, even though that causes a whole load of pain. To play devil's advocate, does the property belong in the object at all? If it doesn't interact with any of the other properties (e.g. populating $post and $uploads based on form data), could "better syntax for getting raw request for global state" be a separate feature. Then again, maybe the ability to over-ride it in the constructor is enough to make it useful. Regards, -- Rowan Tommins [IMSoP]
  108776
February 26, 2020 19:57 pmjones@pmjones.io ("Paul M. Jones")
Hi Rowan,

> On Feb 26, 2020, at 12:55, Rowan Tommins collins@gmail.com> wrote: > > That's a reasonable justification. Just to check, are there other > implementations that have both of these names side by side, or do most > implementations have one or the other, but using this naming?
My recollection is that they have one or the other, but none with both side-by-side. (A very few have neither.)
> The main confusion I can see is having to remember which two of these is an > error without having to read the docs each time: > > isset( $request->files['attachment']['name'][0] ); > isset( $request->files['attachment'][0]['name'] ); > isset( $request->uploads['attachment']['name'][0] ); > isset( $request->uploads['attachment'][0]['name'] );
/me nods While I too have imagined that, my impression has been that consumers of 1.x use $files unless & until they change their systems to use $uploads, at which point they switch over entirely to $uploads. Given that, my concerns (such as they may have been) are soothed.
> If getUploads was a method, it could take a similar behaviour switch - > GROUP_BY_ITEM vs GROUP_BY_ATTRIBUTE or similar. For a property, that would > have to be part of the name, like $uploadsByItem and $uploadsByAttribute, > which are a bit ugly. > > Alternatively, you could lean more heavily on the legacy aspect, and have > $uploads and $uploadsLegacyFormat or something like that.
Noted. Are there any others here who feel that the names $files and $uploads are "too confusing" (for whatever values of "confusing" you find appropriate) ?
> Again, any mention of JSON or XML is drifting away from what I'm asking > for.
Ah, my bad then.
> What I'm asking for (or rather, suggesting would be a useful and > consistent addition) is a way to do *exactly what PHP does right now*, but > based on a given input string, rather than the initial request.
I'm sorry, I'm still having trouble seeing what you're getting at. Do you mean something like this in the ServerRequest constructor? public function __construct(array $globals, ?string $content = null) { if ( ($globals['_POST'] ?? null) === null && strtolower($globals['_SERVER']['CONTENT_TYPE']) === 'application/x-www-form-urlencoded' ) { if ($content === null) { $content = file_get_contents('php://input'); } $globals['_POST'] = []; parse_str($content, $globals['_POST']); } // ... } If so, it seems unnecessary for the goals of this RFC, even overkill. If not, then I await correction.
> To play devil's advocate, does the [$content] property belong in the object at all? If it doesn't interact with any of the other properties (e.g. populating $post and $uploads based on form data), could "better syntax for getting raw request for global state" be a separate feature. Then again, maybe the ability to over-ride it in the constructor is enough to make it useful.
I continue to think it does belong there. Often enough, API developers will read php://input to decode JSON or XML they find there, so having it easily-available as $request->content is appealing. To boot, the other content-related headers are present, so might as well have the content itself there too. And as you note, it's easy enough to pass in custom $content via the constructor, e.g. for testing. * * * I feel that if these are the discussion points, then we are close to exhausting the topic (even if we do not agree on everything). Thank you for your diligence and attention to detail! -- 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
  108777
February 26, 2020 20:12 rowan.collins@gmail.com (Rowan Tommins)
On 26/02/2020 19:57, Paul M. Jones wrote:
> I'm sorry, I'm still having trouble seeing what you're getting at. Do you mean something like this in the ServerRequest constructor? > > public function __construct(array $globals, ?string $content = null) > { > if ( > ($globals['_POST'] ?? null) === null > && > strtolower($globals['_SERVER']['CONTENT_TYPE']) === 'application/x-www-form-urlencoded' > ) { > if ($content === null) { > $content = file_get_contents('php://input'); > } > $globals['_POST'] = []; > parse_str($content, $globals['_POST']); > } > > // ... > }
That's the easy part, yes; the harder part is this: if ( ($globals['_POST'] ?? null) === null && strtolower($globals['_SERVER']['CONTENT_TYPE']) === 'multipart/form-data' ) { if ($content === null) { $content = file_get_contents('php://input'); } [ $globals['_POST'], $globals['_FILE'] ] = parse_multipart_form_data($content); } Where parse_multipart_form_data is a function which doesn't currently exist, but whose logic must all be there in the core somewhere, because everything shows up in $_POST and $_FILES when you process such a request. Recreating that functionality in userland is non-trivial, but is essential for several use cases, e.g. an event-based server like ReactPHP, or a test using a saved request body as a fixture. If both content types (application/x-www-form-urlencoded and multipart/form-data) were handled, it would also mean that the relationship $content -> $input would match the relationship php://input -> $_POST by default, which seems consistent with the aim of matching existing behaviour. Regards, -- Rowan Tommins (né Collins) [IMSoP]
  108779
February 27, 2020 02:43 pmjones@pmjones.io (Paul M . Jones)
> On Feb 26, 2020, at 14:12, Rowan Tommins collins@gmail.com> wrote: > > On 26/02/2020 19:57, Paul M. Jones wrote: > >> Do you mean something like this in the ServerRequest constructor? >> >> ... > > That's the easy part, yes; the harder part is this: > > ...
Yes, that would definitely be the harder part.
> Recreating that functionality in userland is non-trivial, but is essential for several use cases, e.g. an event-based server like ReactPHP, or a test using a saved request body as a fixture. > > If both content types (application/x-www-form-urlencoded and multipart/form-data) were handled, it would also mean that the relationship $content -> $input would match the relationship php://input -> $_POST by default, which seems consistent with the aim of matching existing behaviour.
Yes, it would indeed. However, it strikes me that the thing to do here is not to try and embed that behavior in ServerRequest; rather, it would be to expose the existing functionality on its own, so that ReactPHP (and many others!) can use them, instead of having to roll their own in userland (a la <https://github.com/reactphp/http/blob/master/src/Io/MultipartParser.php>).. -- 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
  108781
February 27, 2020 09:43 rowan.collins@gmail.com (Rowan Tommins)
On Thu, 27 Feb 2020 at 02:43, Paul M. Jones <pmjones@pmjones.io> wrote:

> > Recreating that functionality in userland is non-trivial, but is > essential for several use cases, e.g. an event-based server like ReactPHP, > or a test using a saved request body as a fixture. > > > > If both content types (application/x-www-form-urlencoded and > multipart/form-data) were handled, it would also mean that the relationship > $content -> $input would match the relationship php://input -> $_POST by > default, which seems consistent with the aim of matching existing behaviour. > > Yes, it would indeed. However, it strikes me that the thing to do here is > not to try and embed that behavior in ServerRequest; rather, it would be to > expose the existing functionality on its own, so that ReactPHP (and many > others!) can use them, instead of having to roll their own in userland (a > la < > https://github.com/reactphp/http/blob/master/src/Io/MultipartParser.php>). >
Why not hope that ReactPHP and others will want to use this object, precisely because it avoids them having to roll their own implementations of things? If your concern is that the object should only wrap up features that are also available via some other mechanism, I'll point again at the $accept array, which AFAIK is a useful piece of parsing which is not available outside the proposed object's constructor. If anything, that's *more* special to this object, because what I'm proposing would directly parallel the existing behaviour of $_POST and $_FILES. If somebody really wanted to use the parser without the rest of the object, they could trivially wrap it in a function: function parse_multipart_form_data($content) { $request = new ServerRequest([], $content); return [ 'input' => $request->input, 'uploads' => $request->uploads ]; } The only downside I can see to adding it is complexity of implementation. But that's at best a reason to say "we'll hope to add this later" rather than "it would be better not to add it". Regards, -- Rowan Tommins [IMSoP]
  108782
February 27, 2020 15:55 pmjones@pmjones.io ("Paul M. Jones")
Hi Rowan,


> Why not hope that ReactPHP and others will want to use this object, > precisely because it avoids them having to roll their own implementations > of things?
Like I said earlier: if React ends up wanting to use ext/request, with its tradeoffs, of course I would think that's great. But if they want to keep using what they have already, with its own tradeoffs, that's great too.
> If somebody really wanted to use the parser without the rest of the object, > they could trivially wrap it in a function: > > function parse_multipart_form_data($content) { > $request = new ServerRequest([], $content); > return [ 'input' => $request->input, 'uploads' => $request->uploads ]; > }
This is very similar to what I'm saying: to use your phrasing, I opine it is better to "trivially wrap" the existing PHP functionality as part of a separate RFC, rather than try to embed it in ServerRequest (exposed or otherwise). To reiterate what I've said before: this RFC is a relatively conservative one. The vision around it is to stay pretty close to PHP as-it-is, and to incorporate those things from the researched implementations that show up over and over again. I know that does not lead quickly toward (what I surmise is) your vision of overhauling how PHP presents global state, but an overhaul of that kind is just not what this RFC aims to do. -- 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
  108783
February 27, 2020 16:57 rowan.collins@gmail.com (Rowan Tommins)
On Thu, 27 Feb 2020 at 15:55, Paul M. Jones <pmjones@pmjones.io> wrote:

> > Why not hope that ReactPHP and others will want to use this object, > > precisely because it avoids them having to roll their own implementations > > of things? > > Like I said earlier: if React ends up wanting to use ext/request, with its > tradeoffs, of course I would think that's great. But if they want to keep > using what they have already, with its own tradeoffs, that's great too. >
This is one place our attitudes differ: if I was proposing something like this to be included in every copy of PHP, I'd actively want people to use it, and consider that a measure of success; you seem to be happy to just put it out there and see. As such, I perhaps place greater value on functionality that would make it more attractive, and less value on matching what we already have.
> This is very similar to what I'm saying: to use your phrasing, I opine it > is better to "trivially wrap" the existing PHP functionality as part of a > separate RFC, rather than try to embed it in ServerRequest (exposed or > otherwise). >
That's not really the same, no. I am saying that once you have the parsing functionality somewhere in userland, whether it's inside ServerRequest or its own function doesn't matter, you could still use it to delete 300+ lines of code from ReactPHP. That's a reason to add it *as soon as possible*, even if in an ideal world it would be implemented in a slightly different place, or two different places, or whatever. Regards, -- Rowan Tommins [IMSoP]
  108784
February 27, 2020 17:06 pmjones@pmjones.io ("Paul M. Jones")
Hi Rowan,

> On Feb 27, 2020, at 10:57, Rowan Tommins collins@gmail.com> wrote: > > you seem to be happy to just put it out there and see
Perhaps it was not your intent, but even so: please don't put words in my mouth. We spoke of this before; quoting myself from <https://externals.io/message/108436#108650> ...
> 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.
I continue to hold that position. -- 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
  108785
February 27, 2020 17:27 rowan.collins@gmail.com (Rowan Tommins)
On Thu, 27 Feb 2020 at 17:06, Paul M. Jones <pmjones@pmjones.io> wrote:

> Hi Rowan, > > > On Feb 27, 2020, at 10:57, Rowan Tommins collins@gmail.com> > wrote: > > > > you seem to be happy to just put it out there and see > > Perhaps it was not your intent, but even so: please don't put words in my > mouth. We spoke of this before; quoting myself from < > https://externals.io/message/108436#108650> ... > > > 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. > > I continue to hold that position. >
Perhaps I am over-interpreting them, but the words you choose always seem to me very passive, as though shrugging and saying "it might happen, or it might not". In the exchange quoted above, I can see why "expect" might have felt too strong, but "suspect" is right at the other extreme; do you also "hope", "desire", or "want" people to use it? Regards, -- Rowan Tommins [IMSoP]
  108786
February 27, 2020 17:52 pmjones@pmjones.io ("Paul M. Jones")
Hi Rowan,

> On Feb 27, 2020, at 11:27, Rowan Tommins collins@gmail.com> wrote: > > On Thu, 27 Feb 2020 at 17:06, Paul M. Jones <pmjones@pmjones.io> wrote: > >> We spoke of this before; quoting myself from <https://externals.io/message/108436#108650> ... >> >>> 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 ths extension that they migrate over to it entirely. >> >> I continue to hold that position. > > Perhaps I am over-interpreting them, but the words you choose always seem to me very passive, as though shrugging and saying "it might happen, or it might not".
It could be that you are over-interpreting; in point of fact, some combination of those three things is guaranteed to happen, no "might" about it. In any case, we seem to have drifted from evaluation of the proposal (per se) to other topics. -- 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
  108787
February 27, 2020 18:04 mike@newclarity.net (Mike Schinkel)
> On Feb 27, 2020, at 12:52 PM, Paul M. Jones <pmjones@pmjones.io> wrote: >> On Feb 27, 2020, at 11:27, Rowan Tommins collins@gmail.com> wrote: >> On Thu, 27 Feb 2020 at 17:06, Paul M. Jones <pmjones@pmjones.io> wrote:
At the risk of being the messenger that gets shot, I feel like that horse is dead. And beating it more won't revive it, or make it any more dead. JMTCW. -Mike
  108778
February 26, 2020 21:46 mike@newclarity.net (Mike Schinkel)
> On Feb 26, 2020, at 1:55 PM, Rowan Tommins collins@gmail.com> wrote: > > On Wed, 26 Feb 2020 at 16:42, Paul M. Jones <pmjones@pmjones.io> wrote: > >> Your presumption is correct! And your point on trying for better names is >> well-taken -- though I think these are "expected" names, based on my >> research into existing implementations. The most-common names are ... >> >> - the word "files" for unparsed or unmodified $_FILES values, leading me >> to think $files is well-understood >> >> - the words "upload(s)", "fileUpload(s)", or "uploadedFile(s)" for parsed, >> transformed, or restructured $_FILES values, leading me to think $uploads >> is well-understood >> > > > That's a reasonable justification. Just to check, are there other > implementations that have both of these names side by side, or do most > implementations have one or the other, but using this naming? > > The main confusion I can see is having to remember which two of these is an > error without having to read the docs each time: > > isset( $request->files['attachment']['name'][0] ); > isset( $request->files['attachment'][0]['name'] ); > isset( $request->uploads['attachment']['name'][0] ); > isset( $request->uploads['attachment'][0]['name'] );
Here is an easy way to remember: $request->files is exactly like $_FILES (and if you can't remember that, blame PHP, not the RFC) $request->uploads is therefore new, and since it is new it follows a reasonable collection-list-item structure (as opposed to an unreasonable collection-item-list structure that PHP implemented in $_FILES.) So from those rules: // Don't use, it's the old way of doing things, unless you are just replacing a reference to $_FILES then then it's a non-issue. isset( $request->files['attachment']['name'][0] ); // Don't use, it's the old way of doing things, unless you are just replacing a reference to $_FILES then then it's a non-issue. isset( $request->files['attachment'][0]['name'] ); //Don't use. Not a reasonable collection-list-item structure. Why would "they" create a new API with a parallel array structure?!? isset( $request->uploads['attachment']['name'][0] ); // This one is golden! It's clearly new because of now $_UPLOADS existing, and it follows a reasonable collection-list-item structure isset( $request->uploads['attachment'][0]['name'] ); #jmtcw -Mike
  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