Laravel Localisation messing with Route Resource parameter - php

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');

Related

Laravel Route resource named functions

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

Dynamically permissions based on routes path in laravel via entrust

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']
]);

Laravel not navigating to url based on database id

I am attempting to navigate to a product page with laravel. The URL gets an id which is obtained from the database id which it uses to display the product.
Products controller file:
public function index()
{
$products = Product::all();
return view('products.index')->with('products',$products);
}
public function show($id)
{
$product = Product::find($id);
return view('products.show')->with('product',$product);
}
Error:
The requested URL /products/1 was not found on this server.
Here's my routes as requested:
<?php
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
return view('HomePage');
});
Route::get('/about', function () {
return view('pages.about');
});
/*
* Login
*/
Route::get('/login', [
'as' => 'login',
'uses' => 'UserLoginController#showLogin',
]);
Route::post('/login', 'UserLoginController#postLogin');
Auth::routes();
Route::get('/home', 'HomeController#index')->name('home');
Route::resource('products','productcontroller');
You don't have any route like this /products/1 so need to add that route and assign the controller and action that you need . Eg.
Route::get('/product/{id}', 'ProductController#show')
Edit: is your controller class named correctly? I think issue is there. Yes you can use resource route for restful request response but route can't find your controller class. Name it correctly. ProductsController or something. Double check.
Can you paste your route? Problem is probably there. Not in your controller. Check the documentation, if you can't figure it out paste here.
the href tag was wrong and needed to be exactly what was in the localhost e.g. localhost/8888/public

Laravel: How to share variables with parent view

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.

Laravel 5 : How to use Auth::user()->id in route

I try to create a application using php Laravel framework .When i use Auth::user()->id in my route file i got error "Trying to get property of non-object" .So how to i fix it?
This is my route file
`
<?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 controller to call when that URI is requested.
|
*/
Route::get('/', 'HomeController#index');
Route::controllers([
'auth' => 'Auth\AuthController',
'password' => 'Auth\PasswordController',
]);
$common_variable=App\MyModel::where('site_id',Auth::user()->id)->count();
view()->share(compact('common_variable'));
`
If you want something to execute you should use laravel 5 middleware.
1.- php artisan make:middleware newMiddleware.
2.- Add your middleware to all routes you need by setting the middleware property.
Route::get('test', ['before' => 'newMiddleware', function(){
//
}]);
3.- Now do in a proper way what you where trying to accomplish
$user_id = Auth::user()->id()
if($user_id){
$variable = \App\Model::where('site_id', '=', $user_id)
}
return $variable
4.- Now you have your wanted value in your route function / controller, you can pass it to the view along with other data.
you have to define your route, otherwise it will not work
Route::get('/testauth', function()
{
var_dump(Auth::user());
// your code here
});
Now hit the 'testauth' route. It should be working.
You can't access the authenticated user in a route model binding because the authentication middleware has not run. Please see thus discussion thread where some workarounds are proposed.
you have to make sure your user is authenticated
$common_variable=Auth::check() ?App\MyModel::where('site_id',Auth::user()->id)->count() : 0;
when you are not authenticated Auth::user() will always return null and you are referring id on a null object which give you the error
This is how I did this. I wanted to restrict a view to an owner of the requested object, in my case an "API Key". I used Laravel's route model binding to do this.
Route::model('apikeys', 'Apikey');
Route::bind('apikeys', function($value, $route) {
return Apikey::whereSlug($value)->where('users_id', '=', Auth::user()->id)->where('approved', '=', true)->where('deleted', '=', false)->first();
});
Route::resource('apikeys', 'ApikeysController');
In this case, I'm passing in the value of the requested "slug" (i.e. the friendly url of the api key), then appending an AND'ed where clause with the authenticated user ID, approved = true, and deleted = false.

Categories