So I've been working on an app that lived on the domain root and now has to work on /admin. So URLs like domain.com/[resource] should now be domain.com/admin/[resource]. I didn't thought this very well before since I assumed that this had to be a very easy fix on Laravel. After all, that's one of the main reasons for not hardcoding routes, right?
So my routes.php file looked something like:
Route::group(['before' => 'auth'], function() {
Route::resource('books', 'BooksController');
... more resources here ...
});
Going through the docs I found that 'prefix' => 'admin' would do the trick:
Route::group(['prefix' => 'admin', 'before' => 'auth'], function() {
Route::resource('books', 'BooksController');
... more resources here ...
});
But it turns out that every route name get's changed from books.{action} to admin.books.{action} which requires me to change the whole app. Regexing would be dangerous and doing it manually would be annoying. Laravel was supposed to help with this! Or am I missing something?
This is untested, but after looking on the documentation for resource controllers it seems as though you can manually set their names. I'm assuming Laravel automatically namespaces grouped resource controller routes to avoid name collision, but you can override this to avoid going back through the rest of your app (just beware of future name collision):
Route::resource(
'books',
'BooksController',
array(
'names' => array(
'index' => 'photo.index',
'create' => 'photo.create',
'store' => 'photo.store',
'show' => 'photo.show',
'edit' => 'photo.edit',
'update' => 'photo.update',
'destroy' => 'photo.destroy',
)
)
);
Shorter Method:
Just define a quick method at the top of your routes.php file to shorten up this repetitive task of creating an array of route names. Still not the greatest solution, but I believe its the only thing you can do with how Laravel has this set up.
function createRouteNames($resource) {
$names = array();
$types = ['index', 'create', 'store', 'show', 'edit', 'update', 'destroy'];
foreach($types as $type) {
$names[$type] = $resource . '.' . $type;
}
return $names;
}
Route::resource('books', 'BooksController', ['names' => createRouteNames('books')]);
Note: [] === array() and may not be supported on older PHP's, meaning you may need to replace them with the old syntax.
Related
I have this Route
Route::group([ 'middleware' => ['auth','lang']], function() {
// SETTINGS
Route::namespace( 'Settings' )->prefix( 'settings' )->group( function () {
// INDEX
Route::get( '/', 'SettingsController#index' );
// ACCOUNTS
Route::resource( 'accounts', 'AccountController', ['only' => ['index','store','edit','update']] );
// TAGS
Route::resource( 'tags', 'TagController', ['only' => ['index','destroy']] );
// PROFILE
Route::get('profile', 'ProfileController#index');
Route::post('profile', 'ProfileController#update');
});
Any way I can join the two PROFILE ones into one that is resource? Whenever I try using Route::resource( 'profile', 'ProfileController', ['only' => ['index','update']] ), it gives me an error that the method is not allowed - 405 (Method Not Allowed). I think it just doesn't find the update one? I am really not sure what might be the issue.
This is happening because in the case of resourceful controllers, a post would be defaulted to a store method, not update.
So you are posting to the store method, which is not defined, giving you the 403 method not allowed.
To solve this, either change your request to a PUT or change your code to Route::resource( 'profile', 'ProfileController', ['only' => ['index','store']] ) Keep in mind, if you do this, you have to move the contents of your update function to store.
For more information, checkout https://laravel.com/docs/5.5/controllers#resource-controllers
I have to support url friendly structure for a project.
There is multiple tables with a slug column, in cakephp how can I route the slug to a controller in the most efficient way.
At first I was checking if slug exist in a table, if slug exist use the route:
$c = TableRegistry::get('cateogories');
$result= $c->find()->select(['id'])->where(['url'=>$slug])->toArray();
if(count($result) > 0) {
$routes->connect(
'/:slug',
['controller' => 'Categories', 'action' => 'index', 'id' => $result[0]['id']]
);
}
The problem being that I have multiple checks like the one above and each one is being ran even if a route prior matches (doesn't need to be ran so extra querys are being called).
So how can I add a conditional statement of some sort so that it only checks if the route matches if none of the prior ones have.
I'd suggest to go for a custom route class that handles this. While you could query the data in your routes files, this is
not overly test friendly
not very DRY
not safe for reverse routing
The latter point means that when not connecting all routes, trying to generate a URL from a route array for a non-connected route might trigger an exception, or match the wrong route.
With a custom route class you could simply pass the model in the options when connecting the routes, and in the route class after parsing the URL, query that model for the given slug, and return false or the parsed data accordingly.It's really simple, just have a look at what the existing route classes do.
Here's a very basic example which should be pretty self-explantory.
src/Routing/Route/SlugRoute.php
namespace App\Routing\Route;
use Cake\Routing\Route\Route;
use Cake\ORM\Locator\LocatorAwareTrait;
class SlugRoute extends Route
{
use LocatorAwareTrait;
public function parse($url)
{
$params = parent::parse($url);
if (!$params ||
!isset($this->options['model'])
) {
return false;
}
$count = $this
->tableLocator()
->get($this->options['model'])
->find()
->where([
'slug' => $params['slug']
])
->count();
if ($count !== 1) {
return false;
}
return $params;
}
}
This example assumes that in the controller, you'd use the slug to retrieve the record. If you'd wanted to have the ID passed, then instead of using count(), you could fetch the ID and pass it along in the parsed data, like:
$params['pass'][] = $id;
It would then end up being passed as the second argument of the controller action.
routes.php
$routes->connect(
'/:slug',
['controller' => 'Articles', 'action' => 'view'],
[
'pass' => ['slug'],
'routeClass' => 'SlugRoute',
'model' => 'Articles'
]
);
$routes->connect(
'/:slug',
['controller' => 'Categories', 'action' => 'view'],
[
'pass' => ['slug'],
'routeClass' => 'SlugRoute',
'model' => 'Categories'
]
);
// ...
This would first check the Articles model, then the Categories model, etc., and stop once one of the routes finds a record for the given slug.
See also
Cookbook > Routing > Custom Route Classes
API > \Cake\Routing\Route::parse()
Source > \Cake\Routing\Route
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
...
I know there are tickets here, which are talking about the similar scenario, but I can't find any answer that would satisfy my case.
I'm trying to do the following:
Route::group(
[
'prefix' => 'admin',
'namespace' => 'Admin'
],
function() {
Route::controller('/', 'LoginController');
Route::group(
[
'prefix' => '',
'before' => 'auth.admin'
],
function() {
Route::controller('page', 'PageController');
Route::controller('article', 'ArticleController');
}
);
}
);
When I call /admin I get the LoginController and it's getIndex() view, but when I call /admin/page - I get:
Symfony \ Component \ HttpKernel \ Exception \ NotFoundHttpException
Controller method not found.
I know you can nest Route::group calls, but it doesn't seem to be documented well anywhere how to achieve it. From my understanding, you have to have 'prefix' specified with each Route::group call - In the nested one I've just used blank string '' - as it doesn't require any additional prefix apart from the parent one. The encapsulated calls to controllers within the nested group require admin.auth filter - and that's the reason why I wanted to enclose them in the nested group - rather than specifying filter for each controller separately.
Any idea what needs to be done to make this scenario work?
Also - even if I change the code so that it calls controllers directly under the parent group like so:
Route::group(
[
'prefix' => 'admin',
'namespace' => 'Admin'
],
function() {
Route::controller('/', 'LoginController');
Route::controller('page', 'PageController');
Route::controller('article', 'ArticleController');
}
);
I seem to be getting the same error when I call /admin/page - PageController looks like this:
namespace Admin;
use BaseController;
use View;
class PageController extends BaseController {
public function getIndex() {
return View::make('Admin.page.index');
}
}
I'll just chime in to say that by being explicit in your routing - only using Route::get/post/delete etc and not Route::controller or Route::resource - you avoid this sort of problem and lots of others. Route::controller especially is considered bad practice.
Ok - after experimenting with it for a while the answer appears to be in the order that you put the calls within your group.
When I move call to Route::controller('/', 'LoginController'); after the nested group - then everything seem to work fine:
Route::group(
[
'prefix' => 'admin',
'namespace' => 'Admin'
],
function() {
Route::group(
[
'prefix' => '',
'before' => 'auth.admin'
],
function() {
Route::controller('page', 'PageController');
Route::controller('article', 'ArticleController');
}
);
Route::controller('/', 'LoginController');
}
);
It's a shame that such an important aspect isn't documented anywhere - nevertheless - it works!
Recently, I've been studying cake, I've seen the auth library which said to be will take care of the access control over your app, but, it seems like, you can't initialize or even use this auth library when you're not in the 'UsersController', i did not want that, what if it has some admin part wherein i want the URI to be admin/login, or just simply /login, i've been scratching my head over this one, please help.
Another question, why it seems like the functionality of the '$this->redirect' is not effective when i'm putting this one at any method that contains nothing but redirection, or even in the __construct()?
thanks guys, hoping someone could clearly explain to me those things.
you can use the Auth component inside any controller in the application. If you want it will only effect with the admin section then you can add condition in the beforeFilter funciton in you application AppController on Auth initialization like.
// for component initialization.
public $components = array(
'Auth' => array(
'authenticate' => array(
'userModel' => 'Customer', // you can also specify the differnt model instead of user
'Form' => array(
'fields' => array('username' => 'email')
)
)
)
}
and you can bind this on the admin routing like
function beforeFilter(){
// only works with admin routing.
if(isset($this->request->params['prefix']) && ($this->request->params['prefix'] == 'admin')){
$this->Auth->loginRedirect = array('admin' => true, 'controller' => 'pages', 'action' => 'index');
$this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login', 'admin' => true);
$this->Auth->loginAction = array('admin' => true, 'controller' => 'customers', 'action' => 'login');
}
}
If you're using cake 2.3.x or later then make sure you have specified the redirect action in correct format like.
return $this->redirect('action_name'); // you can also specify the array of parameters.