Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

4.x - Base path handling #2512

Open
akrabat opened this issue Oct 2, 2018 · 25 comments

Comments

@akrabat
Copy link
Member

commented Oct 2, 2018

We need to have base path handling working consistently without confusion in Slim 4.

See also: #1964

@l0gicgate

This comment has been minimized.

Copy link
Contributor

commented Nov 19, 2018

This should be moved to Slim-Psr7 repo along with #1964

@akrabat

This comment has been minimized.

Copy link
Member Author

commented Nov 25, 2018

This essentially means we should support running Slim in a subdirectory.

i.e. Slim should work for a URL like www.example.com/myapp/hello where the route in Slim is $app->get('/hello', HelloAction::class');

I would like this to be PSR-7 implementation independent.

@tuupola

This comment has been minimized.

Copy link
Contributor

commented Nov 26, 2018

Slim already works when running from a subdirectory. I do it all the time. Just need to set the RewriteBase in .htaccess file. For example for the above example it would be:

RewriteBase /myapp/
@l0gicgate

This comment has been minimized.

Copy link
Contributor

commented Nov 26, 2018

@tuupola wouldn't it be better to make it server environment independent somehow? We could maybe add a setting to App which appends a path prefix to all the routes when they're mapped maybe?

@tuupola

This comment has been minimized.

Copy link
Contributor

commented Nov 27, 2018

@l0gicgate I don't have any strong feelings about that.

However one cannot get rid of RewriteBase. It is not about prefixing Slim urls. It is about telling Apache where to find the index.php file.

Lets assume the document root is /var/www/public and Slim app is in subfolder foo.

/var/www/public
/var/www/public/foo
/var/www/public/foo/.htaccess
/var/www/public/foo/index.php
$app = new \Slim\App;
$app->get("/hello", function ($request, $response, $args) {
    print "Hello!";
});
$app->run();
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]

Without RewriteBase when request is made to https://example.com/foo/hello Apache tries to access /var/www/public/index.php which is wrong. You will get a 404 response from Apache with something like:

The requested URL /var/www/public/index.php was not found on this server.

With RewriteBase when request is made to https://example.com/foo/hello Apache accesses /var/www/public/foo/index.php which is correct and you will get a response from Slim.

RewriteEngine On
RewriteBase /foo/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]

Since RewriteBase is anyway needed I am not sure if another setting is needed? Maybe for Nginx and other servers?

@l0gicgate

This comment has been minimized.

Copy link
Contributor

commented Feb 18, 2019

I don't believe nginx has this problem since when you setup fastcgi you have to set the base path like so:

server {
    # FastCGI Parameters
    location ~ \.php$ {
        fastcgi_pass    unix:/var/run/php-fpm.sock;
        fastcgi_param   SCRIPT_FILENAME /var/www/html/foo$fastcgi_script_name;
        include fastcgi_params;
    }
}

@akrabat better documentation perhaps? This cannot be fixed at the Slim level from my understanding.

@akrabat

This comment has been minimized.

Copy link
Member Author

commented Feb 18, 2019

@akrabat better documentation perhaps? This cannot be fixed at the Slim level from my understanding.

I'm more than happy to have a definitive documentation page on how to run Slim in a sub-directory.

@tuupola

This comment has been minimized.

Copy link
Contributor

commented Feb 18, 2019

For Apache all you need to do is to add RewriteBase directive to the default Slim config. Works with both Slim 2 and Slim 3. I have been running Slim like this since the beginning.

Example of an .htaccess from one of my live projects. Slim is installed to folder named foo.

RewriteEngine On

RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L]

RewriteBase /foo/

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]

The .htaccess must in the same folder where Slim's main entry point is. In the example below /var/www/public is Apache main document root. Slim app files are simply copied to foo folder under the document root. Slim app is accessible at example.com/foo/ url.

/var/www/public
/var/www/public/foo
/var/www/public/foo/.htaccess
/var/www/public/foo/index.php
/var/www/public/foo/src
/var/www/public/foo/vendor

Since it is bad habit to have vendor and src publicly accessible and maybe you use deployer for deploying the app, directory structure might look like this:

/var/www/public
/var/www/deployer/foo
/var/www/deployer/foo/public/.htaccess
/var/www/deployer/foo/public/index.php
/var/www/deployer/foo/src
/var/www/deployer/foo/vendor

With above folder structure /foo/public must also be aliased as /foo in Apache config.

Alias /foo/ /var/www/deployer/foo/public/

Now source code is outside document root but Slim app is still accessible at example.com/foo/ url.

