I have an app running on Laravel 4.2, and I am trying to implement a somewhat complex routing mechanism. I have a route group set up using:
Route::group(['domain' => '{wildcard}.example.com'], $closure);
I need to be able to check the $wildcard parameter in the closure for the group -- meaning before the request gets passed to the controller (I need to defined Route::get() and Route::post() depending on the subdomain).
An example of what I'd like to do is as follows:
Route::group(['domain' => '{wildcard}.example.com', function ($wildcard) {
if ( $wildcard == 'subdomain1' ) {
Route::get('route1', 'Subdomain1Controller#getRoute1');
Route::get('route2', 'Subdomain1Controller#getRoute2');
} else if ( $wildcard == 'subdomain2' ) {
Route::get('route1', 'Subdomain2Controller#getRoute1');
Route::get('route2', 'Subdomain2Controller#getRoute2');
}
}
);
Of course, the above does not work. The only parameter passed into a Route::group() closure is an instance of Router, not the parameters defined in the array. However, there must be a way to access those parameters -- I know for a fact that I've done it before, I just don't remember how (and can't find the solution anywhere online).
I know I can always use PHP's lower-level methods to retrieve the URL, explode() it, and check the subdomain that way. But I've done it before using Laravel's methods, and if possible, I'd prefer to do it that way (to keep things clean and consistent)
Does anyone else know the solution? Thanks in advance!
Use the Route::input() function:
Route::group(['domain' => '{wildcard}.example.com', function ($wildcard) use ($wildcard) {
if ( Route::input('wildcard') === 'subdomain1' ) {
Route::get('route1', 'Subdomain1Controller#getRoute1');
Route::get('route2', 'Subdomain1Controller#getRoute2');
} else {
Route::get('route1', 'Subdomain2Controller#getRoute1');
Route::get('route2', 'Subdomain2Controller#getRoute2');
}
}
);
See "Accessing A Route Parameter Value" in the docs.
Related
I am currently using the lumen framework (5.6) to build an API, this API can be used to request a page by for example its title. The route for this is:
Route::group(["prefix" => '/api/v1', "middleware" => ["ContentTypeJson","Paginator"]], function () {
Route::group(["prefix" => '/{databaseIdentifier}', "middleware"=>"DatabaseIdentifier"], function () {
Route::group(["prefix" => '/pages'], function () {
Route::group(["prefix" => '/{title}'], function () {
Route::get("/", "PageController#getPageByTitle");
Route::get("/parents", "SearchController#getParentalSpecies");
Route::get("/all", "PageController#getPageByTitleWithLinks");
Route::get("/overlap/{overlapProperty}", "PageController#getPagesWithOverlap");
Route::put("/", "PageController#overwritePage");
});
});
});
As you can see the title is used in multiple functions and controllers, the same applies to the databaseIdentifier which is used in the middleware to determine which database needs to be used.
However all url parameters with a space will be converted with %20 instead of a space, which is the expected behaviour. However I would like to convert this back to the raw string, which can be done with urldecode().
But since this is applied in every controller and function I would like to use some kind of preprocessing step for this.
I have tried using a middleware for this to alter the route parameters as suggested here (using $request->route()->setParameter('key', $value);).
Unfortunately this does not work in lumen since the result of $request->route() is an array and not an object. I have tried altering this array but I can not get it to change the actual array in the Request object. No error appears here.
So in short: I am looking for a way to urldecode every URL parameter which is passed to my controllers and functions without putting $param = urldecode($param); everywhere.
If you need more information feel free to ask
Thank you in advance
For anyone who also encounters this issue I have found a solution using middleware.
In the middleware I do the following:
public function handle(Request $request, Closure $next)
{
$routeParameters = $request->route(null)[2];
foreach ($routeParameters as $key=>$routeParameter) {
$routeParameters[$key] = urldecode($routeParameter);
}
$routeArray = $request->route();
$routeArray[2] = $routeParameters;
$request->setRouteResolver(function() use ($routeArray)
{
return $routeArray;
});
return $next($request);
}
This code will decode every route parameter and save it in an array, then I take the whole route array which is created by lumen itself (which contains the url encoded parameters), these are then replaced with the url decoded version of the parameter. This is not enough because this does not affect the route array in the Request object.
In order to apply these changes I alter the routeResolver so it will return the changed array instead of the one created by lumen.
I need to get variable "type"
Route::model('type', \App\Models\Document::class, function($type) {
return (new \App\Models\ShareFactory($type));
});
than return object and use it in other bind
Route::model('key', \App\Models\Document::class, function($key) {
return $objectFromFactory::where('share_key', $key)->first();
});
And after all I need to set controller that will process request
Route::get('share/{type}/{key}', 'ProcessShareController#share');
Is it possible? Or I'm trying to code in a wrong way?
It is possible, although solution is not future-proof. This will also work only if in your resolution logic you are using route parameters that appear in the route before the parameter currently resolved.
When route parameters are resolved by Router, they are processed in the same order as they are defined in the path. Each resolved parameter is added to route parameters list.
You can access the list of already resolved parameters by calling
$parameters = Route::getCurrentRoute()->parameters();
You will see all route parameters there even if some have not yet been resolved. Before resolution you will see the string from the URL as their value.
So, in your case, you need the following:
Route::model('type', \App\Models\Document::class, function($type) {
return (new \App\Models\ShareFactory($type));
});
Route::model('key', \App\Models\Document::class, function($key) {
$parameters = Route::getCurrentRoute()->parameters();
$objectFromFactory = $parameters['type'];
return $objectFromFactory::where('share_key', $key)->first();
});
Warning:
Remember that key parameter will always be resolved with above logic, even if there is no type parameter in the route. This might lead to errors, as you always have to make sure that if you define one parameter, you also define the other.
As mentioned, this solution is not future-proof. The logic here relies on the assumption, that route parameters are always processed in the order they are defined. Although I don't see a reason why it could change in the future, there is no guarantee that this won't happen.
I am learning Laravel and have run into a problem handling missing arguments, which has also been explained here. However, the solution(s) focus primarily on named routes, but am using RESTful controllers.
TL;DR Is there some elegant solution to handle missing arguments on all of a given controller's methods without naming each one?
Route
Route::controller('accounts', 'AccountsController');
Controller
Here is a sample controller method...where the "example.com/accounts/profile/1" works, but "example.com/accounts/profile/" [with or without the trailing slash] will throw an exception. "ErrorException: Missing argument 1 for AccountsController::getProfile()"
public function getProfile($id)
{
$account = DB::table('accounts')->where('id', $id)->first();
$this->layout->content = View::make('account.profile', array('account' => $account));
}
One Attempted Solution (Doesn't work)
I've also tried this ↓ , which was a promising-looking suggestion from another question. However, it also does not work.
public function getProfile($id = NULL)
{
if ($id = NULL)
{
return Redirect::to('accounts');
} else {
$account = DB::table('accounts')->where('id', $id)->first();
$this->layout->content = View::make('account.profile', array('account' => $account));
}
}
Other Solution (Works, but is tedious)
I know that using named routes, such as the one below , to cover all of the holes will work, but this seems like such an un-elegant solution! (especially considering the suuuuper tedious process of naming all of the routes for all of the controllers that we are planning on using)
Route::get('/accounts/profile', 'AccountsController#missingMethod');
So....
What do you all think? Is there some elegant solution to handle missing arguments on all of a given controller's methods without naming each one?
The default parameter option should work aside from a minor and very commonly uncaught error in php.. You're using the assignment operator in your conditional. Use
if($id == NULL)
instead of
if($id = NULL)
or if you like
if(is_null($id) )
One of the tragedies of a dynamically typed language. Gets me more frequently than I care to admit.
I'd like my API to handle calls of the such:
/teams/colors
/teams/1/colors
The first would return all colors of all teams, the second would return colors of team 1 only.
How would I write a route rule for this in Laravel?
This should be simple using a laravel route.
Route::pattern('teamid', '[0-9]+');
Route::get('/teams/{teamid}/colors', 'controller#method');
Route::get('/teams/colors', 'controller#method');
Using the pattern, it lets you specify that a route variable must match a specific pattern. This would be possible without the pattern also.
I noticed you mentioned REST in the title. Note that my response is not using Laravel's restful routes system, but its normal routes system, but I'm sure this could be adapted to be restul, or work with the restful system.
Hope this helps.
Edit:
After a bit of looking around, you may be able to use this if you are using Route::resource or Route::controller.
Route::resource('teams', 'TeamsController');
Route::any('teams/{teamid}/colors', 'TeamsController#Method');
// Or to use a different route for post, get and so on.
Route::get('teams/{teamid}/colors', 'TeamsController#getMethod');
Route::post('teams/{teamid}/colors', 'TeamsController#postMethod');
Note: the resource word above can be replaced with ::controller.
*Note 2: I have not tested this and am unable to guarantee it would work, but it does seem possible.*
You may try something like this:
class TeamsController extends BaseController {
// GET : http://example.com/teams
public function getIndex()
{
dd('Colors of all teams');
}
// GET : http://example.com/teams/1/colors
public function getColorsById($id)
{
dd("Colors of team $id");
}
// This method will call the "getColorsById" method
public function missingMethod($parameter = array())
{
if(count($parameter) == 2) {
return call_user_func_array(array($this, 'getColorsById'), $parameter);
}
// You may throw not found exception
}
}
Declare a single route for both methods:
Route::controller('/teams', 'TeamsController');
I need to intercept a route so that I can grab one of its parameters, and use that parameter to query a cache at the onset of the page request. I have two questions:
Is there a "matched" event I can listen for?
Is there a way in Laravel to check if a route parameter key exists/isset, or simply get all parameters from the route?
EDIT: Note that the Route::matched() or Event::listen('router.matched') feature requires 4.1.19 or later, earlier versions will not contain these features.
Yes, you can use something like this:
Event::listen('router.matched', function($route) {
$username = $route->getParameter('username');
if($username && $username == 'heera') {
// do something
}
});
For example, I've a route like Route::get('/user/{username}', 'UserController#show') and the url for this route would be something like http://blog.dev/user/heera so, in the matched event listener I'll get username parameter using $route->getParameter('username') and if it's heera then do something, otherwise do nothing. The $route variable is an instance of Illuminate\Routing\Route and you can use all the public methods of this object.
BTW, you may put it in your filters.php file.
Update: It's also possible to register an event for router.matched event using this:
Route::matched(function($route) {
// Do something
});
Or this:
$app['router']->matched(function($route) {
// do something
});
Tested on Laravel Framework version 4.1.19.