I’m using CakePHP 5.3.2
I have a (scoped) route for deleting, created with the route builder to have 2 route elements and to accept the DELETE HTTP method:
$builder->connect('/delete', ['controller' => 'MyThings', 'action' => 'delete'], ['pass' => ['firstArg', 'secondArg']])->setMethods(['DELETE']);
This successfully creates the route named “mythings:delete” with the pattern:
/my-department/my-parent-things/{firstArg}/my-things/{secondArg}/delete
When I create a deleteLink with the $url argument set like so:
<?= $this->Form->deleteLink(
'Delete',
['action' => 'delete', 'firstArg' => 'XXXXX', 'secondArg' => 'XXXXX'],
['confirm' => 'Are you sure?'])
?>
the form action that is created does not use the route above but defaults to action=“/my-things/delete”, following the routeTable route name “_controller:_action”.
<form name="post_XXXXX" method="post" style="display:none;" action="/my-things/delete">
<input type="hidden" name="_method" value="DELETE">
<input type="hidden" name="_csrfToken" value="XXXXX">
</form >
In my controller, I can’t use:
public function delete(string $firstArg, string $secondArg): ?Response {
//
}
because of course the arguments are not being passed. I have to add request data to the form to know which record to delete.
I dug into the Routing source files and discovered that if a route URL does not have a “_method” set, and it seems that no routes have this, the “_method” will be set by default to GET (line 793):
In /cakephp/cakephp/blob/5.x/src/Routing/Route/Route.php:
protected function _matchMethod(array $url): bool
{
if (empty($this->defaults['_method'])) {
return true;
}
if (empty($url['_method'])) {
$url['_method'] = 'GET';
}
$defaults = (array)$this->defaults['_method'];
$methods = (array)$this->normalizeAndValidateMethods($url['_method']);
foreach ($methods as $value) {
if (in_array($value, $defaults, true)) {
return true;
}
}
return false;
}
Then if the route does not include the GET method among its defaults, it will be skipped over during the route name matching above, and the more general routeTable route name (like “_controller:_action”) will be used instead. By contrast, a route created with a GET method using e.g. setMethods([‘GET’, ‘PUT’]) will not be skipped over and the proper route link will be created.
Aside from the DELETE-only route above, I have a number of others that only allow POST or PUT methods in the route builder (no GET), and these are also skipped over during form action creation to default to “_controller:_action” without the passed arguments. I also need to add the identifying values into the form’s request data to know which records to update.
Could you explain:
- how to use non-GET routes for forms with passed arguments, especially postLink and deleteLink?
- why the Route::_matchMethod() above only allows matching GET methods?
Thanks!