CakePHP3 mapping RESTFul routes - rest

I'm trying to config a RESTful API resources mapping in CakePHP3. I followed some tuts but can't make it works.
https://book.cakephp.org/3.0/en/development/routing.html#creating-restful-routes
http://www.bravo-kernel.com/2015/04/how-to-prefix-route-a-cakephp-3-rest-api/
I'm using 'prefix' route to map /api/v2/:resource with my sub-folders inside Controllers folder.
My file structure:
And this is my routing config
Router::prefix('api/v2', function ($routes) {
$routes->resources('Users');
// $routes->get('users', ['controller' => 'Users', 'action' => 'view']);
// $routes->post('users', ['controller' => 'Users', 'action' => 'create']);
// $routes->post('token', ['controller' => 'Users', 'action' => 'token']);
// $routes->fallbacks(DashedRoute::class);
});
As I read in the tuts i mention before, this should works, but i get a Missing Contoller Exception for ApiController.
If I uncomment the last line, enabling fallbacks it works fine, but it's not matching the Controller Methods with the HTTP method GET, POST, DELETE, PUT as CakePHP3 documentation mention.
Any ideas? My Cake version in 3.5.8
Thanks!!
EDIT: using bin/cake routes it's seems like the routes are fine. I'm using Postman to make request with differents HTTP methods to test this.
EDIT 2: I tried with another prefix Foo to avoid the V2 case sensitive issue, and well, this is extrage, the routes seems to be fine, but cake are not matching any of them..

Your folder name should be V2 not v2.

Related

Problem with plugin routes and multilanguage in a Cakephp4

I've built a multilanguage NewsManager plugin that has a NewsController
I'm try to write routes in this plugin to be able to access to routes like /en/news-manager/news/, /en/news-manager/news/my-lastest-news, ...
Here is my code :
// in /plugins/NewsManager/config/routes.php
$routes->scope('/{lang}', function (RouteBuilder $routes) {
$routes->plugin('NewsManager', function (RouteBuilder $routes) {
$routes->connect('/news', ['controller' => 'News', 'action' => 'index'])
->setPatterns([
'lang' => 'en|fr'
])
->setPersist(['lang']);
$routes->connect('/news/{slug}', ['controller' => 'News', 'action' => 'view'])
->setPass(['slug'])
->setPatterns([
'lang' => 'en|fr'
])
->setPersist(['lang']);
});
});
When I try to access to /en/news-manager/news/ I have a Missing Controller error with the message NewsManagerController could not be found.
What am I doing wrong ?
(Note that when I write exactly the same code that the code above but in my App routes it works...)
For parsing requests, routes are being ordered by their template's static path portion, which is the porition of the template before the first routing element.
So in your case the static portion would be / for all the language routes, as they immediately start with the {lang} routing element. This will result in a first comes first served order, meaning since your fallback routes are being connected before the plugin routes, they will be processed and matched first, making your plugin routes inaccessible.
As mentioned in the comments, one solution would obviously be to remove the fallback routes. This isn't an uncommon thing to do, as bailing out early at routes lookup will avoid unneccsary processing until the code runs into a problem at controller lookup later on.
Another way would be to connect the fallback routes after the plugin routes. Plugin route loading will always be invokved after application routes loading, so the pretty much only way to achieve this, is to move connecting the respective application routes into the plugin routes loading mechanism, which has a slight workaround smell.
Basically, move those routes into Application::pluginRoutes() to after invoking the parent method:
// in src/Application.php
public function pluginRoutes(\Cake\Routing\RouteBuilder $routes): \Cake\Routing\RouteBuilder
{
// connect plugin routes
$routes = parent::pluginRoutes($routes);
// connect fallback routes
$routes->scope('/{lang}', function (\Cake\Routing\RouteBuilder $routes) {
$routes->fallbacks();
});
return $routes;
}

Get nested ressources when using cakePhp restfull

I'm using cakePhp to create a Rest api (see http://book.cakephp.org/2.0/fr/development/rest.html) and I need to get nested resources. The documentation tells how to get let's say books implementing a URI /books.json. But does not tell how to get for example reviews for a given book. What I'm trying to make is somthing like this: /books/14/reviews.json that returns Review resources.
Can any one tell me hwo to make this?
See the Custom REST Routing section of the docs you've linked. In case the default routing doesn't work for you, you'll have to create your own custom routes that either replace or extend the default ones.
Your /books/14/reviews.json URL could for example be mapped to BooksController::reviews() likes this:
Router::connect(
'/books/:id/reviews',
array(
'[method]' => 'GET',
'controller' => 'books',
'action' => 'reviews'
),
array(
'id' => Router::ID . '|' . Router::UUID,
'pass' => array(
'id'
)
)
);
When placed before Router::mapResources() it should work fine together with the default routes.

Use route prefix with RESTful routes in CakePHP

