Skip to content

Commit a68c113

Browse files
committed
fix: patch up router base path issues
1 parent c076298 commit a68c113

2 files changed

Lines changed: 78 additions & 15 deletions

File tree

src/Router.php

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,9 @@ class Router
9898
protected static $namespace = '';
9999

100100
/**
101-
* The Server Base Path for Router Execution
101+
* The Server Base Path for Router Execution (null = not yet resolved)
102102
*/
103-
protected static $serverBasePath = '';
103+
protected static $serverBasePath = null;
104104

105105
/**
106106
* Cached URI for the current request
@@ -787,8 +787,17 @@ public function registerMiddleware(string $name, callable $middleware)
787787
*/
788788
public static function getBasePath(): string
789789
{
790-
if (static::$serverBasePath === '') {
791-
static::$serverBasePath = implode('/', array_slice(explode('/', $_SERVER['SCRIPT_NAME']), 0, -1)) . '/';
790+
if (static::$serverBasePath === null) {
791+
$scriptDir = implode('/', array_slice(explode('/', $_SERVER['SCRIPT_NAME'] ?? ''), 0, -1)) . '/';
792+
$requestPath = rawurldecode(parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/');
793+
794+
// the script directory is only a base path when the request actually
795+
// lives under it (classic subfolder deployments) — under php -S or
796+
// the CLI it usually isn't, and stripping it would eat URI segments
797+
// https://github.com/leafsphp/leaf/issues/323
798+
static::$serverBasePath = ($scriptDir !== '/' && strncmp($requestPath, $scriptDir, strlen($scriptDir)) === 0)
799+
? $scriptDir
800+
: '/';
792801
}
793802

794803
return static::$serverBasePath;
@@ -802,7 +811,7 @@ public static function getBasePath(): string
802811
*/
803812
public static function setBasePath($serverBasePath)
804813
{
805-
static::$serverBasePath = $serverBasePath;
814+
static::$serverBasePath = ($serverBasePath === '' || $serverBasePath === null) ? '/' : $serverBasePath;
806815
static::$currentUri = null;
807816
}
808817

@@ -822,16 +831,11 @@ public static function getCurrentUri(): string
822831
$requestPath = parse_url($requestUri, PHP_URL_PATH) ?: '/';
823832
$requestPath = rawurldecode($requestPath);
824833

825-
// Early exit If base path doesn't match
834+
// if an explicit base path doesn't prefix the request, don't strip
835+
// anything (and never fire handlers from a getter) — unmatched routes
836+
// 404 through the normal dispatch flow
826837
if (strncmp($requestPath, $basePath, strlen($basePath)) !== 0) {
827-
if (!static::$notFoundHandler) {
828-
static::$notFoundHandler = function () {
829-
\Leaf\Exception\General::default404();
830-
};
831-
}
832-
static::invoke(static::$notFoundHandler);
833-
834-
return '/';
838+
return static::$currentUri = '/' . trim($requestPath, '/');
835839
}
836840

837841
// Get the current Request URI and remove rewrite base path from it (= allows one to run the router in a sub folder)
@@ -1107,7 +1111,7 @@ public static function reset(): void
11071111
static::$sitemapOptions = [];
11081112
static::$groupRoute = '';
11091113
static::$namespace = '';
1110-
static::$serverBasePath = '';
1114+
static::$serverBasePath = null;
11111115
static::$currentUri = null;
11121116
static::$currentMethod = null;
11131117
}

tests/router.test.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,62 @@
209209

210210
expect(app()->config('testKey.hooked'))->toBe('original');
211211
});
212+
213+
test('base path is not auto-stripped when the request does not live under the script dir', function () {
214+
// https://github.com/leafsphp/leaf/issues/323 — php -S serving from a subdir
215+
\Leaf\Router::reset();
216+
$_SERVER['SCRIPT_NAME'] = '/public/index.php';
217+
$_SERVER['REQUEST_METHOD'] = 'GET';
218+
$_SERVER['REQUEST_URI'] = '/practices/42';
219+
220+
$matched = null;
221+
app()->get('/practices/{id}', function ($id) use (&$matched) {
222+
$matched = $id;
223+
});
224+
225+
app()->run();
226+
227+
expect(\Leaf\Router::getBasePath())->toBe('/')
228+
->and($matched)->toBe('42');
229+
});
230+
231+
test('base path is stripped for real subfolder deployments', function () {
232+
\Leaf\Router::reset();
233+
$_SERVER['SCRIPT_NAME'] = '/subdir/index.php';
234+
$_SERVER['REQUEST_METHOD'] = 'GET';
235+
$_SERVER['REQUEST_URI'] = '/subdir/users/5';
236+
237+
$matched = null;
238+
app()->get('/users/{id}', function ($id) use (&$matched) {
239+
$matched = $id;
240+
});
241+
242+
app()->run();
243+
244+
expect(\Leaf\Router::getBasePath())->toBe('/subdir/')
245+
->and($matched)->toBe('5');
246+
});
247+
248+
test('setBasePath with an empty string means no base path', function () {
249+
\Leaf\Router::reset();
250+
app()->setBasePath('');
251+
252+
expect(\Leaf\Router::getBasePath())->toBe('/');
253+
});
254+
255+
test('getCurrentUri never fires the 404 handler as a side effect', function () {
256+
\Leaf\Router::reset();
257+
$_SERVER['REQUEST_METHOD'] = 'GET';
258+
$_SERVER['REQUEST_URI'] = '/outside';
259+
app()->setBasePath('/api/');
260+
261+
$fired = false;
262+
app()->set404(function () use (&$fired) {
263+
$fired = true;
264+
});
265+
266+
$uri = \Leaf\Router::getCurrentUri();
267+
268+
expect($fired)->toBeFalse()
269+
->and($uri)->toBe('/outside'); // path untouched when base doesn't prefix it
270+
});

0 commit comments

Comments
 (0)