Pass fixed variable from route to controller in Laravel - php

I'm trying to pass a variable through my route to my controller, but I have multiple routes (categories) leading to the same controller i.e.
Route::get('/category1/{region}/{suburb?}', 'SearchController#search');
Route::get('/category2/{region}/{suburb?}', 'SearchController#search');
Making /category1, 2, etc. to be a parameter /{category} is not an option and I don't want to make separate controller function for each category.
How do I send the first segment of the url to my search controller? i.e. category1 or category2?
At present controller is as follows:
public function search($region, $suburb = null) { }
Thanks!

You can specify a mask for your {category} parameter so that it must fit the format "category[0-9]+" in order to match the route.
Route::get('/{category}/{region}/{suburb?}', 'SearchController#search')
->where('category', 'category[0-9]+');
Now, your example url (from the comments) www.a.com/var1/var2/var3 will only match the route if var1 matches the given category regex.
More information can be found in the documentation for route parameters here.
Edit
Yes, this can work with an array of string values. It is a regex, so you just need to put your array of string values into that context:
Route::get('/{category}/{region}/{suburb?}', 'SearchController#search')
->where('category', 'hairdresser|cooper|fletcher');
Or, if you have the array built somewhere else:
$arr = ['hairdresser', 'cooper', 'fletcher'];
// run each array entry through preg_quote and then glue
// the resulting array together with pipes
Route::get('/{category}/{region}/{suburb?}', 'SearchController#search')
->where('category', implode('|', array_map('preg_quote', $arr)));
Edit 2 (solutions for original request)
Your original question was how to pass the hardcoded category segment into the controller. If, for some reason, you didn't wish to use the solution above, you have two other options.
Option 1: don't pass the value in, just access the segments of the request in the controller.
public function search($region, $suburb = null) {
$category = \Request::segment(1);
dd($category);
}
Option 2: modify the route parameters using a before filter (L4) or before middleware (L5).
Before filters (and middleware) have access to the route object, and can use the methods on the route object to modify the route parameters. These route parameters are eventually passed into the controller action. The route parameters are stored as an associative array, so that needs to be kept in mind when trying to get the order correct.
If using Laravel 4, you'd need a before filter. Define the routes to use the before filter and pass in the hardcoded value to be added onto the parameters.
Route::get('/hairdresser/{region}/{suburb?}', ['before' => 'shiftParameter:hairdresser', 'uses' => 'SearchController#search']);
Route::get('/cooper/{region}/{suburb?}', ['before' => 'shiftParameter:cooper', 'uses' => 'SearchController#search']);
Route::get('/fletcher/{region}/{suburb?}', ['before' => 'shiftParameter:fletcher', 'uses' => 'SearchController#search']);
Route::filter('shiftParameter', function ($route, $request, $value) {
// save off the current route parameters
$parameters = $route->parameters();
// unset the current route parameters
foreach($parameters as $name => $parameter) {
$route->forgetParameter($name);
}
// union the new parameters and the old parameters
$parameters = ['customParameter0' => $value] + $parameters;
// loop through the new set of parameters to add them to the route
foreach($parameters as $name => $parameter) {
$route->setParameter($name, $parameter);
}
});
If using Laravel 5, you'd need to define a new before middleware. Add the new class to the app/Http/Middleware directory and register it in the $routeMiddleware variable in app/Http/Kernel.php. The logic is basically the same, with an extra hoop to go through in order to pass parameters to the middleware.
// the 'parameters' key is a custom key we're using to pass the data to the middleware
Route::get('/hairdresser/{region}/{suburb?}', ['middleware' => 'shiftParameter', 'parameters' => ['hairdresser'], 'uses' => 'SearchController#search']);
Route::get('/cooper/{region}/{suburb?}', ['middleware' => 'shiftParameter', 'parameters' => ['cooper'], 'uses' => 'SearchController#search']);
Route::get('/fletcher/{region}/{suburb?}', ['middleware' => 'shiftParameter', 'parameters' => ['fletcher'], 'uses' => 'SearchController#search']);
// middleware class to go in app/Http/Middleware
// generate with "php artisan make:middleware" statement and copy logic below
class ShiftParameterMiddleware {
public function handle($request, Closure $next) {
// get the route from the request
$route = $request->route();
// save off the current route parameters
$parameters = $route->parameters();
// unset the current route parameters
foreach ($parameters as $name => $parameter) {
$route->forgetParameter($name);
}
// build the new parameters to shift onto the array
// from the data passed to the middleware
$newParameters = [];
foreach ($this->getParameters($request) as $key => $value) {
$newParameters['customParameter' . $key] = $value;
}
// union the new parameters and the old parameters
$parameters = $newParameters + $parameters;
// loop through the new set of parameters to add them to the route
foreach ($parameters as $name => $parameter) {
$route->setParameter($name, $parameter);
}
return $next($request);
}
/**
* Method to get the data from the custom 'parameters' key added
* on the route definition.
*/
protected function getParameters($request) {
$actions = $request->route()->getAction();
return $actions['parameters'];
}
}
Now, with the filter (or middleware) setup and in use, the category will be passed into the controller method as the first parameter.
public function search($category, $region, $suburb = null) {
dd($category);
}