Working on building an API and would like to use RESTful routes.
I got it to work just fine like this:
http://www.mysite.com/events.json // returns json results with my events
http://www.mysite.com/events/123.json // returns json results with event of id '123'
BUT - I want to be able to do this using an 'api' prefix.
So, I added the api Routing prefix:
Configure::write('Routing.prefixes', array('admin', 'api'));
And changed my actions from 'view' and 'index' to 'api_view' and 'api_index'.
But now it doesn't work. (eg. I have to write the action name or it won't find the correct one based on HTTP.
The end goal would be to be able to do something like this:
GET http://www.mysite.com/api/1.0/events.json // loads events/api_index()
GET http://www.mysite.com/api/1.0/events/123.json // loads events/api_view($id)
DELETE http://www.mysite.com/api/1.0/events/123.json // loads events/api_delete($id)
...etc
I ended up having to just write the routes manually:
Router::parseExtensions('json', 'xml');
Router::connect('/api/:version/:controller/:id/*',
array('[method]'=>'GET', 'prefix'=>'api', 'action'=>'view'),
array('version'=>'[0-9]+\.[0-9]+', 'id'=>'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'));
Router::connect('/api/:version/:controller/*',
array('[method]'=>'GET', 'prefix'=>'api', 'action'=>'index'),
array('version'=>'[0-9]+\.[0-9]+'));
Router::connect('/api/*', array('controller'=>'events', 'action'=>'index', 'ext'=>'html'));
Notes:
The [method] is what forces the HTTP type (eg. RESTful)
The parseExtensions() makes it so you can have it display the data in different formats automatically by changing the extension in your URL.
The last Router:: line was just a catchall for anything /api/ that didn't match - it forwarded it to the homepage. Eventually I'll probably just route this to an API error page.
The 'ext'=>'html' of the last Router:: line was to keep parseExtensions from trying to use whatever extension was in the URL - if it's redirecting for reasons they made the call wrong, I just want it to go back to the homepage (or whatever) and use the normal view.
Try something like this.
Router::connect('/:api/:apiVersion/:controller/:action/*',
array(),
array(
'api' => 'api',
'apiVersion' => '1.0|1.1|'
)
);
With prefix routing
Router::connect('/:prefix/:apiVersion/:controller/:action/*',
array(),
array(
'prefix' => 'api',
'apiVersion' => '1.0|1.1|'
)
);
Will match only valid API versions like 1.0 and 1.1 here. If you want something else use a regex there.
I know this is an old post, but there is a routing method called mapResources which creates the special method based routing for you.
http://book.cakephp.org/2.0/en/development/rest.html
You put it in routes.php like so:
Router::mapResources(array('controller1', 'controller2'));
The docs have a nice little table showing how the requests are mapped to different actions, which you can always override if you need to.

Zend-Route for ajax API

I'm trying to add a route to my application, so that I can use it with ajax calls.
Here is what I have in my application.ini
;Routes
resources.router.routes.products.route = "/backend/api/:command"
resources.router.routes.products.defaults.module = "backend"
resources.router.routes.products.defaults.controller = "api"
resources.router.routes.products.defaults.action = "index"
When a ajax call is made, to /backend/api/SomeCommand, the following error is produced:
Message: Invalid controller specified (backend)
array (
'controller' => 'backend',
'action' => 'maestro',
'module' => 'default',
)
as you can see module has been set to "default", instead of "backend", and controller is "backend" instead of "api", what could have caused this?
Looks like you've got another more generic route defined after this one that's matching the request.
You need to define your routes in order of least to most specific, specificity usually being improved by the presence of fixed terms like your backend/api prefix.
See Basic Rewrite Router Operation, in particular
Note: Reverse Matching
Routes are matched in reverse order so make sure your most generic routes are defined first.
FYI: You don't need to prefix your routes with a forward-slash

Zend Framework - routes - all requests to one controller except requests for existing controllers

How to create route that accept all requests for unexsting controllers, but leave requests for existing.
This code catch all routes
$route = new Zend_Controller_Router_Route_Regex('(\w+)', array('controller' => 'index', 'action' => 'index'));
$router->addRoute('index', $route);
how should I specify route requests like /admin/* or /feedback/* to existing adminController or feedbackController?
You should not create a route to handle that. The error controller will take care of all three kinds of the following errors:
Controller does not exist
Action does not exsist
No route matched
Take a look at the documentation on how to use it correctly:
http://framework.zend.com/manual/en/zend.controller.plugins.html#zend.controller.plugins.standard.errorhandler.fourohfour
I found only the way - not to add route in case current request is about admin area
$request = $frontController->getRequest();
if (!preg_match('/knownController/', $request->getRequestUri())){
$router->addRoute('index', new Zend_Controller_Router_Route_Regex('(.*)', array('controller' => 'index', 'action' => 'index')));
}
You can also use the ErrorController to do a similar thing. Maybe if you dig into the way they implement the plugin it will help you build something that meets your needs closely?