Laravel: middleware to determine which controller to use - laravel

I am building a site which loads all pages and content via javascript while also manipulating the browser address bar (giving the illusion of a normal, navigable site with each page at its own URL). As a fallback, and for the benefit of search engines, the pages must also be able to be loaded normally at their respective URLs.
To do this, I need to let Laravel know if the page data is being requested via an ajax call or normal HTTP request. This- I presume- would be a situation where I would use Middleware. I want to be able to process the pages using two different controllers; one for ajax, one for HTTP.
ie:
if (Request::ajax()){
forward request to ajax page controller
}else {
forward request to standard page controller
}
Is this possible to handle with middleware? All examples I can find seem to assume that the controller is already a given.

I use the routes.php file instead of middleware. I believe middleware is after the route has been determined.
if(Request::ajax() || Request::json()){
Route::get('items', [
'as' => 'api.posts.index' ,
'uses' => 'Api\ItemsController#index'
]);
} else {
Route::get('items', [
'as' => 'posts.index' ,
'uses' => 'ItemsController#index'
]);
}
I do it this way because I like to separate out the urls for json versus web.
Route::get('items', [
'as' => 'posts.index' ,
'uses' => 'ItemsController#index'
]);
/**
* JSON API
*
*/
Route::group([
'prefix' => 'api/v1',
'as' => 'api.',
'namespace' => 'Api'
], function () {
Route::get('items', [
'as' => 'posts.index' ,
'uses' => 'ItemsController#index'
]);
}
Either way your controllers would live here.
App/Http/Controllers/Api/ItemsController.php
App/Http/Controllers/ItemsController.php
EDIT
I read the comment form GONG and the RouteServiceProvider would also work for this, but you would still have two distinct urls. You would have to manage another routes file, but whatever works for you.

Related

What does 'as' method do in Laravel

In the example from the tutorial, it shows up.
Route::group([
'prefix' => 'admin',
'as' => 'admin.'
], function () {}
Can someone tells me what 'as' does? Also, is the dot next to the 'admin' neccessary?
Thank you.
Let's say, for example, that you have this route:
Route::get('admin', [
'as' => 'admin', 'uses' => 'AdminController#index'
]);
By using as you assign custom name to your route. So now, Laravel will allow you to reference said route by using:
$route = route('admin');
So you don't have to build the URL manually over and over again in your code. You don't really need . notation if you only want to call your route admin. If you want a more detailed name of your route, lets say for ex. admin product route, then you use the . notation, like this:
Route::get('admin/product', [
'as' => 'admin.product', 'uses' => 'AdminController#showProduct'
]);
So now, you will be able to call this route by the assigned name:
$route = route('admin.product');
Update:
The previous answer I provided is valid for a single routes. For the route groups, the procedure is very similar. In the route groups you need the . notation when you add a custom name, since you will be referencing another route after that . notation. This will allow you to set a common route name prefix for all routes within the group. So by your example, lets say you have a dashboard route inside your admin route group:
Route::group(['as' => 'admin.'], function () {
Route::get('dashboard', ['as' => 'dashboard', function () {
//Some logic
}]);
});
Now, you will be able to call the dashboard route like this:
$route = route(admin.dashboard);
You can read more about this in Laravel official documentation.
you may specify an as keyword in the route group attribute array, allowing you to set a common route name prefix for all routes within the group.
For Example
Route::group(['as' => 'admin::'], function () {
// Route named "admin::"
});
UseRoute Name like {{route(admin::)}} or route('admin::')
you can use an 'as' as a named route. if you do not prefix your route name in group route than you may add custom route name like this.
Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'roles'], 'roles' => ['2']], function () {
Route::post('/changeProfile', ['uses' => 'UserController#changeProfile',
'as' => 'changeProfile']);
});

Laravel 5.4 route simplification

I've been trying to find some documentation on how to accomplish the following, but it seems like maybe I'm not using the correct search terms.
I would like to implement some simplified routes in Laravel 5.4 by omitting the route name from the path – for example:
/{page} instead of /pages/{page}
/profile instead of /users/{user}/edit
/{exam}/{question} (or even /exams/{exam}/{question}) instead of /exams/{exam}/questions/{question}
Example of current routes
Route::resource('exams.questions', 'ExamQuestionController', ['only' => ['show']]);
// exams/{exam}/question/{question}
I know how to do this with route closures and one-off routes (e.g.: Route::get...) but is there a way to do this using Route::resource?
In rails the above could be accomplished with:
resources :exams, path: '', only: [:index, :show] do
resources :question, path: '', only: [:show]
end
// /:exam_id/:id
While I haven't yet found a way to accomplish my test cases using strictly Route::resource, here is what I implemented to accomplish what I was trying to do:
// For: `/{exam}/{question}`
Route::group(['as' => 'exams.', 'prefix' => '{exam}'], function() {
Route::get('{question}', [
'as' => 'question.show',
'uses' => 'QuestionController#show'
]);
});
// For: `/exams/{exam}/{question}`
Route::group(['as' => 'exams.', 'prefix' => 'exams/{exam}'], function() {
Route::get('{question}', [
'as' => 'question.show',
'uses' => 'QuestionController#show'
]);
});
// For: `/profile`
Route::get('profile', function() {
$controller = resolve('App\Http\Controllers\UserController');
return $controller->callAction('edit', $user = [ Auth::user() ]);
})->middleware('auth')->name('users.edit');
// For: `/{page}`
// --------------
// Note that the above `/profile` route must come before
// this route if using both methods as this route
// will capture `/profile` as a `{page}` otherwise
Route::get('{page}', [
'as' => 'page.show',
'uses' => 'PageController#show'
]);
No, you cannot and should not be trying to do this with Route::resource.
The whole purpose of Route::resource is that it creates the routes in a specific way that matches the common "RESTful Routing" pattern.
There is nothing wrong with wanting simpler routes (no one is forcing you to use RESTful routing), but you will need to make them yourself with Route::get, etc. as you already know.
From the documentation (not exactly your case, but related to it - showing that Route::resource is not meant to be super-configurable):
Supplementing Resource Controllers
If you need to add additional routes to a resource controller beyond the default set of resource routes, you should define those routes before your call to Route::resource; otherwise, the routes defined by the resource method may unintentionally take precedence over your supplemental routes:
Route::get('photos/popular', 'PhotoController#method');
Route::resource('photos', 'PhotoController');

Laravel, need variable on admin routes

I need a variable for my admin dashboard, so my guess is to put something inside cache so I can do I can access it in my admin layout-template.
I need to merge 2 config files with links:
$dashboard_links = \Auth::user()->hasRole(['root', 'admin'])
? config('dashboard')
: config('dashboard-user');
$taxonomies = Config::get('taxonomies');
foreach ($taxonomies as $name => $config)
{
$original_array = array_splice( $dashboard_links, $config['order'], 0 );
$dashboard_links = array_merge ($original_array, [$name => $config], $dashboard_links);
}
I want the $dashboard_links available in the base template "resources/views/layouts/master.blade.php".
But I don't want to create the variable on each page request or put it in every controller. It's only needed when someone is logged as an administrator and visits any admin page.
I am not that good in programming so I don't know what is the best approach.
These are my routes if helpfull:
Route::group(['middleware' => ['role:admin']], function()
{
// edit post form
Route::get('post/edit/{slug}', [
'as' => 'post-edit',
'uses' => 'Post\AdminController#edit'
]);
Route::get('medialibrary/edit/{id}', [
'as' => 'media-edit',
'uses' => 'MediaLibrary\MediaController#edit',
])->where('id', '[0-9]+');
// and so on...
});
You should consider using a view composer https://laravel.com/docs/5.6/views#view-composers
View composers allow you to pass variables to all views associated with the composer.
So you would register a new service provider to associate your composer to specific routes. Then in your composer poplate the variable, and pass it to all admin views.

Get a parameter from a route?