Related

How to check url on array using regex in php?

Firstly, I tried all the questions & answers related to this topic. Additionally and I tried related questions and try to solve it but no success. So please read my question thoroughly.
How to check url is valid on my Routes using Regex patterns ?
This Code write "Core PHP" not any Framework.
$routes = [
'PUT' =>
[
'/order/{id}' => 'updateOrder'
],
'GET' =>
[
'/order' => 'getOrder',
'/order/{id}' => 'getOrder',
'/order/status/{id}' =>'getOrderStatus'
],
'POST' =>
[
'/order' =>'createOrder'
],
'DELETE' =>
[
'/order/{id}' => 'deleteOrder'
]
];
My url like :
1) '/order/BPQ153'
2) '/order/status/BPQ123'
3) '/order'
You can simply loop through the routes and find the first matching one. Note that the outer loop in below code is only because I am checking all the sample URLs you provided at once:
$routes = [
'PUT' =>
[
'/order/{id}' => 'updateOrder'
],
'GET' =>
[
'/order' => 'getOrder',
'/order/{id}' => 'getOrder',
'/order/status/{id}' => 'getOrderStatus'
],
'POST' =>
[
'/order' => 'createOrder'
],
'DELETE' =>
[
'/order/{id}' => 'deleteOrder'
]
];
$urlsToCheck = [
'/order/BPQ153',
'/order/status/BPQ123',
'/order',
];
foreach ($urlsToCheck as $url) {
foreach ($routes as $method => $methodValues) {
foreach ($methodValues as $route => $function) {
// match ID to everything that is not a slash
$regex = str_replace('{id}', '([^\/]+)', $route);
if (preg_match('#^' . $regex . '$#', $url)) {
echo "The URL $url matches on $method HTTP method for function $function.";
echo PHP_EOL;
}
}
}
}
this outputs:
The URL /order/BPQ153 matches on PUT HTTP method for function updateOrder.
The URL /order/BPQ153 matches on GET HTTP method for function getOrder.
The URL /order/BPQ153 matches on DELETE HTTP method for function deleteOrder.
The URL /order/status/BPQ123 matches on GET HTTP method for function getOrderStatus.
The URL /order matches on GET HTTP method for function getOrder.
The URL /order matches on POST HTTP method for function createOrder.
As you can see, I did not check for a specific HTTP method, but you would have to add an extra check in there depending on the current HTTP method that is used. However that was not part of the question, so I am only mentioning it here for completeness.
P.S.: For cleaner code you can of course put this into a function / method / class, I just tried to keep the code as short as possible here.
First of all you have to define your routes a little more detailed. Otherwise, it is not clear how your placeholders in the curly brackets should be used. Of course you as a programmer know that there should be a numerical value for an ID. Your validator may not know this.
So let us have a look, how you can define your routes a little bit more detailed.
$routes = [
[
'controller' => SomeController::class,
'route' => '/order/{id}',
'parameters' => [ 'id' => '([0-9a-z]+)' ],
'allowed_methods' => [ 'GET' ],
]
];
This is just an example entry for a route. A route contains the controller, which has to be called, when this route is requested. Also the route itself is mentioned here. Beside that we define a parameter called id which acts as a placeholder in your route and we define the allowed request methods. In this case the route should only be accessible via GET requests. In our small example here, we just need the parameters and the route. The below shown router class does not recognize the request method and the controller.
So how a route can be resolved and validated? All we have to know is now defined in the route. When a request happens, we can check against the route.
Here 's a small router class example. Ofcourse this small example should not be used for production.
declare(strict_types=1);
namespace Marcel\Router;
class Router
{
protected array $routes = [];
protected array $filters = [];
public function __construct(array $routes)
{
$this->routes = $routes;
}
public function match(string $request) : bool
{
foreach ($this->routes as $route) {
// find parameters and filters (regex) in route
if (preg_match_all('/\{([\w\-%]+)\}/', $route['route'], $keys)) {
$keys = $keys[1];
}
foreach ($keys as $key => $name) {
if (isset($route['parameters'][$name])) {
$this->filters[$name] = $route['parameters'][$name];
}
}
// match requested route against defined route
$regex = preg_replace_callback('/\{(\w+)\}/', [ $this, 'substituteFilter' ], $route['route']);
$filter = rtrim($regex, '/');
$pattern = '#^' . $filter . '/?$#i';
// if not matching, check the next route
if (!preg_match($pattern, $request, $matches)) {
continue;
}
return true;
}
return false;
}
protected function substituteFilter(array $matches): string
{
if (isset($matches[1], $this->filters[$matches[1]])) {
return $this->filters[$matches[1]];
}
return '([\w\-%]+)';
}
}
This small router class example tests the given urls against the collection of routes. The class pays attention to the placeholders that can be filled with a regular expression. So the class checks every request against the defined regex for the given placeholder.
So let us test this little class against some requests
$router = new Router($routes);
$result = $router->match('/order/BPQ123');
var_dump($result); // true
$result = $router->match('/bla/yadda/blubb');
var_dump($result); // false