@odan

This comment has been minimized.

Copy link

commented Feb 22, 2019

@tuupola Maybe RewriteBase is a little bit to "unflexible", because it's a hardly coded path and may change from development machine to development machine and from server (test, staging, prod) to server.

I think Slim 4 should handle this issue, like Symfony and other Frameworks can handle it.

In Slim 3 I do a little "hack" by...

  1. changing the SCRIPT_NAME in the $_SERVER super global variable:
$container['environment'] = function () {
    $scriptName = $_SERVER['SCRIPT_NAME'];
    $_SERVER['SCRIPT_NAME'] = dirname(dirname($scriptName)) . '/' . basename($scriptName);
    return new Slim\Http\Environment($_SERVER);
};
  1. ... and then I add a second .htaccess file above the public/ directory. The second .htaccess file only makes an internal redirect to the public/directory, but only if Apache DocumentRoot does not point to the public/directory (e.g. in my development environment).
RewriteEngine on
RewriteRule ^$ public/ [L]
RewriteRule (.*) public/$1 [L]

I hope that in Slim 4 this "hack" is no longer needed. :-)

@l0gicgate

This comment has been minimized.

Copy link
Contributor

commented Apr 24, 2019

@odan can you point me where the logic is from Symfony to circumvent this issue? Because I'm looking at Zend Expressive and they explicitly document that a server side configuration is required:
https://docs.zendframework.com/zend-expressive/v3/cookbook/using-a-base-path/

I'd like to fix this for 4.x but I don't think it's possible.

@odan

This comment has been minimized.

Copy link

commented Apr 24, 2019

@l0gicgate

It looks like I confused it with a feature in CakePHP.

Update: Here is my working solution using $app->setBasePath($basePath);

The basic directory structure:

  • public/ Web server files, the DocumentRoot
    • .htaccess Apache redirect rules for the front controller
    • index.php The front controller
  • .htaccess Internal redirect to the public/ directory

For Apache we have to "redirect" the web traffic to the front controller in public/index.php.

Create a file: public/.htaccess with this content:

# Redirect to front controller
RewriteEngine On
# RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]

We also need a rule to "redirect" the sub-directories to the front-controller in public/index.php.

Create a .htaccess file above the public/ directory with this content:

RewriteEngine on
RewriteRule ^$ public/ [L]
RewriteRule (.*) public/$1 [L]

Set the base path to run the app in a subdirectory. This path is used in urlFor().

Example:

<?php

use Slim\Factory\AppFactory;
use Selective\BasePath\BasePathDetector;

$app = AppFactory::create();

// composer require selective/basepath

// Set the base path to run the app in a subdirectory.
// This path is used in urlFor().
$basePath = (new BasePathDetector($_SERVER))->getBasePath();

$app->setBasePath($basePath);

// Add middleware
// ...

$app->run();
@adriansuter

This comment has been minimized.

Copy link
Contributor

commented Apr 28, 2019

I just installed Slim 4.0.0-alpha, configured an alias called slim4 to my virtual host localhost (Apache 2.4.34) pointing towards a subdirectory public of the installation and added the following .htaccess-file to that subdirectory (no RewriteBase needed)

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ /slim4/index.php [QSA,L]

In the same subdirectory I then added a very simple index.php-file

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;

include_once(__DIR__ . '/../vendor/autoload.php');

define('ROUTE_NAME_ANSWER', 'answer');

$app = AppFactory::create();
$app->setBasePath('/slim4');

$app->get('/', function (Request $request, Response $response, $args) use ($app) {
    $routeParser = $app->getRouteCollector()->getRouteParser();
    $response->getBody()->write('<a href="' . $routeParser->urlFor(ROUTE_NAME_ANSWER) . '">What is the answer?</a>');
    return $response;
});

$app->get('/answer', function (Request $request, Response $response, $args) {
    $response->getBody()->write('42');
    return $response;
})->setName(ROUTE_NAME_ANSWER);

$app->run();

Requesting http://localhost/slim4/ worked as expected. The link href was correctly set (so the route parser urlFor did prefix the base path) and http://localhost/slim4/answer worked also like a charm.

Of course if would be great if the base path could either be detected automatically, or if it could be set via settings.

One possibility would be to use Apache environment variables (if allowed by hosting provider):

  1. Add the following line to .htaccess
SetEnv SLIM_BASE_PATH /slim4
  1. Update the following line in index.php
$app->setBasePath($_SERVER['SLIM_BASE_PATH']);
@l0gicgate

This comment has been minimized.

