Define Cakephp Route to call specific controller if given action not exists - php

Using CakePHP v3.3.16
I want to write a fallback route in such a way that if URL is not connected to any action then it should go to that fallback.
Created routes for SEO friendly URL like this
$routes->connect(
':slug',
['prefix'=>'website','controller' => 'Brands', 'action' => 'index'],
['routeClass' => 'DashedRoute']
);
$routes->connect(
':slug/*',
['prefix'=>'website','controller' => 'Products', 'action' => 'index'],
['routeClass' => 'DashedRoute']
);
But it's also inclute all the controller actions in it so if i try to call a controller ex: cart/index it's going to website/brands/index/index
If I have to remove exclude it, I have to create a route like this/
$routes->connect('/cart',['controller' => 'Cart'], ['routeClass' => 'DashedRoute']);
And so on to the other controller to access.
Example:
I have a controller CartController action addCart
CASE 1
if I access URL my_project/cart/addCart/ It should go to cart controller action
CASE 2
if I access URL my_project/abc/xyz/ and there is no controller named abc so it should go to BrandsController action index
My Current routes.php looks like this
Router::defaultRouteClass(DashedRoute::class);
Router::scope('/', function (RouteBuilder $routes) {
$routes->connect('/', ['prefix'=>'website','controller' => 'Home', 'action' => 'index']);
$routes->connect('/trending-brands', ['prefix'=>'website','controller' => 'Brands', 'action' => 'trending']);
$routes->connect('/users/:action/*',['prefix'=>'website','controller' => 'Users'], ['routeClass' => 'DashedRoute']);
$routes->connect('/cart',['prefix'=>'website','controller' => 'Cart'], ['routeClass' => 'DashedRoute']);
$routes->connect('/cart/:action/*',['prefix'=>'website','controller' => 'Cart'], ['routeClass' => 'DashedRoute']);
$routes->connect(
':slug',
['prefix'=>'website','controller' => 'Brands', 'action' => 'index'],
['routeClass' => 'DashedRoute']
);
$routes->connect(
':slug/*',
['prefix'=>'website','controller' => 'Products', 'action' => 'index'],
['routeClass' => 'DashedRoute']
);
$routes->connect(':controller', ['prefix'=>'website'], ['routeClass' => 'DashedRoute']);
$routes->connect(':controller/:action/*', ['prefix'=>'website'], ['routeClass' => 'DashedRoute']);
$routes->fallbacks(DashedRoute::class);
});
Router::prefix('website', function (RouteBuilder $routes) {
$routes->fallbacks(DashedRoute::class);
});
Plugin::routes();

Your edits totally invalidate my first answer, so I decided to just post another answer.
What you want to achieve can not be done by the router because there is no way for router to tell if the controller/action exists for a particular route. This is because the router just work with url templates and delegates the task of loading Controllers up in the request stack.
You can emulate this feature though by using a Middleware
that check if the request attribute params is set and then validate them as appropriate and changing them to your fallback controller if the target controller doesn't exist.
Just make sure to place the RoutingMiddleware execute before your middleware otherwise you will have no params to check because the routes wouldn't have been parsed.
Middlewares implement one method __invoke().
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Cake\Http\ControllerFactory;
use Cake\Routing\Exception\MissingControllerException;
class _404Middleware {
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
{
//$params = (array)$request->getAttribute('params', []);
try {
$factory = new ControllerFactory();
$controller = $factory->create($request, $respond);
}
catch (\Cake\Routing\Exception\MissingControllerException $e) {
// here you fallback to your controllers/action or issue app level redirect
$request = $request->withAttribute('params',['controller' =>'brands','action' => 'index']);
}
return $next($request, $response);
}
}
Then attach in your App\Application see also attaching middlewares
$middlewareStack->add(new _404Middleware());
please remmber to clean up the code as I didn't test it under real dev't enviroment.
After thought: if your were looking to create an error page for all not found resources, then you don't need all that, instead you would just customise the error template in Template/Error/error404.ctp.

