Search code examples
phpcakephpurl-routingcakephp-3.xapi-versioning

Cake - Setup routing to support versioned API controllers?


I want to be able to support multiple versioned endpoints from my api simultaneously, such as:

/api/v1.1/counties/get
/api/v1.2/counties/get

But in trying to implement the routing for this, a bit perplexed as to how Cake wants this as I keep getting a

Error: Controller class Counties could not be found.

Attempt 1:

Router::scope('/api', function ($routes) {

    $routes->setExtensions(['json']);
    $routes->fallbacks('DashedRoute');

    $versions = [
        1.1
    ];

    foreach ($versions as $version) {
        $routes->scope('/' . $version, function($routes) {

            $routes->resources('Counties', [
                'controller' => 'Counties',
                'prefix' => 'api',
                'map' => [
                    'get' => [
                        'action' => 'get',
                    ]
                ]
            ]);

        }
    }

});

Attempt 2:

Router::scope('/api', function($routes) {

    $routes->scope('/v1.1', function($routes) {
        $routes->resources('Counties', [
            'controller' => 'Counties',
            'map' => [
                'get' => [
                    'action' => 'get'
                ]   
            ]   
        ]); 
    }); 

    $routes->connect(
        '/v1.1/counties/get',
        [   
            'controller' => 'Counties',
            'action' => 'get',
        ]   
    );  
});

The directory structure I'm currently using (which is still open for debate):

src/Controller/Api/V1.1, which would use base controllers from src/Controller/Api and extend them with stub methods to override if needed. Most of my "fat" is in the models.

and src/Controller/Api/V1.1/CountiesController.php has:

namespace App\Controller\Api\V1.1;

class CountiesController extends AppController
{
}

Would appreciate any insight


Solution

  • You cannot use chars like dots in the namespace (folder) structure, as that is invalid PHP.

    What you are looking for is using prefix routing and the path option, so that you can connect prefixes that are valid in namespaces, and supply a custom path (URL segment) for the route, something like:

    Router::prefix('api', function (RouteBuilder $routes) {
        // ...
    
        $routes->prefix('v11', ['path' => '/v1.1'], function (RouteBuilder $routes) {
            $routes->resources('Counties', [
                'map' => [
                    'get' => [
                        'action' => 'get'
                    ]  
                ]
            ]);
        });
    });
    

    That would connect the following routes (you can check the connected routes in the shell via bin/cake routes):

    +---------------------+-----------------------+--------------------------------------------------------------------------------------------------+
    | Route name          | URI template          | Defaults                                                                                         |
    +---------------------+-----------------------+--------------------------------------------------------------------------------------------------+
    | v11:counties:index  | api/v1.1/counties     | {"controller":"Counties","action":"index","_method":"GET","prefix":"v11","plugin":null}          |
    | v11:counties:add    | api/v1.1/counties     | {"controller":"Counties","action":"add","_method":"POST","prefix":"v11","plugin":null}           |
    | v11:counties:view   | api/v1.1/counties/:id | {"controller":"Counties","action":"view","_method":"GET","prefix":"v11","plugin":null}           |
    | v11:counties:edit   | api/v1.1/counties/:id | {"controller":"Counties","action":"edit","_method":["PUT","PATCH"],"prefix":"v11","plugin":null} |
    | v11:counties:delete | api/v1.1/counties/:id | {"controller":"Counties","action":"delete","_method":"DELETE","prefix":"v11","plugin":null}      |
    | v11:counties:get    | api/v1.1/counties/get | {"controller":"Counties","action":"get","_method":"GET","prefix":"v11","plugin":null}            |
    +---------------------+-----------------------+--------------------------------------------------------------------------------------------------+
    

    The CountiesController class would then be expected in

    src/Controller/Api/V11/CountiesController.php
    

    with a namespace of:

    App\Controller\Api\V11
    

    See also