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.
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 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 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 am hoping that someone can help me out with dynamic routing for urls that can have multiple segments. I've been doing some searching all over the web, but nothing I find helps me out with my specific situation.
A little background ... several years ago, I developed a CMS package for custom client websites that was built on CodeIgniter. This CMS package has several modules (Pages, Blog, Calendar, Inquiries, etc). For the Pages module, I was caching the routes to a "custom routes" config file that associated the full route for the page (including parent, grandparent, etc) with the ID of the page. I did this so that I didn't have to do a database lookup to find the page to display.
I am currently working on rebuilding this CMS package using Laravel (5.1) [while I'm learning Laravel]. I need to figure out the routing situation before I can move on with my Pages module in the new version of the package.
I know that I can do something like ...
// routes.php
Route::get('{slug}', ['uses' => 'PageController#view']);
// PageController.php
class PageController extends Controller
{
public function view($slug)
{
// do a query to get the page by the slug
// display the view
}
}
And this would work if I didn't allow nested pages, but I do. And I only enforce uniqueness of the slug based on the parent. So there could be more than one page with a slug of fargo ...
locations/fargo
staff/fargo
As with the package that I built using CodeIgniter, I would like to be able to avoid extra database lookups to find the correct page to display.
My initial thought was to create a config file that would have the dynamic routes like I did with the old version of the system. The routes will only change at specific times (when page is created, when slug is modified, when parent is changed), so "caching" them would work great. But I'm still new to Laravel, so I'm not sure what the best way to go about this would be.
I did manage to figure out that the following routes work. But is this the best way to set this up?
Route::get('about/foobar', function(){
return App::make('\App\Http\Controllers\PageController')->callAction('view', [123]);
});
Route::get('foobar', function(){
return App::make('\App\Http\Controllers\PageController')->callAction('view', [345]);
});
Basically, I would like to bind a specific route to a specific page ID when the page is created (or when the slug or parent are changed).
Am I just overcomplicating things?
Any help or direction regarding this would be greatly appreciated.
Thanks!
The way I handle this is to use two routes, one for the home page (which generally contains more complex logic like news, pick up articles, banners, etc), and a catch all for any other page.
Routes
// Home page
Route::get('/', [
'as' => 'home',
'uses' => 'PageController#index'
]);
// Catch all page controller (place at the very bottom)
Route::get('{slug}', [
'uses' => 'PageController#getPage'
])->where('slug', '([A-Za-z0-9\-\/]+)');
The important part to note in the above is the ->where() method chained on the end of the route. This allows you to declare regex pattern matching for the route parameters. In this case I am allowing alphanumeric characters, hyphens and forward slashes for the {slug} parameter.
This will match slugs like
test-page
test-page/sub-page
another-page/sub-page
PageController Methods
public function index()
{
$page = Page::where('route', '/')->where('active', 1)->first();
return view($page->template)
->with('page', $page);
}
public function getPage($slug = null)
{
$page = Page::where('route', $slug)->where('active', 1);
$page = $page->firstOrFail();
return view($page->template)->with('page', $page);
}
I keep the template file information in the database, as I allow users to create templates in the content management system.
The response from the query on the database is then passed to the view where it can be output to the metadata, page, breadcrumbs, etc.
I was also looking for the same answer that is about creating a dynamic routing in laravel i come up with this:
In routes.php
<?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.
|
*/
$str=Request::url();
$del="/public/";
$pos=strpos($str, $del);
$important1=substr($str, $pos+strlen($del), strlen($str)-1);
$important=ucfirst($important1);
$asif=explode("/", $important);
$asif1=explode("/", $important1);
//echo $important;
$post=$asif1[0];
$post1=$asif1[1];
if(isset($asif1[2]))
{
$post2=$asif1[2];
}
if(!(isset($post2)))
{
Route::match(array('GET','POST'),$important1, $asif[0].'Controller#'.$asif[1]);
}
if(isset($post2))
{ Route::match(array('GET','POST'),$post.'/'.$post1.'/{id}',$asif[0].'Controller#'.$asif[1]);
}
Route::get('/', function () {
return view('welcome');
});
Ex
if you have PostController with method hello in laravel. You can use this url http://localhost/shortproject/public/post/hello. Where shortproject is your project folder name.
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');