I have setup sub-domain routing on my app (using Laravel 5.4) with the following web.php route:
Route::domain('{company}.myapp.localhost')->group(function () {
// Locations
Route::resource('/locations' , 'LocationController');
// Services
Route::resource('/services' , 'ServiceController');
});
However as my show and edit endpoints require an ID to be passed, using a normal route('services.show') helper results in an ErrorException stating Missing required parameters for [Route: services.create] [URI: services/create].
I appreciate this is necessary, but as the company is associated to the user on login (and is in the sub-domain) I don't want to be passing this for every view. I want to set this at a global level.
To avoid repeated queries, I thought about storing this in the session as so (in the :
protected function authenticated(Request $request, $user)
{
$current_company = $user->companies->first();
$company = [
'id' => $current_company->id,
'name' => $current_company->name,
'display_name' => $current_company->display_name
];
$request->session()->put('company', $company);
}
Which is fine, but I wonder if I can pass this to the route as a middleware or something. What's be best solution here?
Recommendation: remove the slash before the resource name.
The resource method will produce the following URIs:
/services/{service}
So, you should define your routes like this:
Route::domain('{company}.myapp.localhost')->group(function () {
// Locations
Route::resource('locations' , 'LocationController');
// Services
Route::resource('services' , 'ServiceController', ['only' => ['index', 'store']]);
Route::get('services');
});
I ran into this exact issue today, I poked around in the source and found a defaults method on the url generator that allows you to set global default route parameters like so:
app('url')->defaults(['yourGlobalRouteParameter' => $value]);
This will merge in whatever value(s) you specify into the global default parameters for the route url generator to use.
Related
I am building custom views to reset passwords.
The routes looks like this:
Route::get('password/reset', 'Auth\ForgotPasswordController#showLinkRequestForm')->name('password.reset');
Route::post('password/email', 'Auth\ForgotPasswordController#sendResetLinkEmail')->name('password.email');
Route::get('password/reset/{token}', 'Auth\ResetPasswordController#showResetForm')->name('password.reset.token');
Route::post('password/reset', 'Auth\ResetPasswordController#reset');
In ResetPasswordController.php I have added this:
//Show form to seller where they can reset password
public function showResetForm(Request $request, $token = null)
{
return view('auth.passwords.reset')->with(
['token' => $token, 'email' => $request->email]
);
}
The link sent to me looks like this:
https://myapp.dev/password/reset?451c70284a9d4b41123c4ec3efe83602b6cb955427ac48835200a45980bcf9f3
If I now enter that link I will go straight to the password/reset view and not the password/reset/{token}
However if I change the link in my broswer to
https://myapp.dev/password/reset/451c70284a9d4b41123c4ec3efe83602b6cb955427ac48835200a45980bcf9f3 (changing "?" to "/") I it works
So why doesnt the ? version of the URL work? I am using laravel 5.5
And since I dotn use the Auth:routes() is there any way to see what routes laravel generates when you use that?
There are two different things with parameters.
Route Parameters: These are included in the routes with '/' as in your example. You can get them by:
$request->parameter('parameter_name');
$request->parameters(); // for all parameters
Request Parameters: These are request parameters which attached in the URL after '?'. Parameters are sent this way in GET request. You can get them by:
$request->input('parameter_name');
$request->all(); // for all parameters
Laravel doc..
Probably you are confused with required parameters and optional parameters.
When you are defining the following route..
Route::get('password/reset/{token}', 'Auth\ResetPasswordController#showResetForm')->name('password.reset.token');
Laravel expects the token value as a required parameter in the third segment of the route.
But when you are accessing the route as
https://myapp.dev/password/reset?451c70284a9d4b41123c4ec3efe8360..
There is only two segment for the route. The token value is assigned as the get parameter or optional parameter.
As you already defined as follows..
Route::get('password/reset', 'Auth\ForgotPasswordController#showLinkRequestForm')->name('password.reset');
your generated link points to the password/reset and ?451c70284a9d4b41123c4ec3efe83602b6cb955427ac48835200a45980bcf9f3 value is passed as the get parameter.
To trigger your reset the following route
Route::get('password/reset/{token}', 'Auth\ResetPasswordController#showResetForm')->name('password.reset.token');
you should use the following link format
https://myapp.dev/password/reset/451c70284a9d4b41123c4ec3efe83602b6cb955427ac48835200a45980bcf9f3
First of all, in your route
Route::get('password/reset', 'Auth\ForgotPasswordController#showLinkRequestForm')->name('password.reset');
will take precedence over your url. since the route was declared on top and thus will goes into that showLinkRequestForm function first.
Meanwhile in your '/{token}' it will take slashes with the value that you sent thru the get route. Which currently you get.
Route::get('password/reset/{token}', 'Auth\ResetPasswordController#showResetForm')->name('password.reset.token');
Note that, position of route declaration is also affecting . Given example of 2 route with the same url but different name
//1st password/reset
Route::get('password/reset', 'Auth\ForgotPasswordController#showLinkRequestForm')->name('password.reset');
//2nd password/reset
Route::get('password/reset', 'Auth\ForgotPasswordController#showTokenForm')->name('password.reset.form');
In that case route naming will take the latest/last declaration which is 2nd password/reset and the first password/reset will be ignored or made into not available (tested).
So to answer your question which you asking Muhammad Nauman :
"How should I then change my code in order to get given route to work?"
Route::get('password/reset/{token}',
'Auth\ResetPasswordController#showResetForm')->name('password.reset.token');
In blade template you can adjust your routing value looks like this
Reset Form
Inside your new ResetPasswordController.php
public function showResetForm(Request $request)
{
$token = $request->route()->parameter('token');
return view('auth.passwords.reset')->with(
['token' => $token, 'email' => $request->email]
);
}
But if you are kinda like doing dirty way which pass the GET request token email
"?token="somevalue"&email="somevalue" you could do something like this
public function showResetForm()
{
return view('auth.passwords.reset')->with(
['token' => request('token'), 'email' => request('email')]
);
}
Then in the blade you add additional routing parameter of email too
Reset Form
I am having a hard time accessing the Route URL Parameter in Middleware after updating from Laravel 5.1 to Laravel 5.3.
Here is my route file:
Route::group(['middleware' => ['app.access']], function()
{
Route::resource('apps/{apps}/houses', 'HouseController',
['except' => ['index', 'create', 'edit']]);
Route::get('apps/{apps}/houses/colour/{colour}', 'HouseController#colourCheck');
...
}
Kernel.php has RouteMiddleware updated like this:
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
// Added ones....
'jwt.auth' => \Tymon\JWTAuth\Middleware\GetUserFromToken::class,
'jwt.refresh' => \Tymon\JWTAuth\Middleware\RefreshToken::class,
// Custom Middleware
'app.access' => \App\Http\Middleware\AppAccess::class,
];
In Laravel 5.1 and I was able to access route parameter from middleware like this and I had no trouble:
public function handle($request, Closure $next)
{
$appId = $request->route('apps');
...
}
But, since upgrading to Laravel 5.3, the above code started returned $appId as null. I also tried accessing route parameter like $appId = $request->apps and this was not working as well.
So I then changed the Middleware to get the url parameter like this:
public function handle($request, Closure $next)
{
$appId = $request->route()->parameters('apps');
...
}
Since changing to the above, the middleware seems to be working for the first route. But then, I am having problem when going to the second route that has 2 parameters. For some reason, the above code fails and when returning $request->route()->parameters('apps') from middleware, I am getting an array like this:
Array
(
[apps] => 1
[colour] => green
)
Why is that? Why is it not returning just the $appId which is 1? Am I missing something or is it a bug in Laravel 5.3? I want the Middleware to access only the appId parameter from the url and nothing else from the route. Can someone help me here please?
Update 1:
I tried changing the Middleware to get parameter like this:
$parameters = $request->route()->parameters();
$appId = $parameters['apps'];
In the above, I am getting the error:
Undefined index: apps
But when I print_r($parameters);, the output is like this:
Array
(
[apps] => 1
[day] => tuesday
)
[]
Solution:
In the end, I found out the reason for this odd behaviour was due the parameter name in the url.
The following resources routes were generating route url parameter as app instead of apps. I dont know why though:
Route::resource('apps/{apps}/houses', 'HouseController',
['except' => ['index', 'create', 'edit']]);
Whereas the following routes were generating route url parameter as apps:
Route::get('apps/{apps}/houses/colour/{colour}', 'HouseController#colourCheck');
Due to this odd behaviour, some routes were returning null for the parameter name apps and some where showing the parameter. Once I renamed all route parameters to {app} in the routes/api.php and Middleware, the Middleware started working the way it should.
I don't know why the resource routes were creating the parameter as app, but the above fixed the error for me, atleast for now.
There are two ways for accessing parameters in a middleware:
Method 1
$request->route('parameter_name');
Here parameter_name refers to what we called the parameter in the route.
Method 2
$request->route()->parameters();
This method will return an array of all the parameters.
Resource Parameters Are Singular By Default
If you would like to maintain the previous behavior instead of automatically singularizing resource route parameters, you may make the following call to the singularResourceParameters method in your AppServiceProvider:
use Illuminate\Support\Facades\Route;
Route::singularResourceParameters(false);
For me, this was the solution for Laravel 6.
In the routes file add ->middleware() like this:
Route::resource('orders', 'OrdersController')->middleware('roles:Admin,System');
Now to retrieve the parameters in the middleware class:
public function handle($request, Closure $next, ...$roles)
{
// now $roles contains: ['Admin, 'System']
}
I like to use resource controllers in Laravel, as it makes me think when it comes to data modelling. Up to now I’ve got by, but I’m now working on a website that has a public front-end and a protected back-end (administration area).
I’ve created a route group which adds an “admin” prefix, like so:
Route::group(array('before' => 'auth', 'prefix' => 'admin'), function()
{
Route::resource('article', 'ArticleController');
Route::resource('event', 'EventController');
Route::resource('user', 'UserController');
});
And I can access the methods using the default URL structure, i.e. http://example.com/admin/article/1/edit.
However, I wish to use a different URL structure on the front-end, that doesn’t fit into what resource controllers expect.
For example, to access an article, I’d like to use a URL like: http://example.com/news/2014/06/17/some-article-slug. If this article has an ID of 1, it should (under the hood) go to /article/1/show.
How can I achieve this in Laravel? In there some sort of pre-processing I can do on routes to match dates and slugs to an article ID, and then pass that as a parameter to my resource controller’s show() method?
Re-visiting this, I solved it by using route–model binding and a pattern:
$year = '[12][0-9]{3}';
$month = '0[1-9]|1[012]';
$day = '0[1-9]|[12][0-9]|3[01]';
$slug = '[a-z0-9\-]+';
// Pattern to match date and slug, including spaces
$date_slug = sprintf('(%04d)\/(%02d)\/(%02d)\/(%s)', $year, $month, $day, $slug);
Route::pattern('article_slug', $date_slug);
// Perform the route–model binding
Route::bind('article_slug', function ($slug) {
return Article::findByDateAndSlug($date_slug);
});
// The actual route
Route::get('news/{article_slug}', 'ArticleController#show');
This then injects an Article model instance into my controller action as desired.
One simple solution would be to create one more route for your requirement and do the processing there to link it to the main route. So, for example:
//routes.php
Route::get('/arical/{date}/indentifier/{slug}', array (
'uses' => 'ArticleController#findArticle'
));
//ArticleContoller
public function findArticle($date,$slug){
$article = Article::where('slug','=','something')->first(); //maybe some more processing;
$article_id = $article->id;
/*
Redirect to a new route or load the view accordingly
*/
}
Hope this is useful.
It seems like if Laravel 4 supports (:all) in routing, you would be able to do it with ease, but unfortunately (:all) is not supported in Laravel 4.
However, Laravel 4 allows detecting routes by regular expression, so we can use ->where('slug', '.*').
routes.php: (bottom of the file)
Route::get('{slug}', 'ArticleController#showBySlug')->where('slug', '.*');
Since Laravel will try to match the top most route in routes.php first, we can safely put our wildcard route at the bottom of routes.php so that it is checked only after all other criteria are already evaluated.
ArticleController.php:
class ArticleController extends BaseController
{
public function showBySlug($slug)
{
// Slug lookup. I'm assuming the slug is an attribute in the model.
$article_id = Article::where('slug', '=', $slug)->pluck('id');
// This is the last route, throw standard 404 if slug is not found.
if (!$article_id) {
App::abort(404);
}
// Call the controller's show() method with the found id.
return $this->show($article_id);
}
public function show($id)
{
// Your resource controller's show() code goes here.
}
}
The code above assumes that you store the whole URI as the slug. Of course, you can always tailor showBySlug() to support a more advanced slug checking.
Extra:
You could also do:
Route::get('{category}/{year}/{slug}', 'ArticleController#showBySlug')->where('slug', '.*');
And your showBySlug() would just have additional parameters:
public function showBySlug($category, $year, $slug)
{
// code
}
Obviously you can extend to month and day, or other adaptations.
Is there a built-in way to do something like this?
Let's say I have a search-page that has a few parameters in the URL:
example.com/search?term=foo&type=user
A link on that page would redirect to an URL where type is link. I'm looking for a method to do this without manually constructing the URL.
Edit:
I could build the URL manually like so:
$qs = http_build_query(array(
'term' => Input::get('term'),
'type' => Input::get('type')
));
$url = URL::to('search?'.$qs);
However, what I wanted to know is if there is a nicer, built-in way of doing this in Laravel, because the code gets messier when I want to change one of those values.
Giving the URL generator a second argument ($parameters) adds them to the URL as segments, not in the query string.
You can use the URL Generator to accomplish this. Assuming that search is a named route:
$queryToAdd = array('type' => 'user');
$currentQuery = Input::query();
// Merge our new query parameters into the current query string
$query = array_merge($queryToAdd, $currentQuery);
// Redirect to our route with the new query string
return Redirect::route('search', $query);
Laravel will take the positional parameters out of the passed array (which doesn't seem to apply to this scenario), and append the rest as a query string to the generated URL.
See: URLGenerator::route(),
URLGenerator::replaceRouteParameters()
URLGenerator::getRouteQueryString()
I prefer native PHP array merging to override some parameters:
['type' => 'link'] + \Request::all()
To add or override the type parameter and remove another the term:
['type' => 'link'] + \Request::except('term')
Usage when generating routes:
route('movie::category.show', ['type' => 'link'] + \Request::all())
You can do it with Laravel's URLGenerator
URL::route('search', array(
'term' => Input::get('term'),
'link' => Input::get('type')
));
Edit: be sure to name the route in your routes.php file:
Route::get('search', array('as' => 'search'));
That will work even if you're using a Route::controller()
From Laravel documentation:
if your route has parameters, you may pass them as the second argument
to the route method.
In this case, for return an URI like example.com/search?term=foo&type=user, you can use redirect function like this:
return redirect()->route('search', ['term' => 'foo', 'type' => 'user']);
Yes, there is a built in way. You can do your manipulation in Middleware.
The $request passed to the handle method of all middleware has a query property. As an InputBag, it comes with a few methods; Namely, for your intentions: ->set().
Pretty self explanatory, but here's an example:
public function handle(Request $request, Closure $next)
{
$request->query->set('term','new-value');
// now you pass the request (with the manipulated query) down the pipeline.
return $next($request);
}
The Input component should also contain query parameters.
i.e Input::get('foo');
I'm building a multi-tenant app, using the subdomain to separate the users.
e.g. .myapp.com
I want to give each tenant their own database too.
How can I detect the subdomain and set the database dynamically?
Also, the code below is from the official documentation and shows us how we can get the subdomain when setting up a route. But how do we pass the subdomain value to a controller function?
Route::group(array('domain' => '{account}.myapp.com'), function()
{
Route::get('user/{id}', function($account, $id)
{
//
});
});
The best way to achieve this would be in a before filter that you apply to the route group.
Route::group(['domain' => '{account}.myapp.com', 'before' => 'database.setup'], function()
{
// Your routes...
}
This before filters gets a $route parameter and a $request parameter given to it, so we can use $request to get the host.
Route::filter('database.setup', function($route, $request)
{
$account = $request->getHost();
}
You could then use the account to adjust the default database connection using Config::set in the filter. Perhaps you need to use the default connection first up to fetch the users database details.
$details = DB::details()->where('account', '=', $account)->first();
// Make sure you got some database details.
Config::set('database.connections.account', ['driver' => 'mysql', 'host' => $details->host, 'database' => $details->database, 'username' => $details->username, 'password' => $details->password]);
Config::set('database.connections.default', 'account');
During runtime you create a new database connection and then set the default connection to that newly created connection. Of course, you could leave the default as is and simply set the connection on all your models to account.
This should give you some ideas. Please note that none of this code was tested.
Also, each method on your controllers will receive the domain as the first parameter. So be sure to adjust for that if you're expecting other parameters.