Route::match() not working in nested groups structure - laravel

I'm creating an API that is available only via POST. I'm planning to have more than one version of the API, so the current one uses v1 as part of the URL.
Now, in case an API call is made via GET, PUT or DELETE I would like to return a Fail response. For this I'm using Route::match(), which works perfectly fine in the code below:
Route::group(['namespace'=>'API', 'prefix' => 'api/v1', 'middleware' => 'api.v1'], function() {
Route::match(['get', 'put', 'delete'], '*', function () {
return Response::json(array(
'status' => 'Fail',
'message' => 'Wrong HTTP verb used for the API call. Please use POST.'
));
});
// User
Route::post('user/create', array('uses' => 'APIv1#createUser'));
Route::post('user/read', array('uses' => 'APIv1#readUser'));
// other calls
// University
Route::post('university/create', array('uses' => 'APIv1#createUniversity'));
Route::post('university/read', array('uses' => 'APIv1#readUniversity'));
// other calls...
});
However, I noticed that I could group the routes even more, to separate the API version and calls to specific entities, like user and university:
Route::group(['namespace'=>'API', 'prefix' => 'api'], function() {
Route::match(['get', 'put', 'delete'], '*', function () {
return Response::json(array(
'status' => 'Fail',
'message' => 'Wrong HTTP verb used for the API call. Please use POST.'
));
});
/**
* v.1
*/
Route::group(['prefix' => 'v1', 'middleware' => 'api.v1'], function() {
// User
Route::group(['prefix' => 'user'], function() {
Route::post('create', array('uses' => 'APIv1#createUser'));
Route::post('read', array('uses' => 'APIv1#readUser'));
});
// University
Route::group(['prefix' => 'university'], function() {
Route::post('create', array('uses' => 'APIv1#createUniversity'));
Route::post('read/synonym', array('uses' => 'APIv1#readUniversity'));
});
});
});
The Route::match() in the code above does not work. When I try to access any API call with e.g. GET, the matching is ignored and I get MethodNotAllowedHttpException.
Can I get the second routes structure to work with Route::match() again? I tried to put it literally everywhere in the groups already. Putting the Route::match() outside of the hole structure and setting path to 'api/v1/*' does dot work either.

If you use the post() function you don't need to deny manualy other verb.
What you can do is to create a listener for the MethodNotAllowedHttpException and display what you want. Or you can also use any() function at the end of your route's group to handle all route that is not defined.

Related

Multi domain routing in Laravel 5.2

I have setup multi-domain routing in my laravel 5.2 app. What I want to achieve is if a user hits, membership.app, he should be served different homepage as compared to user who hits, erp.app domain.
Route::pattern('erp', 'erp.app|erp.domain.com');
Route::pattern('membership', 'membership.app|membership.domain.com');
Route::group(['middleware' => ['web', 'auth'], 'domain' => '{erp}'], function() {
Route::get('/', 'HomeController#getIndex');
Route::controller('members', 'MembersController');
Route::controller('users', 'UsersController');
Route::controller('settings', 'SettingsController');
});
Route::group(['middleware' => 'web', 'domain' => '{erp}'], function () {
Route::controller('auth', 'Auth\AuthController');
});
Route::group(['middleware' => 'web', 'domain' => '{membership}'], function () {
Route::controller('/', 'BecomeMemberController');
});
Route::group(['middleware' => 'web'], function () {
Route::controller('ajax', 'AjaxController');
});
I tried this setup, but it breaks the code with first param in each controller method being the url instead of intended value.
Suppose I have a method hello in members controller.
public function hello($param1, $param2)
{
....
}
If I access erp.app/members/hello/1/2 url and try to print out $param1 of controller method, it returns erp.app instead of intended 1 in this case.
Please help.
I don't know why aren't you seperating the routes to different controllers as you say the output will be quite different...
A quick example of to use that:
Route::group(['domain' => '{type}.myapp.com'], function () {
Route::get('members/hello/{id1}/{id2}', function ($type, $id1, $id2) {
// when you enter --> members.myapp.com/hello/12/45
var_dump($type); //memebers
var_dump($id1); //12
var_dump($id2); //45
});
});

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.

