Repository navigation
fix(routes): mint Laravel routes written without a leading slash - #2471
Merged
Merged
Conversation
Closed
2 tasks done
Why: Laravel accepts a route URI with or without its leading slash and trims
'/' from both ends, so Route::get('users', ...) serves /users and
Route::get('', ...) the group root. Most Laravel apps write it that way. Both
route passes only minted a Route for a first argument starting with '/', so
Firefly III's API registrations (246 distinct routes in routes/api.php) had
no Route at all, and neither did laravel.io's PUT/DELETE API routes or 62 of
its 67 registrations.
Fix (extract_calls.c, PHP only; other languages' path gates are untouched):
- A recognised Laravel registration gets its literal URI normalised to the
slashed form before the gates: 'users' -> /users, '' -> /, 'users/' ->
/users. It is recognised by an HTTP verb (get/post/put/patch/delete)
called on the Route facade - directly, or at the root of a registrar chain
such as Route::middleware('auth')->get(...) - or on $router. The literal
`Route` scope is the same one the #952 callee qualification accepts. Any
other call keeps its argument: $request->get('name'), Cache::get('key'),
config(), a collection's or session's get(), $this->router->get(), and a
test client's $this->get('users').
- The normalised path composes with the enclosing in-file prefix groups the
same way as #952. The attribute-array group form
Route::group(['prefix' => 'v1/autocomplete', ...], function () {...}),
which Firefly III's whole API uses, now contributes its prefix too;
without it those routes would mint as /accounts instead of
/v1/autocomplete/accounts.
- A slashless registration whose action is a string ('index' inside a
Route::controller() group, 'UserController@store') is minted without a
handler. The string does not say which class owns the method, and
resolving it could only guess: on krayin/laravel-crm, 'update' and
'destroy' bound functions in a bundled chart.js, and 'store' bound
ActivityController for /login.
Proof (release CLI built from this tree vs origin/main 1f9b4db):
- Routes minted by route registration: firefly-iii 30 -> 651,
laravel.io 39 -> 81, krayin/laravel-crm 5 -> 132. An independent
recomputation of every composed path from the route files matches
exactly: firefly routes/api.php 246/246 and routes/web.php 390/390,
laravel.io web 64/64 and api 3/3, with 0 missing and 0 extra.
- Samples checked against source: GET /v1/autocomplete/accounts and
DELETE /v1/accounts/{account} (firefly), PUT /articles/{article} (laravel.io
api), PUT /admin/articles/{article}/approve (laravel.io web), and
PUT /leads/edit/{id} (krayin).
- No false Routes: every new route-registration edge comes from a Route::
registrar in a route file. The pre-existing test-client and JS-client
route edges are identical to main. HANDLES stays at 0 on all three
corpora, so no handler is guessed.
Tests (edge_types_probe): routes_laravel_slashless_issue1146 and
routes_laravel_slashless_parallel_issue1146 assert the exact method+path set:
facade, facade chain, $router, chain and array prefix groups, '' and a
trailing slash. routes_laravel_slashless_no_junk_issue1146 covers the
non-route string calls. routes_laravel_slashless_no_guessed_handlers_issue1146
checks string actions against JS and PHP decoys. RED on origin/main 3x,
green, RED on revert (the guessed-handler test is also RED with only the
handler gate removed). edge_types_probe, php_lsp, pipeline, parallel,
route_canon and cross_repo pass (731); make lint-ci passes.
Refs #1146
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
DeusData
force-pushed
the
fix/laravel-slashless-route-paths
branch
from
October 4, 2026 16:48
20703a3 to
e066515
Compare
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
DeusData
added a commit
that referenced
this pull request
Oct 7, 2026
…batch Conflict in tests/test_edge_types_probe.c: both sides added a self-contained helper + tests before the Rails test (this branch: et_handles_exact_routes and the class-handler tests; main/#2471: et_route_set_exact and the slashless-route tests) and their RUN_TEST lines; both blocks kept whole, this branch's first. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
DeusData
added a commit
that referenced
this pull request
Oct 7, 2026
Conflict in internal/cbm/extract_calls.c with main's #1146 slashless Laravel routes (#2471): both helper sets kept (this branch's route_prefix_compose/route_path_with_prefix, main's php_plain_string_value/php_first_arg_value/php_group_array_prefix). At the call site main's slashless-URI step runs first, then this branch's shared route_path_with_group_prefix replaces main's inline #952 composition (identical composition and guard), then main's string-action handler rule. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why: Laravel accepts a route URI with or without its leading slash and trims
'/' from both ends, so Route::get('users', ...) serves /users and
Route::get('', ...) the group root. Most Laravel apps write it that way. Both
route passes only minted a Route for a first argument starting with '/', so
Firefly III's API registrations (246 distinct routes in routes/api.php) had
no Route at all, and neither did laravel.io's PUT/DELETE API routes or 62 of
its 67 registrations.
Fix (extract_calls.c, PHP only; other languages' path gates are untouched):
slashed form before the gates: 'users' -> /users, '' -> /, 'users/' ->
/users. It is recognised by an HTTP verb (get/post/put/patch/delete)
called on the Route facade - directly, or at the root of a registrar chain
such as Route::middleware('auth')->get(...) - or on $router. The literal
Routescope is the same one the PHP/Laravel: routes inside Route::prefix()->group() produce no Route nodes #952 callee qualification accepts. Anyother call keeps its argument: $request->get('name'), Cache::get('key'),
config(), a collection's or session's get(), $this->router->get(), and a
test client's $this->get('users').
same way as PHP/Laravel: routes inside Route::prefix()->group() produce no Route nodes #952. The attribute-array group form
Route::group(['prefix' => 'v1/autocomplete', ...], function () {...}),
which Firefly III's whole API uses, now contributes its prefix too;
without it those routes would mint as /accounts instead of
/v1/autocomplete/accounts.
Route::controller() group, 'UserController@store') is minted without a
handler. The string does not say which class owns the method, and
resolving it could only guess: on krayin/laravel-crm, 'update' and
'destroy' bound functions in a bundled chart.js, and 'store' bound
ActivityController for /login.
Proof (release CLI built from this tree vs origin/main 1f9b4db):
laravel.io 39 -> 81, krayin/laravel-crm 5 -> 132. An independent
recomputation of every composed path from the route files matches
exactly: firefly routes/api.php 246/246 and routes/web.php 390/390,
laravel.io web 64/64 and api 3/3, with 0 missing and 0 extra.
DELETE /v1/accounts/{account} (firefly), PUT /articles/{article} (laravel.io
api), PUT /admin/articles/{article}/approve (laravel.io web), and
PUT /leads/edit/{id} (krayin).
registrar in a route file. The pre-existing test-client and JS-client
route edges are identical to main. HANDLES stays at 0 on all three
corpora, so no handler is guessed.
Tests (edge_types_probe): routes_laravel_slashless_issue1146 and
routes_laravel_slashless_parallel_issue1146 assert the exact method+path set:
facade, facade chain, $router, chain and array prefix groups, '' and a
trailing slash. routes_laravel_slashless_no_junk_issue1146 covers the
non-route string calls. routes_laravel_slashless_no_guessed_handlers_issue1146
checks string actions against JS and PHP decoys. RED on origin/main 3x,
green, RED on revert (the guessed-handler test is also RED with only the
handler gate removed). edge_types_probe, php_lsp, pipeline, parallel,
route_canon and cross_repo pass (731); make lint-ci passes.
Refs #1146