Skip to content

Add BasePathMiddleware for subdirectory deployments#3432

Open
KalimeroMK wants to merge 1 commit intoslimphp:4.xfrom
KalimeroMK:4.x
Open

Add BasePathMiddleware for subdirectory deployments#3432
KalimeroMK wants to merge 1 commit intoslimphp:4.xfrom
KalimeroMK:4.x

Conversation

@KalimeroMK
Copy link

This PR adds a new BasePathMiddleware that automatically strips the base path from request URIs, allowing Slim applications to run in subdirectories without modifying route definitions.


🎯 Problem Statement

Typical Scenario:

# Apache configuration with Alias
Alias /myapp /var/www/myapp/public
// Route is defined as:
$app->get('/api/users', function ($request, $response) {
    return $response->withJson(['users' => []]);
});

// But request comes to:
// GET /myapp/api/users

// Result: ❌ 404 Not Found
// Slim looks for exact match with /myapp/api/users
// But route is defined as /api/users

Current workarounds are inelegant:

  • Manual path stripping before adding to Slim
  • .htaccess hacks that don't work on all servers
  • Defining routes with variable prefix: $app->get($basePath . '/api/users', ...)

💡 Solution

BasePathMiddleware strips the base path before the request reaches the router:

Request: GET /myapp/api/users
    ↓
BasePathMiddleware (/myapp)
    ↓
Slim Router sees: /api/users ✅
    ↓
Route match: /api/users → 200 OK

🚀 Usage

Option 1: Explicit Base Path

When you know the exact base path:

<?php
// index.php

use Slim\Factory\AppFactory;
use Slim\Middleware\BasePathMiddleware;

$app = AppFactory::create();

// Add the middleware first in the stack!
$app->add(new BasePathMiddleware('/myapp'));

// Now routes work without prefix
$app->get('/api/users', function ($request, $response) {
    return $response->withJson(['users' => []]);
});

$app->run();

Apache configuration:

Alias /myapp /var/www/html/myapp/public
<Directory /var/www/html/myapp/public>
    AllowOverride All
    Require all granted
    
    <IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteRule ^ index.php [QSA,L]
    </IfModule>
</Directory>

Option 2: Auto-Detection

When base path varies (dev vs production):

<?php
// index.php

use Slim\Middleware\BasePathMiddleware;

$app = AppFactory::create();

// Auto-detect from $_SERVER['SCRIPT_NAME']
// If it's /myapp/public/index.php → /myapp
$app->add(BasePathMiddleware::fromRequest($request));

$app->get('/api/users', ...);
$app->run();

Option 3: Environment Variable

$app->add(new BasePathMiddleware($_ENV['APP_BASE_PATH'] ?? ''));

🎁 Additional Features

Access Base Path in Routes

The middleware stores the original base path in request attributes:

$app->get('/api/users', function ($request, $response) {
    $basePath = $request->getAttribute('basePath'); // '/myapp'
    
    // Useful for URL generation
    $url = $basePath . '/api/users/123';
    
    return $response->withJson([
        'basePath' => $basePath,
        'url' => $url
    ]);
});

🧪 Deployment Examples

Shared Hosting (cPanel, etc.)

// If application is in /public_html/myapp/
$app->add(new BasePathMiddleware('/myapp'));

Docker with Subdirectory

# docker-compose.yml
nginx:
  image: nginx:alpine
  volumes:
    - ./nginx.conf:/etc/nginx/conf.d/default.conf
# nginx.conf
location /api/ {
    alias /var/www/public/;
    try_files $uri $uri/ /index.php?$query_string;
    
    location ~ \.php$ {
        fastcgi_pass php:9000;
        fastcgi_param SCRIPT_FILENAME $request_filename;
        include fastcgi_params;
    }
}
// index.php
$app->add(new BasePathMiddleware('/api'));

Multi-tenant Application

// For /client1/ prefix
$app->add(new BasePathMiddleware('/client1'));

// All routes are accessible at /client1/api/...

🔄 Backwards Compatibility

  • Fully backwards compatible
  • ✅ No breaking changes
  • ✅ Optional middleware
  • ✅ Doesn't affect existing routes

🧪 Testing

Unit Tests

vendor/bin/phpunit tests/Middleware/BasePathMiddlewareTest.php

Integration Testing

# Apache configuration for testing
Alias /testapp /path/to/test/public

# Start application
php -S localhost:8080 -t public/

# Test
curl http://localhost:8080/testapp/api/users

🎯 Goal of This PR

The goal of this PR is to:

  1. Simplify deployment in subdirectory environments
  2. Lower barrier for using Slim on shared hosting
  3. Standardize a solution for the most common 404 error problem
  4. Enable clean route definitions without hardcoded prefixes

🙏 Thank You

Hope this middleware helps the Slim community simplify their deployments! 🚀

- Adds middleware to strip base path from request URI
- Supports explicit path via constructor
- Supports auto-detection via BasePathMiddleware::fromRequest()
- Auto-strips /public directory from detected paths
- Stores original base path in request attribute for URL generation
- 19 unit tests with 100% code coverage
- Fully backwards compatible - optional middleware

Closes common 404 issues when deploying in subdirectories
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants