[PSR-7] no defined way to get the path without frontcontroller (path_info)

525 views
Skip to first unread message

tob...@gmail.com

unread,
Mar 4, 2015, 7:00:08 PM3/4/15
to php...@googlegroups.com
The main problem that is not covered by PSR-7 is a standard handling of the so called path_info from CGI terminology.

There are several issues on symfony and zf regarding the magic to determine the front controller based on the path using a combination of request path, 
path info, script name etc. This is quite complicated and error prone.

Example: Given a request to http://example.org/index.php/homepage
The PSR-7 UriInterface::getPath would return '/app_dev.php/homepage' I assume because that's what the path is in URI terms.
So with the front controller. But there is not defined way to get the actual "path info" which is only '/homepage' in application terms.
But actually the path without the front controller is what is usually of interest and not the full path.

There are so many libraries that for example do matching based on the path, e.g. symfony routing component and the security firewalls.
And the intended goal is exactly to make all the libs switch to PSR-7 to be interoperable, e.g. Router::match(RequestInterface $request).
If the path contains the front controller it would not work. And since there is no standard way to get the path info only,
they would all need to handle to remove the front controller themselves somehow, which as stated, is not easy because it's not clear if it's a 
front controller or an actual request path.

The only current approach is ServerRequestInterface::getServerParams()['PATH_INFO'] but that does not seem reliable because it's
might not be set, or wrong and it's not documented as part of PSR-7.

So all in all, the actual request path that most people will require to work with, is not covered yet.

tob...@gmail.com

unread,
Mar 4, 2015, 7:14:59 PM3/4/15
to php...@googlegroups.com
So until this is resolved I don't see how PSR-7 could be used in contracts between all the libraries, e.g. between Symfony components routing, security and kernel.

Matthew Weier O'Phinney

unread,
Mar 4, 2015, 11:56:27 PM3/4/15
to php...@googlegroups.com
It's already possible, and it's the responsibility of the application layer.

What you do is in your application, identify the path to the front
controller, strip that path, and create and inject a new URI, creating
a new request instance:

$uri = $request->getUri();
$uri = $uri->withPath(stripBasePathSomehow($uri->getPath());
$request = $request->withUri($uri);

This is something you'd likely do during application initialization,
either during bootstrapping or immediately prior to routing.
Essentially, it's what our frameworks are already doing; the
PhpEnvironment\Request in ZF2 is getting the actual URI, and stripping
the base path if it matches certain criteria (which could include
matching a base path you inject manually, or an autodiscovered base
path based on the path structure).

If the base path is of interest then for generating URIs, you pass
that into your URI assembler, or you store it as a request attribute
($request = $request->withAttribute('BASE_PATH', $basePath)).

I do something similar in Conduit (the reference middleware
implementation I've been developing in tandem with the PSR-7 spec as a
sandbox). If a middleware is registered in the stack with a path, that
path is stripped off the request that is passed into the middleware.
This allows nested middleware to resolve based on the relative path,
without needing to know anything about any layers of nesting.

The point is: just because the incoming request includes that
information in the path does not mean it has to be what you pass to
lower layers of the application. What you're wanting to do is
perfectly possible, and actually falls outside the specification; it's
an application level concern.



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

tob...@gmail.com

unread,
Mar 5, 2015, 6:21:14 AM3/5/15
to php...@googlegroups.com
Thanks for the detailed answer. I feel like when there is not specified way in ServerRequestInterface, to get the base path or the path info,
it will not be interoperable without manual intervention for every component that needs these infos.

You suggest to add this info to the attributes. But doesn't this mean at the end, that every component expects to have these info
available under a certain name. E.g. symfony routing component would expect to have the base path in attributes['BASE_PATH']
and another one want's it in attributes['BASE_URL']. So a user who wants to use both components would need to manually set the
base path into 2 arbitrary names.

Another issue might come up with the following examples:

So these two should be both be considered as having path "/"
But 3) and 4) are different and the difference is important, e.g. in symfony when generating relative URLs.
But when you strip the base path, then for 3) we cannot return "/" for getPath even though the path is now empty. So how would we the stripping know,
if we have case  2), 3) or 4)?

Jordi Boggiano