If I have to remove exclude it, I have to create a route like this/ And so on to the other controller to access.
There is no need to specify every controller/name in the pattern, instead you can create all your specify routes first and then place the following route below them to catch all unmatched routes.
$routes->connect(':/prefix/:controller/:action/*', [], ['routeClass' => 'DashedRoute']);
And maybe another one without prefix
$routes->connect('/:controller/:action/*', [], ['routeClass' => 'DashedRoute']);
And since you mentioned that you are using 3.3.* cake version, Your routes can be written taking advatage of routes scoping
Router::prefix('website', function ($routes) {
// All routes here will be prefixed with `/prefix`
// And have the prefix => website route element added.
$routes->fallbacks(DashedRoute::class);
$routes->connect(
'/:slug',
['controller' => 'Brands', 'action' => 'index']
);
$routes->connect(
':slug/*',
['controller' => 'Products', 'action' => 'index']
});

Related

Routes doesn't connect the right controller

I'm trying to load the VideoController.php when url.com/video/ is called.
Here is a piece of code from my routes.php
Router::scope('/', function (RouteBuilder $routes) {
$routes->connect('/video/*', ['controller' => 'Video', 'action' => 'display']);
...
$routes->fallbacks(DashedRoute::class);
});
The result is '404 not found'
EDIT:
Inside Router::scope I have also this piece of code:
$routes->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
When I change the Pages value into Video, the VideoController is called. So how come when I change the '/' into '/video/*' it doesn't work?
Maybe it's something with the scope function parameters?
Enabling URL rewriting from the httpd.config file solved the problem.
cakephp documentation

Login custom route being rejected by Auth

Router::scope('/:club_slug', function ($routes) {
$routes->connect('/login', ['controller' => 'Users', 'action' => 'login']);
});
So when I'm trying access http://example.com/club-name/login, I'm being redirected to http://example.com/users/login with the flash message You have to login to access this area.
Auth loginAction is [controller => 'Users', 'action' => 'login'], since the custom route that I mentioned at beginning of the question is pointing to the path that is specified at loginAction I thought the route will know that I'm talking about the same thing but is not what is happening.
Dynamic route elements are not being added/recognized automatically, you'll either have to persist them using either the persist option (applies only to that specific route):
Router::scope('/:club_slug', function ($routes) {
$routes->connect(
'/login',
['controller' => 'Users', 'action' => 'login'],
['persist' => ['club_slug']]
);
});
or URL filters (affects all routes that are using a club_slug element):
Router::addUrlFilter(function ($params, $request) {
if (isset($request->params['club_slug']) && !isset($params['club_slug'])) {
$params['club_slug'] = $request->params['club_slug'];
}
return $params;
});
or you have to pass the element to your login action manually (this would match the club_slug route regardless of the current URL):
'loginAction' => [
'controller' => 'Users',
'action' => 'login',
'club_slug' => 'club-slug' // wherever it may have come from
]
See also
Cookbook > Routing > Creating Persistent URL Parameters
API > Cake\Routing\RouteBuilder::connect()

CakePHP 3: Missing route error for route that exists

CakePHP 3.0
I'm getting a "Missing Route" error for a route that exists.
Here are my routes:
#my admin routes...
Router::prefix('admin', function($routes) {
$routes->connect('/', ['controller'=>'Screens', 'action'=>'index']);
$routes->connect('/screens', ['controller'=>'Screens', 'action'=>'index']);
$routes->connect('/screens/index', ['controller'=>'Screens', 'action'=>'index']);
//$routes->fallbacks('InflectedRoute');
});
Router::scope('/', function ($routes) {
$routes->connect('/login', ['controller' => 'Pages', 'action' => 'display', 'login']);
$routes->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']);
$routes->fallbacks('InflectedRoute');
});
Plugin::routes();
Basically I just added the top section (for admin routing) to the default routes that come out of the box.
When I visit /admin/screens/index I see the following error:
Notice the error message says:
Error: A route matching "array ( 'action' => 'add', 'prefix' =>
'admin', 'plugin' => NULL, 'controller' => 'Screens', '_ext' => NULL,
)" could not be found.
...which is strange because I am not trying to access the add action. The params printed below look correct.
What is going on?
Take a closer look at the stacktrace, the error dosn't occour in the dispatching process, which you seem to think, it is being triggered in your view template, where you are probably trying to create a link to the add action, and reverse-routing cannot find a matching route, hence the error.
The solution should be obvious, connect the necessary routes, being it explicit ones like
$routes->connect('/screens/add', ['controller' => 'Screens', 'action' => 'add']);
catch-all ones
$routes->connect('/screens/:action', ['controller' => 'Screens']);
or simply the fallback ones that catch everything
$routes->fallbacks('InflectedRoute');
This work for me in case of use prefix admin :-
Router::prefix('admin', function ($routes) {
// Because you are in the admin scope,
// you do not need to include the /admin prefix
// or the admin route element.
$routes->connect('/', ['controller' => 'Users', 'action' => 'index']);
$routes->extensions(['json', 'xml']);
// All routes here will be prefixed with `/admin`
$routes->connect('/admin', ['controller' => 'Order', 'action' => 'index']);
// And have the prefix => admin route element added.
$routes->fallbacks(DashedRoute::class);
});

