Non-GET method routes are not being used to create form actions with passed arguments in 5.x

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!

Either use named routes

or specify the method in postLink()/deleteLink()

  1. Specifying the method in deleteLink() has no effect.
  2. Using a named route still has Route::_matchMethod() default the _method parameter to GET, and the route does not get matched:

Missing Route

Cake\Routing\Exception\MissingRouteException

Error A named route was found for `deleteMyThings`, but matching failed.

None of the currently connected routes match the provided parameters. Add a matching route to config/routes.php


Only adding GET to the route’s methods allow it to get matched: setMethods(['GET', 'DELETE'])

Could you please explain your two suggestions further? And explain why Route::_matchMethod() is defaulting to set _method to GET?

The mistake is placing method in the third argument ($options). That controls the submitted form’s method, but does not tell the router which method to use when generating its action URL.

<?= $this->Form->deleteLink(
    'Delete',
    [
        'action' => 'delete',
        'firstArg' => 'XXXXX',
        'secondArg' => 'YYYYY',
    ],
    [
        'method' => 'DELETE',
        'confirm' => 'Are you sure?',
    ],
) ?>

Correct: put _method in the second argument ($url):

<?= $this->Form->deleteLink(
    'Delete',
    [
        'action' => 'delete',
        'firstArg' => 'XXXXX',
        'secondArg' => 'YYYYY',
        '_method' => 'DELETE',
    ],
    ['confirm' => 'Are you sure?'],
) ?>
<?= $this->Form->deleteLink(
    'Delete',
    [
        '_name' => 'deleteMyThings',
        'firstArg' => 'XXXXX',
        'secondArg' => 'YYYYY',
        '_method' => 'DELETE',
    ],
    ['confirm' => 'Are you sure?'],
) ?>
1 Like

Thank you!

Your solution to add '_method' => 'DELETE' to the $url argument has worked, the route is being matched, and my form action is being generated correctly.

I’ve searched the docs at GitHub - cakephp/docs: CakePHP CookBook · GitHub for “_method”, but I don’t see any instructions on using this as an option to $url. Perhaps I’ll open a ticket to have the docs updated.

1 Like