Laravel 5 Controller using variable arguments

I have a controller in Laravel 5.
I would like to write a controller function that accepts variable arguments.
For example,
public function show(Request $request, ...$id)
{
// handle multiple $id values here
}
The reason is that I have a url structure that has 'nested' models.
For instance:
\item\{$id}
\parent\{$parent_id}\item\{$id}
\grandparent\{$grandparent_id}\parent\{$parent_id}\item\{$id}
The routes are defined as:
Route::resource('item', 'ItemController');
Route::resource('parent.item', 'ParentController');
Route::resource('grandparent.parent.item', 'GrandparentController');
My desire is to write a single show() method as a trait that each controller can use.
Because of the structure of my database, it is possible.
But the UrlGenerator keeps throwing a UrlGenerationException when I try to use variable arguments. It seems like it doesn't understand this construct?
Ok, here's an idea for you that should get you on the right path:
For the various resource routes you defined, re-declare them to exclude the 'show' action, and define a separate GET route to map the routes you are trying to centralise.
app/Http/routes.php:
Route::resource('item', 'ItemController', ['except' => ['show']]);
Route::get('item/{item}', ['uses' => 'AggregateController#handleShow', 'as' => 'item.show']);
Route::resource('parent.item', 'ParentController', ['except' => ['show']]);
Route::get('parent/{parent}/item/{item}', ['uses' => 'AggregateController#handleShow', 'as' => 'parent.item.show']);
Route::resource('grandparent.parent.item', 'GrandParentController', ['except' => ['show']]);
Route::get('grandparent/{grandparent}/parent/{parent}/item/{item}', ['uses' => 'AggregateController#handleShow', 'as' => 'grandparent.parent.item.show']);
app/Http/Controllers/AggregateController.php:
class AggregateController extends Controller
{
public function handleShow(Request $request, ...$items)
{
dd($request->path(), $items);
}
}
http://stackoverflow42005960.dev/grandparent/1/parent/2/item/3:
"grandparent/1/parent/2/item/3"
array:3 [▼
0 => "1"
1 => "2"
2 => "3"
]
If you still have issues with getting the variable arguments, then check your PHP version and if < 5.6 you'll have to use func_get_args()
There're many ways to go about this. For example, you can use a comma separated list in routes and simply explode in controller.
The way you have it currently, you will have to use a fixed number of optional parameters, e.g.
public function show(Request $request, $id1, $id2 = false, $id3 = false)
{
//if parent item exists
if($id2)
{
//if grandparent item resource
if($id3)
{
}
}
else
{
//just item
}
}

How can I get segment for current page in Laravel 5?