Routing confusion in laravel 4

I'm experiencing routing confusion in laravel 4.
Route::group(['prefix' => 'myProfile', 'before' => 'auth|inGroup:Model|isMe'], function()
{
Route::get('/{username}', function(){
echo 'hello';
});
});
Route::get('/{username}', [
'as' => 'show-profile',
'uses' => 'ProfileController#index'
]);
When i write to address bar domain.app/myProfile it runs second route and runs ProfileController#index...
Thanks.
Looks like correct behaviour. To access first route you would have to type something like domain.app/myProfile/FooUser. You didn't specify / route in myProfile route group, so it cannot match it and uses second one.
Breaking down your routes:
1)
Route::get('/{username}', [
'as' => 'show-profile',
'uses' => 'ProfileController#index'
]);
Use /example URI to access the above route.
2)
Route::group(['prefix' => 'myProfile', 'before' =>'auth|inGroup:Model|isMe'], function()
{
Route::get('/{username}', function(){
echo 'hello';
});
});
Use /myProfile/example URI to access the above route.
Your application is working as expected.

Laravel Route::controller with additional parameters

I'm trying to figure out whether there is a way of adding url parameters to the Route::controller call.
What I have at the moment for my control panel is:
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');
}
);
Now - each of the controllers will have the post / getEdit actions, which will require the url id parameter to be passed over in the simple format of /admin/page/edit/{id}.
My question is - is there a way to perhaps add some parameters to the Route::controller method or do I have to do them all using Route::get / Route::post approach?
I know I can do it by adding two extra cases with get and post above the given controller call:
Route::group(
[
'prefix' => 'admin',
'namespace' => 'Admin'
],
function() {
Route::group(
[
'prefix' => '',
'before' => 'auth.admin'
],
function() {
Route::get('page/edit/{id}', 'PageController#getEdit');
Route::post('page/edit/{id}', 'PageController#postEdit');
Route::controller('page', 'PageController');
Route::controller('article', 'ArticleController');
}
);
Route::controller('/', 'LoginController');
}
);
but perhaps there's a better approach?
You can use Route::resource:
Route::resource('resource', 'ResourceController');
This will register the following routes:
GET /resource index resource.index
GET /resource/create create resource.create
POST /resource store resource.store
GET /resource/{resource} show resource.show
GET /resource/{resource}/edit edit resource.edit
PUT/PATCH /resource/{resource} update resource.update
DELETE /resource/{resource} destroy resource.destroy
You can use it together with only or except to choose what routes to be included (or excluded):
Route::resource('resource', 'ResourceController', ['only' => ['index', 'show', 'update', 'destroy']]);
Read more about restful resource controllers in the Laravel documentation.
This post might also be interesting: Laravel 4 - Route::resource vs Route::controller. Which to use?

Prefixing route controllers

I've two route controllers within a route group:
Route::group(array('before' => 'auth'), function()
{
Route::controller('dashboard/', 'DashboardController');
Route::controller('dashboard/profile', 'DashboardProfileController');
});
That works until I add prefix key to the array:
Route::group(array('prefix' => 'dashboard', 'before' => 'auth'), function()
{
Route::controller('/', 'DashboardController');
Route::controller('/profile', 'DashboardProfileController');
});
It's weird as the first route controller works since I can access localhost/dashboard but the second fails on localhost/dashboard/profile and or localhost/dashboard/profile/edit
What's wrong here?!
It seems both of them route to one location, therefore the longest one should go first because it is interpreted as argument.
Route::group(array('prefix' => 'dashboard', 'before' => 'auth'), function()
{
Route::controller('/profile', 'DashboardProfileController');
Route::controller('/', 'DashboardController');
});

Resources