Laravel rename routing resource path names

Can we rename routing resource path names in Laravel like in Ruby on Rails?
Current
/users/create -> UsersController#create
/users/3/edit -> UsersController#edit
..
I want like this;
/users/yeni -> UsersController#create
/users/3/duzenle -> UsersController#edit
I want to do this for localization.
Example from Ruby on Rails;
scope(path_names: { new: "ekle" }) do
resources :users
end
I know this is an old question. I'm just posting this answer for historical purposes:
Laravel now has the possibility to localize the resources. https://laravel.com/docs/5.5/controllers#restful-localizing-resource-uris
Localizing Resource URIs By default, Route::resource will create
resource URIs using English verbs. If you need to localize the create
and edit action verbs, you may use the Route::resourceVerbs method.
This may be done in the boot method of your AppServiceProvider:
use Illuminate\Support\Facades\Route;
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot() {
Route::resourceVerbs([
'create' => 'crear',
'edit' => 'editar',
]); }
Once the verbs have been customized, a resource route registration such as Route::resource('fotos', 'PhotoController') will
produce the following URIs:
/fotos/crear
/fotos/{foto}/editar
It ain't pretty, but you could define multiple routes that use the same controller function. For example:
Route::get("user/create", "UsersController#create");
Route::get("user/yeni", "UsersController#create");
The only (glaringly obvious downside) being that you're routes will get quite cluttered quite quickly. There is a setting in app/config/app.php where you can set/change your locale, and it could be possible to use that in conjunction with a filter to use the routes and then group those routes based on the current local/language, but that would require more research.
As far as I know, there isn't a way to rename resource routes on the fly, but if you get creative you can figure something out. Best of luck!
You can't change the resource url's.
For this you will need to define/create each route according your needs
Route::get("user/yeni", "UsersController#create");
and if you need more than one languages you can use the trans helper function, which is an alias for the Lang::get method
Route::get('user/'.trans('routes.create'), 'UsersController#create');
I just had the same issue. And managed to recreate some sort of custom resource route method. It probably could be a lot better, but for now it works like a charm.
namespace App\Helpers;
use Illuminate\Support\Facades\App;
class RouteHelper
{
public static function NamedResourceRoute($route, $controller, $named, $except = array())
{
$routes = RouteHelper::GetDefaultResourceRoutes($route);
foreach($routes as $method => $options) {
RouteHelper::GetRoute($route, $controller, $method, $options['type'], $options['name'], $named);
}
}
public static function GetRoute($route, $controller, $method, $type, $name, $named) {
App::make('router')->$type($named.'/'.$name, ['as' => $route.'.'.$method, 'uses' => $controller.'#'.$method]);
}
public static function GetDefaultResourceRoutes($route) {
return [
'store' => [
'type' => 'post',
'name' => ''
],
'index' => [
'type' => 'get',
'name' => ''
],
'create' => [
'type' => 'get',
'name' => trans('routes.create')
],
'update' => [
'type' => 'put',
'name' => '{'.$route.'}'
],
'show' => [
'type' => 'get',
'name' => '{'.$route.'}'
],
'destroy' => [
'type' => 'delete',
'name' => '{'.$route.'}'
],
'edit' => [
'type' => 'get',
'name' => '{'.$route.'}/'.trans('routes.edit')
]
];
}
}
Use it like this in the routes.php:
\App\Helpers\RouteHelper::NamedResourceRoute('recipes', 'RecipeController', 'recepten');
Where the first parameter is for the named route, second the controller and third the route itself.
And something like this to the view/lang/{language}/route.php file:
'edit' => 'wijzig',
'create' => 'nieuw'
This results in something like this:
This is not possible in Laravel as they use code by convention over configuration. A resources uses the RESTfull implementation
Therefore you have to stick to the convention of
GET /news/create
POST /news
GET /news/1
GET /news/1/edit
...