unread,
Mar 5, 2015, 6:53:51 AM3/5/15
to php...@googlegroups.com
On 05/03/2015 04:56, Matthew Weier O'Phinney wrote:
> It's already possible, and it's the responsibility of the application layer.
>
> What you do is in your application, identify the path to the front
> controller, strip that path, and create and inject a new URI, creating
> a new request instance:
>
> $uri = $request->getUri();
> $uri = $uri->withPath(stripBasePathSomehow($uri->getPath());
> $request = $request->withUri($uri);
>
> This is something you'd likely do during application initialization,
> either during bootstrapping or immediately prior to routing.
> Essentially, it's what our frameworks are already doing; the
> PhpEnvironment\Request in ZF2 is getting the actual URI, and stripping
> the base path if it matches certain criteria (which could include
> matching a base path you inject manually, or an autodiscovered base
> path based on the path structure).
>
> If the base path is of interest then for generating URIs, you pass
> that into your URI assembler, or you store it as a request attribute
> ($request = $request->withAttribute('BASE_PATH', $basePath)).

I get what you're saying here, but this means there is no standard way
to get the base path, it'll depend on the application, and that is a bit
sad since it's pretty much a concept any framework with front-controller
will have. Why not have a set/getScriptName or similar so that there is
a standard place to store it and get it?

I can't remember why right now but I know I have needed the script name
in some edge cases before.

Cheers

Paul M. Jones

unread,
Mar 5, 2015, 8:33:29 AM3/5/15
to php...@googlegroups.com

> On Mar 5, 2015, at 05:53, Jordi Boggiano <j.bog...@seld.be> wrote:
...
> I can't remember why right now but I know I have needed the script name in some edge cases before.

For when rewriting has been disabled/not available.


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

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



Larry Garfield

unread,
Mar 5, 2015, 12:02:10 PM3/5/15
to php...@googlegroups.com
On 03/05/2015 07:33 AM, Paul M. Jones wrote:
>> On Mar 5, 2015, at 05:53, Jordi Boggiano <j.bog...@seld.be> wrote:
> ...
>> I can't remember why right now but I know I have needed the script name in some edge cases before.
> For when rewriting has been disabled/not available.

Or for when behavior should vary based on it, in ways other than what's
hard-coded into the file itself.

I think we should try to address this in ServerRequest. It's a
common-enough problem space that we should try to avoid inconsistent
workarounds in different projects.

--Larry Garfield

Matthew Weier O'Phinney

unread,
Mar 6, 2015, 6:05:43 PM3/6/15
to php...@googlegroups.com
SCRIPT_NAME is available via server params already:

$scriptName = $request->getServerParams()['SCRIPT_NAME'];

However, SCRIPT_NAME is the path on the filesystem to the script. This
is often used to try and determine if the path contains a portion of
the script name in it, in which case that segment will be stripped
from the path for purposes of routing.

All of this could be done via a router, which will likely also be what
is used to assemble URI links for your application. The question is:
should that functionality be pushed into the ServerRequest, or left to
the consumer?

If we decide to make this a responsibility of the ServerRequest, then
the next question is: what do we call it? In ZF, we've called this the
"base path". tob2bot referenced "PATH_INFO", but within the SAPI,
that's the path present in the incoming request URI. My take is that,
since we're talking about a prefix in the path that we want to strip
off or resolve to, "BASE_PATH" is likely what we want, which would
suggest the addition of these two methods:

withBasePath($path) : ServerRequest
getBasePath() : string

My question is: does this really need to be in the interface?

When I look at various implementations, the base path is typically
only of interest for:

- routing
- URI assembly

Typically, the latter is a function of the router (as you want to
assemble a URI based on configured routes). When I look at ZF2, we
have exactly two places where the base path is consumed:

- our router — which stores the value, and uses it for route generation.
- our "base path" view helper (which gets the value on instantiation)

I could see having a service or utility method that is passed the
Request instance, and derives the value to pass to either of these,
and/or making that a function of the router. Considering all the
values necessary for deriving the value are already present in the
Request, adding methods around this would, at best, be a convenience,
and could be interpreted as being outside the scope of the Request. In
talking to Larry, it looks like the situation in Symfony is similar.

That said... the methods exist on the current Request implementations,
which makes a case for adding them to PSR-7. I could take it or leave
it at this point; I need to know if this is a show-stopper for any of
you.

Thoughts?

Tobion

unread,
Mar 31, 2015, 10:42:19 PM3/31/15
to php...@googlegroups.com
Too clarify names: $path = $basePath . $pathInfo;
So - different from Matthew said - the base path != path info.

I agree with Matthew that we ideally should not have to deal with base path and path info, if we can just do

    $uri = $request->getUri(); 
    $uri = $uri->withPath(stripBasePathSomehow($uri->getPath()); 
    $request = $request->withUri($uri);

in the initialization phase. And code that needs the base path like URI assembly can just offer a method like `setBasePath` which has to be called manually.
So in this case PSR-7 does not have to offer any specific methods for that. BUT as I demonstrated earlier, there is one bug that comes with it necessarily:
It's impossible to distinguish http://example.org/app_dev.php from http://example.org/app_dev.php/ when you strip out the `/app_dev.php` base path.
This is because "" and "/" path are same path, but it's not true anymore in the context with base path. This is exactly the same problem we have in symfony: https://github.com/symfony/symfony/issues/6634
I see two solutions:

1. either adjust `getPath` somehow to also allow empty path return values in certain cases (when there is a base path, which is not defined...)
2. not going the approach with trimming the base path but instead have built-in support for base path and path info in the ServerRequest

So in both cases we need adjustments and in it's current form PSR-7 is either buggy or not interoperable since we would need to make hacks like setting the "real" path in attributes to workaround that bug.

Tobion

unread,
Mar 31, 2015, 10:57:59 PM3/31/15
to php...@googlegroups.com
One way to go with solution 1 might be:
Adjust the doc for getPath() to tell that an empty path returns "/" unless withPath('') has explicitly been called with an empty path. That would not stand against RFC 3986 and also allow to have an empty path which is required for a trimmed path without base path.

Beau Simensen

unread,
Apr 1, 2015, 1:17:33 PM4/1/15
to php...@googlegroups.com
On Tuesday, March 31, 2015 at 9:57:59 PM UTC-5, Tobion wrote:
One way to go with solution 1 might be:
Adjust the doc for getPath() to tell that an empty path returns "/" unless withPath('') has explicitly been called with an empty path. That would not stand against RFC 3986 and also allow to have an empty path which is required for a trimmed path without base path.

I took a look at the Symfony issue (and associated PR) and see how this could be problematic. However, it seems to me as though this could be handled in a URI generator that is aware of a base URI. I wanted to see how easy this would be so I created a little bit of code to try it out:


From what I can tell, if a URI generator is aware of both a base URI and the PSR-7 Message URI, it should be able to create the originally requested URI correctly.

There are other edge cases that I can thin of as well, but I believe they can all be handled in the generator side and/or whatever is building the base URI / setting the path on the PSR-7 Message URI.

Does this address your concerns?

Tobion

unread,
Apr 2, 2015, 10:08:25 PM4/2/15
to php...@googlegroups.com
I'm sorry but this cannot be "fixed" at generation time. The problem is at correctly representing the current URI (esp. path) with PSR-7 when dealing with a base path.
So when an URI assembly needs to know the current URI to generate a new (relative) one, you want to inject a PSR-7 UriInterface or full RequestInterface.
But then it's impossible to distinguish between "/app.php" and "/app.php/" as the current path because they are handled the same as soon as the base path is trimmed. 
So getPath returns "/" in both cases and the URI assembly will do wrong stuff based on wrong data from PSR-7. So you cannot fix it later.

Tobion

unread,
Apr 2, 2015, 11:28:02 PM4/2/15
to php...@googlegroups.com
My proposal to fix this in PSR-7: https://github.com/php-fig/fig-standards/pull/487

Beau Simensen

unread,
Apr 3, 2015, 11:15:00 AM4/3/15
to php...@googlegroups.com
On Thursday, April 2, 2015 at 9:08:25 PM UTC-5, Tobion wrote:
I'm sorry but this cannot be "fixed" at generation time. The problem is at correctly representing the current URI (esp. path) with PSR-7 when dealing with a base path.
So when an URI assembly needs to know the current URI to generate a new (relative) one, you want to inject a PSR-7 UriInterface or full RequestInterface.
But then it's impossible to distinguish between "/app.php" and "/app.php/" as the current path because they are handled the same as soon as the base path is trimmed. 
So getPath returns "/" in both cases and the URI assembly will do wrong stuff based on wrong data from PSR-7. So you cannot fix it later.

I think perhaps I do not understand what you are referring to when you say "URI assembly" as that sounds like what I was referring to as "URI generation." I also maybe did not follow the issue on the Symfony bug ( https://github.com/symfony/symfony/issues/6634 ) correctly.

Can you point to how this was fixed in Symfony? From what I can see, you were asking for this commit to be reverted but I'm not sure if that happened or if you fixed it in some other way:

Reply all
Reply to author
Forward
0 new messages