To create a Role/permission bases laravel app I'm using Zizaco/entrust package.
Now I want to use an approach that no need to assign defined perms to routes as different middlewares in web.php and that is:
First fetches all defined routes (via Route::getRoutes()->getRoutes() ) and store each of them in permissions table.
We can get all routes by this code:
$routes = collect(Route::getRoutes()->getRoutes())->reduce(function ($carry = [], $route) {
$carry[] = $route->uri();
return $carry;
});
On the other hand we can define roles that have those permissions and attach those to user in normal way.
Now when a user want to access a page , first we get route path name and then by can method defined in entrust we check that user can access to that route or not. this can done via a simple middleware named checkAccess for example that is added to all routes as a route group. like this:
class checkAccess
{
public function handle($request, Closure $next)
{
if (Auth::check()) {
$currentName = Route::getCurrentRoute()->getPath();
if (Auth::user()->can($currentName)) {
return $next($request);
}else{
return response()->view('errors.403', ['prevPage'=> URL::previous()]);
}*/
return $next($request);
}
return Redirect::to('/admin/login');
}
}
Route::middleware(['checkAccess'])->group(function () {
//Other routes
});
But a problem is that some resource routes have same route path but different method access. like:
+-----------+-----------------+---------+----------------+
| METHOD | URL | Action | Route Name |
+-----------+-----------------+---------+----------------+
| GET | /photos/{photo} | show | photos.show |
| PUT/PATCH | /photos/{photo} | update | photos.update |
| DELETE | /photos/{photo} | destroy | photos.destroy |
+-----------+-----------------+---------+----------------+
And this is cause duplicate permission name Although they are really different in action.
I want to know are there any relative way to create dynamically permission. or what can I do that to solve this problem in this case?
I am not sure if that is the thing that you need, but maybe it will help you. I have modular application structure and for each module one route.php file. In that file my routes depend on permission:
Route::get('/edit/{param?}', [
'as' => 'get.users.edit',
'uses' => 'UsersController#getEdit',
'middleware' => ['permission:admin']
]);
Related
On my second try I implemented a Multi lingual implementation for a site I am working on by using a Route::group that prefixes the {locale} in the first segment of the url using routeMiddleware Kernel. It works very well except when retrieving route Resources with parameters.
The implementation has a hiccup in that for some reason it turns the parameter into %2F{id} (which is not correct) and do not retrieve the resource that my PublicGalleriesController requested. I do not understand why, because when I hover over the generated anchor href I see the correct url format. But when I click it give a 404 Not Found message with the messed up url.
web.php This is my route group that encapsulates all routes with a function
Route::group([
'prefix' => '{locale}',
'middleware' => 'setlocale',
], function() {
// all my routes are within this route group including:
Route::resource('gallery', 'PublicGalleriesController');
Auth::routes();
Route::group(['middleware' => 'auth'], function() {
...
});
});
App/Http/Middleware/Localisation.php Route Middleware that is routed through Kernel.php
public function handle($request, Closure $next)
{
\App::setLocale($request->segment(1));
return $next($request);
}
PublicGalleriesController.php Retrieves image paths from model and returns it to client view
public function show($id)
{
// Show gallery group images for given group id
$pics = null;
$path = null;
$path = GalleryGroup::find($id);
$pics = Gallery::select('imagefilename', 'group_id')->where('group_id', $id)->orderBy('id', 'asc')->get()->toArray();
return view('gallery.show', compact('pics', 'path'));
}
When I hover over a gallery group photo link that is visible on the index.blade it shows in the browser left corner as: localhost/en/gallery/41. The index.blade retrieves the gallery group primary keys and builds html anchor links in a loop: {{$item['descrp']}}
When I click this link it should via the PublicGalleriesController run the show function and retrieve all those gallery group photos but instead returns a 404 Not Found with the url in the browser showing localhost/en/gallery%2F41. The %2F I believe is a Url encoded forward slash.
php artisan route:list shows the show resource as follows:
| Domain | Method | URI | Name | Action
| Middleware |
+--------+-----------------------------------------+--------------+-----------------------
| | GET|HEAD | {locale}/gallery/{gallery} | gallery.show | App\Http\Controllers\PublicGalleriesController#show
| web,setlocale |
Can someone please help me to understand why the url is becoming so messy?
Laravel version: 5.6.39
The signature for the url helper is:
function url($path = null, $parameters = [], $secure = null)
$parameters is an array of parameters, not a path, so it is encoding the / that you are using, since parameters are a segment of the URL not a path.
You can adjust the call to url to have a more full path or use the parameters array as the additional segments, not paths:
url(app()->getLocale() .'/gallery/'. $item['id']); // 1 argument
url(app()->getLocale() .'/gallery', [$item['id']]); // 2 arguments
url(app()->getLocale(), ['gallery', $item['id']]); // 2 arguments
You can also use the route helper:
route('gallery.show', ['locale' => app()->getLocale(), 'gallery' => $item['id']]);
If you don't want to have to pass locale in for all the URLs you want to generate with the route helper you can adjust your middleware to set a default for this parameter:
public function handle($request, Closure $next)
{
App::setLocale($locale = $request->route('locale'));
URL::defaults(['locale' => $locale]); // set default
return $next($request);
}
Now you don't have to pass that parameter:
route('gallery.show', ['gallery' => ...]);
If you wanted to not have the locale parameter passed to all your route "actions" (Controller methods and closures) that can be done as well. You can add a line like so to that middleware before you return the response:
$request->route()->forgetParameter('locale');
I currently have routes like this
//Settings
Route::prefix('settings')->group(function(){
//Get all users settings
Route::resource('user', 'SettingsController');
});
Which will produce a list of routes like so
| POST | settings/user |user.store | App\Http\Controllers\SettingsController#store | web,auth,GlobalAdmin |
| GET|HEAD | settings/user |user.index | App\Http\Controllers\SettingsController#index | web,auth,GlobalAdmin |
| GET|HEAD | settings/user/create |user.create | App\Http\Controllers\SettingsController#create | web,auth,GlobalAdmin |
And so on.
My issue is that I want the settings controller to be able to control a list of different settings in 1 controller, not just 'users'.
How would I name the resource so that it names the functions at the end?
For example, the above code generates function names like SettingsController#store, how would i get it so that it auto builds the function name with a prefix like SettingsController#userstore?
You can't. Unless you go behind the scenes and actually hack into how Laravel handles the generation. What you can do however is use except on the resource route or use partial resource routes.
Once you have done one of the above, you can just add your routes manually such as
POST settings/user
POST settings/other
POST settings/general
And point them that way.
If you really want to have such behavior you can have it. You can extend the Illuminate\Routing\ResourceRegistrar class and bind your extended version to the container.
There is only one method that needs to be adjusted, which is the method that sets up the action for each of the routes of the resource, getResourceAction. This can be adjusted to check for a key passed in the options array the the ResourceRegistrar already uses. If a key is present you can enable the behavior you need, prefixing the method name with the resource name and uppercasing the first letter of the actual method.
class YourRegistrar extends \Illuminate\Routing\ResourceRegistrar
{
protected function getResourceAction($resource, $controller, $method, $options)
{
$name = $this->getResourceRouteName($resource, $method, $options);
// check if 'pre' option was set
$method = isset($options['pre']) ? $resource . ucfirst($method) : $method;
$action = ['as' => $name, 'uses' => $controller.'#'.$method];
if (isset($options['middleware'])) {
$action['middleware'] = $options['middleware'];
}
return $action;
}
}
In a Service Provider #register (binding your new class to the current ResourceRegistrar):
$this->app->bind(
\Illuminate\Routing\ResourceRegistrar::class,
\Where\Ever\YourResourceRegistrar::class
);
In a routes file:
Route::resource('user', 'SettingsController', ['pre' => true]);
// SettingsController#userIndex
// SettingsController#userShow
// ...
Route::resource('user', 'SettingsController');
// SettingsController#index
// SettingsController#show
// ... normal
The router checks to see if there is something bound to the name of the ResourceRegistrar on the container before newing up one. If there is a binding it asks the container to resolve one. This is how you can extend the ResourceRegistrar and the router uses your version.
In our version we are checking if the options key pre was set or not. If it was we adjust the method names for the routes accordingly. ($method = isset($options['pre']) ? $resource . ucfirst($method) : $method;)
You can read more on the ResourceRegistrar and more detail of what happened above in my blog article on the subject:
asklagbox blog - Resource Registrar - lets extend
I do have the current state that I have multiple projects in my Laravel application (which are stored in a database). I do have the following URL structure:
http://app.com/project/7-exampleproject/news
where
https://app.com/project/{id}-{seostring}/{module}
Is the idea behind it. What I do need is that the project variable should be available on any view (module) which comes after the {id} part. How can I achieve it?
==
Additional information
Here is the route list:
GET|HEAD | project/{project}{extra?} | project.dashboard.index | App\Http\Controllers\Project\DashboardController#index | web
GET|HEAD | project/{project}{extra?}/news | project.news.index | App\Http\Controllers\Project\NewsController#index | web
GET|HEAD | project/{project}{extra?}/tournaments | project.tournaments.index | App\Http\Controllers\Project\TournamentController#index | web
Route::group(['prefix' => 'project', 'namespace' => 'Project'], function(){
Route::get('/{project}{extra?}', 'DashboardController#index')->name('project.dashboard.index')->where(['id', '[0-9+]','extra' => '-[A-Za-z0-9]+']);
Route::get('/{project}{extra?}/news', 'NewsController#index')->name('project.news.index')->where(['id', '[0-9+]','extra' => '-[A-Za-z0-9]+']);
Route::get('/{project}{extra?}/tournaments', 'TournamentController#index')->name('project.tournaments.index')->where(['id', '[0-9+]','extra' => '-[A-Za-z0-9]+']);
});
In each of those views my app.layouts.project gets extended by the content of each, so we end up with:
#extends('layouts.project')
#section('content')
<h3>Welcome to <br>{{ $project->name }}!</h3>
#endsection
But what I want to achieve is, EACH time the /project/{project} gets called, the view layouts.project should get it's active ID and $project variable
==
How I want the data to be available:
In my case, each module currently gets the active project from the URL (as seen on the dashboard here), already from the URL. This is not what I am looking for.
public function index(Project $project)
{
return view('project.dashboard.index',[
'project' => $project
]);
}
What I am looking for is to include the $project object (including all information) to layouts.project, which includes meta and head title informations, as well as displaying the project name itself.
Edit 3: Another thing was that you wanted to set the data in your parent view. You can do this even when you extend the parent and pass the data to the child view from the controller. Hope i've clarified things in the chat for you.
Edit 2: You want to auto load the object in the view. You can do this by creating a view composer and reading the current route. If the routes match, extract the project id and load the project model. You can then access the project object in your view without passing it through your controller. Though this is too much of work and bad practise compared to passing the object via the controller.
Edit: After all the comments and chat i understand what you're trying to do.
This will give you the Project object using route model binding while ignoring the unwanted SEO string. This will also work with both cases.
http://app.com/project/7-exampleproject/news
http://app.com/project/7/news
Replace the boot in your RouteServiceProvider
// app/Providers/RouteServiceProvider.php
public function boot()
{
parent::boot();
Route::bind('project', function ($value) {
$id = explode('-', $value)[0];
return \App\Project::findOrFail($id);
});
}
Change your routes to
// routes/web.php
Route::group(['prefix' => 'project', 'namespace' => 'Project'], function () {
Route::get('/{project}', 'DashboardController#index')->name('project.dashboard.index');
Route::get('/{project}/news', 'NewsController#index')->name('project.news.index');
Route::get('/{project}/tournaments', 'TournamentController#index')->name('project.tournaments.index');
});
The path has to be passed as a single parameter and you can split it in your controller to get the id and seotring separately. Something like this.
Route::get('/project/{project}/{module}', 'SomeController#show');
public function show($project, $module)
{
$data = explode('-', $project, 2);
$id = $data[0];
$name = $data[1];
return view('someview')->with(compact('id', 'name'));
}
I think you are looking for ViewComposers, it is not based on the url but on the view name.
My personal opinion is that this is a better approach than something based on URL since URL are changing more often than view names.
I have been using RESTful controllers in my Laravel project. By including:
Route::controller('things', 'ThingController')
in my routes.php, I can define functions in the ThingController like:
public function getDisplay($id) {
$thing = Thing::find($id)
...
}
so that GETting the URL "...things/display/1" would automatically be directed to the controller function. This seems pretty handy and has been working great for me so far.
I noticed many of my controller functions start with getting a model by id from the url, and I thought it would be nice to be able to use route model binding to do this for me instead. So I updated my routes.php to
Route::model('thing', 'Thing');
Route::controller('things', 'ThingController')
and changed the ThingController functions to
public function getDisplay($thing) {
...
}
I assumed this would magically work the way I wanted it to (like everything else I've tried so far in Laravel has) but unfortunately I get "Trying to get property of non-object" when I attempt to use $thing in the function. Is this something that should be able to work and I have just done it wrong, or can route model binding only work with routes explicitly named in routes.php?
If you don't mind with URI path, method name and just work only show, edit and update method, you can use Resource Controller to generate URI string which can define model binding.
In routes.php change to
Route::model('things', 'Thing');
Route::resource('things', 'ThingController');
You can use php artisan routes command to see all URIs
$ artisan routes | grep ThingController
GET|HEAD things | things.index | ThingController#index
GET|HEAD things/create | things.create | ThingController#create
POST things | things.store | ThingController#store
GET|HEAD things/{things} | things.show | ThingController#show
GET|HEAD things/{things}/edit | things.edit | ThingController#edit
PUT things/{things} | things.update | ThingController#update
PATCH things/{things} | | ThingController#update
After that you can threat parameter as Thing object without explicitly name route.
/**
* Display the specified thing.
*
* #param Thing $thing
* #return mixed
*/
public function show(Thing $thing)
{
return $thing->toJson();
}
If you want to access ThingController#show, pass your model ID and Laravel will retrieve it automatically.
http://example.com/things/1
{"id":1,"type":"Yo!"}
You can use Route:resource and still provide other methods. Place the route you need just before that particular Route::resource line.
Eg:
Route::model('things', 'Thing');
Route::get('things/{things}/owner', 'ThingController#getOwner');
Route::resource('things', 'ThingController');
Then create the corresponding method in your controller.
public function getOwner($things) {
return Response::json($things->owner()->get());
}
Here is the official documentation from the Laravel 4.2 docs:
Source: http://laravel.com/docs/controllers#resource-controllers
Adding Additional Routes To Resource Controllers
If it becomes necessary for you to add additional routes to a resource controller beyond the default resource routes, you should define those routes before your call to Route::resource:
Route::get('photos/popular');
Route::resource('photos', 'PhotoController');
I'm new in Laravel 4 development, can't find enough information about resource method in Route class
Route::resource();
How to use it?
It's a great way to setup API's. It implements RESTful in a clever way. The recourse controller route can catch a request and maps it to a specific method in the controller based on the RESTful state.
routes.php
/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It's a breeze. Simply tell Laravel the URIs it should respond to
| and give it the Closure to execute when that URI is requested.
|
*/
// Route group for API versioning
Route::group(array('prefix' => 'api/v1'), function() {
Route::resource('posts', 'PostController');
});
For example:
POST = store() (Create a new entry)
DELETE = destroy($id) (Delete an entry)
GET = index() (Get all entries)
GET = show($id) (Get one entry)
PUT = update($id) (Update an entry)
A practical example:
How do I create a RESTful API in Laravel to use in my BackboneJS app