I have the following route:
Route::get('news', [
'as' => 'news',
'uses' => 'ArticleController#getIndex',
]);
There are also other routes that use the same resource controller, e.g:
Route::get('travel', [
'as' => 'travel',
'uses' => 'ArticleController#getIndex',
]);
In the article controller, how can I get the category of the articles. e.g. travel or news?
I can't turn it into a route param e.g.
Route::get('{travel}', [
'as' => 'travel',
'uses' => 'ArticleController#getIndex',
]);
As there are other sections such as contact, faqs, etc that do not use the article controller.
I know I could put it all on one route:
Route::get('/article/{category}', [
'as' => 'travel',
'uses' => 'ArticleController#getIndex',
]);
But I want pretty URLS like:
mydomain.com/category-slug/article-slug
Ideally if I could use a resource controller:
Route::resource('news', ArticleController');
With some sort of array:
Route::resource(['news', 'travel'], ArticleController');
So my questions:
How can I get the name of the resource. e.g. news, travel in my controller?
Is there an easy way to specify varying routes to the same resource?
For Example Keep your routes like this as your static routes will be limited and need to be unique so they will not get conflict between routes
demo routes :
Route::get('contact', [
'as' => 'contact',
'uses' => 'contactController#getIndex',
]);
Route::get('{travel}', [
'as' => 'travel',
'uses' => 'ArticleController#getIndex',
]);
In order to achieve a route like that:
mydomain.com/category-slug/article-slug
Use the following:
Route::get('/{category}/{article}', 'ArticleController#getArticle');
Then in your controller, you will have 2 parameters in your getArticle method.
public function getArticle($category, $article)
{
//Your code here
}
In order to have pretty urls you need to make a few steps:
1)
First of all you need to add a column to your articles table called for example 'slug'.
2) Then you need to specify your route key in your article model. Like this:
Article model
public function getRouteKeyName()
{
return 'slug';
}
3) When you add an article you should create the slug by your own in order to be unique. so in your controller that you create the article add this code.
$slug = str_slug($article['title'], '-');
$slugs_count = DB::table('articles')->where('name',$article['title'])- >count();
if($slugs_count>1){
$slug = $slug.'-'.($slugs_count-1);
}
$article->slug = $slug;
$article->update();
4) Now we have to set the route
Route::get('/article/{slug}', [
'as' => 'travel',
'uses' => 'ArticleController#getIndex'
])->where(['slug','[\w\d\-\_]+']);
5) Finally in order to get the article you need
$article = Article::where('slug','=',$slug)->first();
If you want to use categories etc you could pass more parameters in your route and manipulate it in your controller. like this
Route::get('/article/{category}/{slug}', [
'as' => 'travel',
'uses' => 'ArticleController#getIndex'
])->where(['slug','[\w\d\-\_]+','category','[\w\d\-\_]+']);
The where clause is to restrict the parameter with some regular expressions(its optional).
I forgot to mention that the routekeyname in your model works in laravel 5.2.

Route using either a prefix or a domain

