Closed akrabat closed 5 years ago
This should be moved to Slim-Psr7 repo along with #1964
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.
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/
@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?
@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?
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 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.
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.
@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...
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);
};
.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. :-)
@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.
@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 controllerindex.php
The front controller.htaccess
Internal redirect to the public/ directoryFor 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();
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):
.htaccess
SetEnv SLIM_BASE_PATH /slim4
index.php
$app->setBasePath($_SERVER['SLIM_BASE_PATH']);
@adriansuter that's a good solution. I suppose we should add that to the docs? Can we consider this issue resolved as well?
@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
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?
@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.
@l0gicgate that's a good idea.
@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.
@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.
@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.
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);
I left a note about this in https://github.com/slimphp/Slim/issues/2770#issuecomment-518389295 because it tripped me up during migration from v3 to v4. I didn't see this open issue at the time.
any idea, why PSR7 implementations are not using: $_SERVER['PATH_INFO'] ?
@dominikzogg typically when you're on . It's not reliable.localhost
that variable does not get set
$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
When using $app->setBasePath('/public')
it breaks RouteParser::relativeUrlFor
output:
echo $routerParser->urlFor('about');
/public/about
echo $routerParser->relativeUrlFor('about');
about <- expected
/public/about <- result
Using only $app->getRouteCollector()->setBasePath('/public')
, RouteParser produces corrent urls for both urlFor and relativeUrlFor methods but breaks current path detection
Using both $app->setBasePath
and $routeCollector->setBasePath
produces invalid url paths for all methods
@piotr-cz that's an interesting bug. Let's open an issue for that.
I'm closing this as resolved
To set base path use:
$app->setBasePath('/base-path)
thank you @adriansuter , work for me in URL : http://localhost/slim4/public/
and structure folder : Click here for view
I just installed Slim 4.0.0-alpha, configured an alias called
slim4
to my virtual hostlocalhost
(Apache 2.4.34) pointing towards a subdirectorypublic
of the installation and added the following.htaccess
-file to that subdirectory (noRewriteBase
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
-fileuse 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 parserurlFor
did prefix the base path) andhttp://localhost/slim4/answer
worked also like a charm.
my htaccess:
RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^ /slim4/public/index.php [QSA,L]
my index.php ( the same of http://www.slimframework.com/)
<?php use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Slim\Factory\AppFactory;
require DIR . '/../vendor/autoload.php';
$app = AppFactory::create();
$app->get('/hello/{name}', function (Request $request, Response $response, array $args) { $name = $args['name']; $response->getBody()->write("Hello, $name"); return $response; });
$app->run();
We need to have base path handling working consistently without confusion in Slim 4.
See also: https://github.com/slimphp/Slim/issues/1964