CakePHP: Can I Promote active router with Router::promote or some other way?

I've got two or more routes that will be going to the same controller and action. This is fine until I want to use a helper such as the form helper or pagination on the page.
What happens is that the current url changes to whatever is declared first in my routes.php file.
I see there is a way to promote a router with Router::promote but I'm not sure if I can do it based on the current url or router being used or if there's a bett way to do this.
Here's an example of what my router.php looks like:
Router::connect('/cars-for-sale/results/*', array('controller' => 'listings', 'action' => 'results'));
Router::connect('/new-cars/results/*', array('controller' => 'listings', 'action' => 'results'));
Router::connect('/used-cars/results/*', array('controller' => 'listings', 'action' => 'results'));
Let's say for example that I'm at the url domain.com/used-cars/results/ and I'm using the form helper or pagination helper, the url that is being put in the action or href is domain.com/cars-for-sale/results/.
Any help or info would be appreciated.
Routes should be unique and identifiable!
The problem with these Routes is that, basically, you created duplicate URLs not only does this cause problems with CakePHP picking the right route, Google doesn't like that as well; duplicated content will have a negative effect on your SEO ranking!
In order to pick the right URL (Route), CakePHP should be able to do so, based on its parameters; your current Routes do not offer any way to distinguish them.
And neither does your application!
All these URLs will present the same data;
/cars-for-sale/results/
/new-cars/results/
/used-cars/results/
Solution 1 - separate actions
If your application is limited to these three categories, the easiest solution is to create three actions, one per category;
Controller:
class ListingsController extends AppController
{
const CATEGORY_NEW = 1;
const CATEGORY_USED = 2;
const CATEGORY_FOR_SALE = 3;
public $uses = array('Car');
public function forSaleCars()
{
$this->set('cars', $this->Paginator->paginate('Car', array('Car.category_id' => self::CATEGORY_FOR_SALE)));
}
public function newCars()
{
$this->set('cars', $this->Paginator->paginate('Car', array('Car.category_id' => self::CATEGORY_NEW)));
}
public function usedCars()
{
$this->set('cars', $this->Paginator->paginate('Car', array('Car.category_id' => self::CATEGORY_USED)));
}
}
Routes.php
Router::connect(
'/cars-for-sale/results/*',
array('controller' => 'listings', 'action' => 'forSaleCars')
);
Router::connect(
'/new-cars/results/*',
array('controller' => 'listings', 'action' => 'newCars')
);
Router::connect(
'/used-cars/results/*',
array('controller' => 'listings', 'action' => 'usedCars')
);
Solution 2 - Pass the 'category' as parameter
If the list of URLs to be used for the 'listings' will not be fixed and will expand, it may be better to pass the 'filter' as a parameter and include that in your routes;
routes.php
Router::connect(
'/:category/results/*',
array(
'controller' => 'listings',
'action' => 'results',
),
array(
// category: lowercase alphanumeric and dashes, but NO leading/trailing dash
'category' => '[a-z0-9]{1}([a-z0-9\-]{2,}[a-z0-9]{1})?',
// Mark category as 'persistent' so that the Html/PaginatorHelper
// will automatically use the current category to generate links
'persist' => array('category'),
// pass the category as parameter for the 'results' action
'pass' => array('category'),
)
);
Read about the Router API
In your controller:
class ListingsController extends AppController
{
public $uses = array('Car');
/**
* Shows results for the specified category
*
* #param string $category
*
* #throws NotFoundException
*/
public function results($category = null)
{
$categoryId = $this->Car->Category->field('id', array('name' => $category));
if (!$categoryId) {
throw new NotFoundException(__('Unknown category'));
}
$this->set('cars', $this->Paginator->paginate('Car', array('Car.category_id' => $categoryId)));
}
}
And, to create a link to a certain category;
$this->Html->link('New Cars',
array(
'controller' => 'listings',
'action' => 'results',
'category' => 'new-cars'
)
);

Categories