Copy link
Contributor

commented Apr 28, 2019

@adriansuter that's a good solution. I suppose we should add that to the docs? Can we consider this issue resolved as well?

@adriansuter

This comment has been minimized.

Copy link
Contributor

commented Apr 28, 2019

@l0gicgate Maybe in the docs we should use filter_input(INPUT_SERVER, 'SLIM_BASE_PATH', FILTER_SANITIZE_STRING) instead of just $_SERVER['SLIM_BASE_PATH']. Would you add this to the docs? English is not my native language :-)

I haven't checked all possible use cases of the base path. Other than that, in my opinion the concept is pretty consistent and without confusion, as requested by @akrabat

@moritz-h

This comment has been minimized.

Copy link
Contributor

commented May 11, 2019

I just started looking at v4 alpha and directly saw this issue just after copying the hello world code.

I wondered why Slim 3 works out the box and found some base-path-detection code here:
https://github.com/slimphp/Slim/blob/3.12.1/Slim/Http/Uri.php#L201-L215

I would guess this was removed because of the PSR7 changes.
But couldn't some automatic base path detection not just be added in the AppFactory?

@l0gicgate

This comment has been minimized.

Copy link
Contributor

commented May 12, 2019

@moritz-h we could possibly include some type of middleware which appends the user defined base path to the request's uri's path.

We could do something like https://github.com/Lansoweb/basepath does.

@silentworks

This comment has been minimized.

Copy link
Member

commented May 17, 2019

@l0gicgate that's a good idea.

@moritz-h

This comment has been minimized.

Copy link
Contributor

commented May 17, 2019

@l0gicgate I think you mean something different. You talk about handling the user defined base path. What I meant is, that I do not want to define a base bath and Slim should auto-detect it (like Slim v3 does, when I run it in a subdirectory).

Currently I cloud either define $app->setBasePath(...), then the base path will be added to all route definitions. Or I cloud use something like Lansoweb/basepath which removes the base path from the request. But both ways work only with user defined base path.

What I was suggesting above is that perhaps in AppFactory the auto-detection is done, so there there is no need for calling $app->setBasePath().
But I could also do something like using the autodetection code from Slim3 in something like the Lansoweb/basepath Middleware. That should also work. I would then suggest to add this a example middleware to slim documentation.

@l0gicgate

This comment has been minimized.

Copy link
Contributor

commented May 18, 2019

@moritz-h feel free to raise a PR with base path detection logic in AppFactory. As I said before, it's not possible to be done without assumptions and for the record it does not work in Slim 3 either, hence why this is a longstanding issue.

@moritz-h

This comment has been minimized.

Copy link
Contributor

commented May 19, 2019

@l0gicgate I read #2107 only after my last post here. Before I only read Slim 3 sources and thought it is a matter of copy and paste and adjust this part of code. Now I see what the problem is with this.

@esetnik

This comment has been minimized.

Copy link

commented Aug 7, 2019

I implemented a BasePathMiddleware that seems to work.

<?php

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Psr\Http\Message\ResponseInterface;

class BasePathMiddleware
{
  private $basePath;

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

  public function __invoke(Request $request, RequestHandler $handler): ResponseInterface
  {
    $uri = $request->getUri();
    $path = $uri->getPath();
    $path = '/'.ltrim($path, $this->basePath);
    $request = $request->withUri($uri->withPath($path));
    return $handler->handle($request);
  }
}

Use it like:

$app->addRoutingMiddleware();

$basePathMiddleware = new BasePathMiddleware('/api');
$app->add($basePathMiddleware);
@esetnik

This comment has been minimized.

Copy link

commented Aug 7, 2019

I left a note about this in #2770 (comment) because it tripped me up during migration from v3 to v4. I didn't see this open issue at the time.

@l0gicgate l0gicgate changed the title Base path handling in Slim 4 4.x - Base path handling Aug 7, 2019

@dominikzogg

This comment has been minimized.

Copy link

commented Aug 9, 2019

any idea, why PSR7 implementations are not using: $_SERVER['PATH_INFO'] ?

@l0gicgate

This comment has been minimized.

Copy link
Contributor

commented Aug 9, 2019

@dominikzogg typically when you're on localhost that variable does not get set. It's not reliable.

@phpbg

This comment has been minimized.

Copy link

commented Aug 30, 2019

$app->setBasePath() did the trick for me, but I couldn't find it in the doc.

IMO it should me mentionned on this page: http://www.slimframework.com/docs/v4/start/web-servers.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
10 participants
You can’t perform that action at this time.