I have URL like this /project/1
How can I get param 1
I need it variable in another controller for another route....
here is examle:
route 1:
Route::get('project/{id}',array(
'as' => 'projectID',
'uses' => 'FirstController#someMethod'
));
route 2:
Route::post('another/route',array(
'as' => 'another',
'uses' => 'SecondController#anotherMethod'
));
I need to get inside anotherMethod id param from project/{id}... I tried like this return Request::segment(2); but it will return just segments from this route: another/route...
Any solution?
You can try this:
Controller:
public function index(Request $request){
return $request->segment(2); //set the segment number it depends on you
}
public function someMethod(Request $request)
{
// If you know the segment number
$id = $request->segment(2);
// If you know the parameter name
$id = $request->route('id');
// If you only know it's the last segment
$segments = $request->segments();
$id = array_pop($segments);
}

Passing arguments to a filter - Laravel 4

Is it possible to access route parameters within a filter?
e.g. I want to access the $agencyId parameter:
Route::group(array('prefix' => 'agency'), function()
{
# Agency Dashboard
Route::get('{agencyId}', array('as' => 'agency', 'uses' => 'Controllers\Agency\DashboardController#getIndex'));
});
I want to access this $agencyId parameter within my filter:
Route::filter('agency-auth', function()
{
// Check if the user is logged in
if ( ! Sentry::check())
{
// Store the current uri in the session
Session::put('loginRedirect', Request::url());
// Redirect to the login page
return Redirect::route('signin');
}
// this clearly does not work..? how do i do this?
$agencyId = Input::get('agencyId');
$agency = Sentry::getGroupProvider()->findById($agencyId);
// Check if the user has access to the admin page
if ( ! Sentry::getUser()->inGroup($agency))
{
// Show the insufficient permissions page
return App::abort(403);
}
});
Just for reference i call this filter in my controller as such:
class AgencyController extends AuthorizedController {
/**
* Initializer.
*
* #return void
*/
public function __construct()
{
// Apply the admin auth filter
$this->beforeFilter('agency-auth');
}
...
Input::get can only retrieve GET or POST (and so on) arguments.
To get route parameters, you have to grab Route object in your filter, like this :
Route::filter('agency-auth', function($route) { ... });
And get parameters (in your filter) :
$route->getParameter('agencyId');
(just for fun)
In your route
Route::get('{agencyId}', array('as' => 'agency', 'uses' => 'Controllers\Agency\DashboardController#getIndex'));
you can use in the parameters array 'before' => 'YOUR_FILTER' instead of detailing it in your constructor.
The method name has changed in Laravel 4.1 to parameter. For example, in a RESTful controller:
$this->beforeFilter(function($route, $request) {
$userId = $route->parameter('users');
});
Another option is to retrieve the parameter through the Route facade, which is handy when you are outside of a route:
$id = Route::input('id');

How does one define default key=>value pair behavior on route params in Phalcon?

I would like to define the following default behavior on my routes:
Url: myapp.com/mymodule/mycontroller/myaction/q/someTerm/key/someValue/key2/anotherValue
This url should give the dispatcher the following params:
array(
'q' => 'someTerm',
'key' => 'someValue',
'key2' => 'anotherValue'
):
I know it can be easily done by extending the router and implementing my own, but I was wondering if Phalcon has a default flag that switches that approach on by default.
Right now, if I apply the route
':module/:controller/:action/:params' to this URL, I get the following params:
array(
0 => 'q',
1 => 'someTerm',
2 => 'key'
... etc
);
Which is something I don't want.
If the Phalcon router doesn't have a default flag that does this, is there an event that fires immediately before the params become available in the DI's dispatcher in the controller? I would like to manually map them into key=>value pairs at least, before they reach the controller.
Edit: I am now looking into the beforeExecuteRoute event in the controller, should do what I need. But I'd still like it if the router did this automatically - sometimes params aren't in a fixed order, or some of them just disappear (think complex search queries).
You can intercept the 'beforeDispatchLoop' event in the dispatcher to transform the parameters before dispatch the action:
$di['dispatcher'] = function(){
$eventsManager = new Phalcon\Events\Manager();
$eventsManager->attach('dispatch', function($event, $dispatcher) {
if ($event->getType() == 'beforeDispatchLoop') {
$keyParams = array();
$params = $dispatcher->getParams();
foreach ($params as $number => $value) {
if ($number & 1) {
$keyParams[$params[$number - 1]] = $value;
}
}
$dispatcher->setParams($keyParams);
}
});
$dispatcher = new Phalcon\MVc\Dispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
});
Then use the transformed parameters in the controller:
class IndexController extends ControllerBase
{
public function indexAction()
{
print_r($this->dispatcher->getParams());
print_r($this->dispatcher->getParam('key'));
}
}

Categories