.json ext giving Missing Method

I use ajax to load some JSON from a method in a controller.

However I am getting error 404 returned

If I remove the .json extension I get error 500 missing template which I have not been able to resolve either. I have tried different solutions and I have posted on SO. The install is pretty clean and I dont think it is something that I am doing wrong.

Missing Method in StrategiesConditionsController
Cake\Controller\Exception\MissingActionException

Error The action conditions.json is not defined in StrategiesConditionsController

Create StrategiesConditionsController::conditions.json() in file: src/Controller/StrategiesConditionsController.php.

From reading the doc a couple of times I am sure I have the routes correct.

routes.php

<?php
/**
 * Routes configuration.
 *
 * In this file, you set up routes to your controllers and their actions.
 * Routes are very important mechanism that allows you to freely connect
 * different URLs to chosen controllers and their actions (functions).
 *
 * It's loaded within the context of `Application::routes()` method which
 * receives a `RouteBuilder` instance `$routes` as method argument.
 *
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 * @link          https://cakephp.org CakePHP(tm) Project
 * @license       https://opensource.org/licenses/mit-license.php MIT License
 */

use Cake\Http\Middleware\CsrfProtectionMiddleware;
use Cake\Routing\Route\DashedRoute;
use Cake\Routing\RouteBuilder;
use Cake\Routing\Router;

/*
 * The default class to use for all routes
 *
 * The following route classes are supplied with CakePHP and are appropriate
 * to set as the default:
 *
 * - Route
 * - InflectedRoute
 * - DashedRoute
 *
 * If no call is made to `Router::defaultRouteClass()`, the class used is
 * `Route` (`Cake\Routing\Route\Route`)
 *
 * Note that `Route` does not do any inflections on URLs which will result in
 * inconsistently cased URLs when used with `:plugin`, `:controller` and
 * `:action` markers.
 */
/** @var \Cake\Routing\RouteBuilder $routes */
$routes->setRouteClass(DashedRoute::class);

$routes->scope('/', function (RouteBuilder $routes) {
    $routes->setExtensions(['json']);
    $routes->resources('StrategiesConditions', [
        'map' => [
            'conditions' => [
                'action' => 'conditions',
                'method' => 'post'
            ]
        ],
        'only' => ['conditions']
     ]);
});

$routes->scope('/', function (RouteBuilder $builder) {
    // Register scoped middleware for in scopes.
    $builder->registerMiddleware('csrf', new CsrfProtectionMiddleware([
        'httpOnly' => true,
    ]));

    /*
     * Apply a middleware to the current route scope.
     * Requires middleware to be registered through `Application::routes()` with `registerMiddleware()`
     */
    $builder->applyMiddleware('csrf');

    /*
     * Here, we are connecting '/' (base path) to a controller called 'Pages',
     * its action called 'display', and we pass a param to select the view file
     * to use (in this case, templates/Pages/home.php)...
     */
    $builder->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);

    /*
     * ...and connect the rest of 'Pages' controller's URLs.
     */
    $builder->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']);

    /*
     * Connect catchall routes for all controllers.
     *
     * The `fallbacks` method is a shortcut for
     *
     * ```
     * $builder->connect('/:controller', ['action' => 'index']);
     * $builder->connect('/:controller/:action/*', []);
     * ```
     *
     * You can remove these routes once you've connected the
     * routes you want in your application.
     */
    $builder->fallbacks();
});

Router::prefix('admin', function (RouteBuilder $routes) {
    $routes->connect('/', ['controller' => 'Users', 'action' => 'index']);
    $routes->fallbacks(DashedRoute::class);
});

/*
 * If you need a different set of middleware or none at all,
 * open new scope and define routes there.
 *
 * ```
 * $routes->scope('/api', function (RouteBuilder $builder) {
 *     // No $builder->applyMiddleware() here.
 *     // Connect API actions here.
 * });
 * ```
 */

strategiesConditionsController