I am working on a platform that allows users to run their own site in either a sub folder of the main website domain, or map a custom domain for their site.
When using a custom domain the URL structure for each route is slightly different in that it is prefixed with the username, but when using a custom domain then this prefix is not used.
Is there a clever way to achieve this in my Route::group to handle both request types in one route and successfully use reverse routing to produce the appropriate URL based on the parameters passed to it.
Below is an example of using the prefix
Route::group(array( 'prefix' => 'sites/{username}'), function() {
Route::get('/photos/{album_id}.html', array('uses' => 'Media\PhotosController#album_view', 'as' => 'photo_album'));
});
And here is an example of using a custom domain
Route::group(array('domain' => '{users_domain}'), function() {
Route::get('/photos/{album_id}.html', array('uses' => 'Media\PhotosController#album_view', 'as' => 'photo_album'));
});
Ideally I would like to be in a position where I could use either
route('photo_album', ['username' => 'johnboy', 'album_id' => 123] )
and be returned
http://www.mainwebsitedomain.com/sites/johnboy/photos/123.html
or call the same route with different parameters
route('photo_album', ['users_domain' => 'www.johnboy.com', 'album_id' => 123] )
and be returned
http://www.johnboy.com/photos/123.html
This is a pretty tricky question so expect a few not so perfect workarounds in my answer...
I recommend you read everything first and try it out afterwards. This answer includes several simplification steps but I wrote down whole process to help with understanding
The first problem here is that you can't have multiple routes with the same name if you want to call them by name.
Let's fix that by adding a "route name prefix":
Route::group(array( 'prefix' => 'sites/{username}'), function() {
Route::get('/photos/{album_id}.html', array('uses' => 'Media\PhotosController#album_view',
'as' => 'photo_album'));
});
Route::group(array('domain' => '{users_domain}'), function() {
Route::get('/photos/{album_id}.html', array('uses' => 'Media\PhotosController#album_view',
'as' => 'domain.photo_album'));
});
So now we can use this to generate urls:
route('photo_album', ['username' => 'johnboy', 'album_id' => 123] )
route('domain.photo_album', ['users_domain' => 'www.johnboy.com', 'album_id' => 123])
(No worries we will get rid of domain. in the URL generation later...)
The next problem is that Laravel doesn't allow a full wildcard domain like 'domain' => '{users_domain}'. It works fine for generating URLs but if you try to actually access it you get a 404. What's the solution for this you ask? You have to create an additional group that listens to the domain you're currently on. But only if it isn't the root domain of your site.
For simplicity reasons let's first add the application domain to your config. I suggest this in config/app.php:
'domain' => env('APP_DOMAIN', 'www.mainwebsitedomain.com')
This way it is also configurable via the environment file for development.
After that we can add this conditional route group:
$currentDomain = Request::server('HTTP_HOST');
if($currentDomain != config('app.domain')){
Route::group(array('domain' => $currentDomain), function() {
Route::get('/photos/{album_id}.html', array('uses' => 'Media\PhotosController#album_view',
'as' => 'current_domain.photo_album'));
});
}
Soooo... we got our routes. However this is pretty messy, even with just one single route. To reduce the code duplication your can move the actual routes to one (or more) external files. Like this:
app/Http/routes/photos.php:
if(!empty($routeNamePrefix)){
$routeNamePrefix = $routeNamePrefix . '.';
}
else {
$routeNamePrefix = '';
}
Route::get('/photos/{album_id}.html', ['uses' => 'Media\PhotosController#album_view',
'as' => $routeNamePrefix.'photo_album']);
And then the new routes.php:
// routes for application domain routes
Route::group(['domain' => config('app.domain'), 'prefix' => 'sites/{username}'], function($group){
include __DIR__.'/routes/photos.php';
});
// routes to LISTEN to custom domain requests
$currentDomain = Request::server('HTTP_HOST');
if($currentDomain != config('app.domain')){
Route::group(['domain' => $currentDomain], function(){
$routeNamePrefix = 'current_domain';
include __DIR__.'/routes/photos.php';
});
}
// routes to GENERATE custom domain URLs
Route::group(['domain' => '{users_domain}'], function(){
$routeNamePrefix = 'domain';
include __DIR__.'/routes/photos.php';
});
Now the only thing missing is a custom URL generation function. Unfortunately Laravel's route() won't be able to handle this logic so you have to override it. Create a file with custom helper functions, for example app/helpers.php and require it in bootstrap/autoload.php before vendor/autoload.php is loaded:
require __DIR__.'/../app/helpers.php';
require __DIR__.'/../vendor/autoload.php';
Then add this function to the helpers.php:
function route($name, $parameters = array(), $absolute = true, $route = null){
$currentDomain = Request::server('HTTP_HOST');
$usersDomain = array_get($parameters, 'users_domain');
if($usersDomain){
if($currentDomain == $usersDomain){
$name = 'current_domain.'.$name;
array_forget($parameters, 'users_domain');
}
else {
$name = 'domain.'.$name;
}
}
return app('url')->route($name, $parameters, $absolute, $route);
}
You can call this function exactly like you asked for and it will behave like the normal route() in terms of options and passing parameters.

Resources