public function conditions()
{
    $this->request->allowMethod(['post']);

    if ($this->request->is('ajax')) {
        $this->response = $this->response->withDisabledCache();
    }

    $strategy_id = $this->request->getData('strategy_id');
    
    $strategiesConditions = $this->StrategiesConditions->find('all', [
        'where' => ['strategy_id' => $strategy_id],
    ]);
    $this->viewBuilder()->setOption('serialize', ['strategiesConditions']);
}

Cakephp console has 8 routes showing

Route name	URI template	Defaults
pages:display	/	
{
    "controller": "Pages",
    "action": "display",
    "0": "home",
    "plugin": null
}
pages:display	/pages/*	
{
    "controller": "Pages",
    "action": "display",
    "plugin": null
}
_controller:index	/{controller}	
{
    "action": "index",
    "plugin": null
}
_controller:_action	/{controller}/{action}/*	
{
    "plugin": null,
    "action": "index"
}
admin:users:index	/admin	
{
    "controller": "Users",
    "action": "index",
    "prefix": "Admin",
    "plugin": null
}
admin:_controller:index	/admin/{controller}	
{
    "action": "index",
    "prefix": "Admin",
    "plugin": null
}
admin:_controller:_action	/admin/{controller}/{action}/*	
{
    "prefix": "Admin",
    "plugin": null,
    "action": "index"
}
strategiesconditions:conditions	/strategies-conditions/conditions	
{
    "controller": "StrategiesConditions",
    "action": "conditions",
    "_method": "post",
    "plugin": null
}

Update from the history tab in debug kit

Request
Routing Params
controller StrategiesConditions
action conditions.json
pass (empty)
plugin (null)
_matchedRoute /{controller}/{action}/*
_ext (null)
Post data
strategy_id 11
Query string
No querystring data.

Cookie
csrfToken dd12f852560a384d39206e511f1857f77f71da2eadb023a6c67ae346
PHPSESSID 3d2jt7dgpo09af8b1908quljpn
Matched Route
template /{controller}/{action}/*

and the routes tab:

Routes
Toggle debugkit internal routes
Route name	URI template	Defaults
pages:display	/	
{
    "controller": "Pages",
    "action": "display",
    "0": "home",
    "plugin": null
}
pages:display	/pages/*	
{
    "controller": "Pages",
    "action": "display",
    "plugin": null
}
_controller:index	/{controller}	
{
    "action": "index",
    "plugin": null
}
_controller:_action	/{controller}/{action}/*	
{
    "plugin": null,
    "action": "index"
}
admin:users:index	/admin	
{
    "controller": "Users",
    "action": "index",
    "prefix": "Admin",
    "plugin": null
}
admin:_controller:index	/admin/{controller}	
{
    "action": "index",
    "prefix": "Admin",
    "plugin": null
}
admin:_controller:_action	/admin/{controller}/{action}/*	
{
    "prefix": "Admin",
    "plugin": null,
    "action": "index"
}
strategiesconditions:conditions	/strategies-conditions/conditions	
{
    "controller": "StrategiesConditions",
    "action": "conditions",
    "_method": "post",
    "plugin": null
}

Request URL

http://localhost:8888/trading-journal/strategies-conditions/conditions.json

Application.php

        // Add routing middleware.
        // If you have a large number of routes connected, turning on routes
        // caching in production could improve performance. For that when
        // creating the middleware instance specify the cache config name by
        // using it's second constructor argument:
        // `new RoutingMiddleware($this, '_cake_routes_')`
        ->add(new RoutingMiddleware($this))

As stated in the book

you have to put

Router::extensions(['json']);

before the routes you want to response as json

That is only if you want to set the extension as a global. " Defining global extensions can be achieved via the routers static Router::extensions() method:"

" In order to restrict extensions to specific scopes, you can define them using the Cake\Routing\RouteBuilder::setExtensions() method:"

This is what I want.

Just as a note. Adding Router::extensions(['json']); to the top of the routes.php still gives me a 404

To follow up on this as I handled the github issue. The problem was that method was post and not POST. I made a change to the Route constructor so that is automatically normalizes HTTP method names as this is an annoying edge case to hit and I don’t think it should exist anymore.

1 Like