I am new to laravel and I am really struggling to understand how to pass multiple optional url parameters.
What is the standard way to code routes when passing 3 optional parameters to the controller?
Also is there a way to code a route to allow named parameters to be passed to the controller?
such as
public/test/id=1&page=2&opt=1
or
public/test/id=1/page=2/opt=1
Thanks for any help
If you have multiple optional parameters
Route::get('test',array('as'=>'test','uses'=>'HomeController#index'));
And inside your Controller
class HomeController extends BaseController {
public function index()
{
// for example public/test/id=1&page=2&opt=1
if(Input::has('id'))
echo Input::get('id'); // print 1
if(Input::has('page'))
echo Input::get('page'); // print 2
//...
}
}
Named parameters are usually done as route segments but without explicit naming. So for example you could o something like this:
Route:get('test/{id?}/{page?}/{opt?}', function ($id = null, $page = null, $opt = null) {
// do something
});
$id, $page and $opt are all optional here as defined by the ? in the segment definitions, and the fact that they have default values in the function. However, you'll notice there's something of a problem here:
They have to appear in the URL in the correct order
Only $opt is truly optional, $page must be supplied if $opt is, and $id must be if $page is
This is a limitation brought about by the way that Laravel maps the named segments to function/method parameters. You could theoretically implement your own logic to make this work, however:
Route:get('test/{first?}/{second?}/{third?}', function ($first = null, $second = null, $third = null) {
if ($first) {
list($name, $value) = #explode('=', $first, 2);
$$name = $value;
}
if ($second) {
list($name, $value) = #explode('=', $second, 2);
$$name = $value;
}
if ($third) {
list($name, $value) = #explode('=', $third, 2);
$$name = $value;
}
// you should now have $id, $page and $opt defined if they were specified in the segments
});
Not that this is a very naïve solution, relying on blind exploding by = as well as setting the name of an arbitrarily-inputted variable (which is obviously asking for trouble). You should add more checking to this code, but it should give you an idea of how to get over the aforementioned two problems.
It should probably be noted that this is kinda going against the 'right way' to do routing and URIs in Laravel, so unless you really need this functionality, you should rethink the way you are forming these URIs to a way that the Laravel framework is more set-up for.
Related
I am trying to figure out how to achieve a specific URL structure in a Laravel 8 project and the necessary route to achieve this. What I want is:
// Example urls to listings in the business directory.
// These urls should be routed to the directory controller.
www.domain-name.com/example-business-name-d1.html
www.domain-name.com/example-business-name-d15.html
www.domain-name.com/example-business-name-d100.html
www.domain-name.com/example-business-name-d123.html
www.domain-name.com/example-business-name-d432.html
// Example urls to articles/posts in the blog.
// These urls should be routed to the posts controller.
www.domain-name.com/example-post-name-p5.html
www.domain-name.com/example-post-name-p11.html
www.domain-name.com/example-post-name-p120.html
www.domain-name.com/example-post-name-p290.html
www.domain-name.com/example-post-name-p747.html
// We want to avoid the more traditional:
www.domain-name.com/directory/example-business-name-1.html
www.domain-name.com/blog/example-post-name-5.html
This is because we don't want the strings “directory” or “blog” contained in the url for every listing or blog post. Search engine results work better without it.
So far I am using a catch-all route {any} at the bottom of the web.php routes file to “catch all” routes that get that far. I then manipulate the string provided by the path to get the ID and single character token from the end of the urls. I then have these 2 variables but can figure out how to pass these onto the right controllers!
Or am I being really dumb and there is a much better way of achieving this?
Route::get('{any}', function($any = null){
// Break up the url into seperate parts.
$pieces = explode("-", $any);
$pieces = array_reverse($pieces);
$piece = $pieces[0];
// Remove the .html
$piece = substr($piece, 0, -5);
// Get the two parts of the identifier.
$id = substr($piece, 1);
$token = substr($piece, 0, 1);
// Call correct controller based on the token.
switch ($token) {
case "d":
// HERE I WANT TO PASS THE ID ON TO THE SHOW ACTION OF THE DIRECTORY CONTROLLER
break;
case "p":
// HERE I WANT TO PASS THE ID ON TO THE SHOW ACTION OF THE POSTS CONTROLLER
break;
default:
return abort(404);
break;
}
});
I would split the path into 2 variables ($slug and $id) and directly pass it to the controller.
Route::get('{slug}-d{id}.html', 'DirectoryController#show')
->where(['slug' => '([a-z\-]+)', 'id' => '(\d+)']);
Route::get('{slug}-p{id}.html', 'PostController#show')
->where(['slug' => '([a-z\-]+)', 'id' => '(\d+)']);
And in your controllers
class DirectoryController
{
public function show(string $slug, int $id) {}
}
class PostController
{
public function show(string $slug, int $id) {}
}
I can see two ways of achieving this result:
Create an intermediate controller
Route::get('{path}', 'CheckPathController#redirect')
Then in your CheckPathController you do all the checks and your call the proper controller action:
public function redirect(Request $request, $path) {
// Your checks on $path, extract $id and content type
if($isPost) {
$controller = resolve(PostController::class);
return $controller->show($request, $id);
}
if($isBusiness) {
$controller = resolve(BusinessController::class);
return $controller->show($request, $id);
}
// No matches, error 404
abort(404);
}
Complex regex
see: https://laravel.com/docs/8.x/routing#parameters-regular-expression-constraints
I'm not a regexp master, this should be a basic was to match any {word}-{word}-...-p{id}.html pattern but it will break in case of unexpected chars
Route::get('{path}', 'PostController::show')
->where(['path' => '([\w]*-)*p[0-9]+\.html$']);
Route::get('{path}', 'BusinessController::show')
->where(['path' => '([\w]*-)*d[0-9]+\.html$']);
Note that in this case, you controller will receive the pull $path string, so you will need to extract the id there.
You can match the slug using regex
Route::get('/{any}', 'YourController#methodName')->where(['any' => '.*(-d(.*?)\.).*']);
Repeated with p
Then when you pickup your $site in your controller method you can use regex to grab the site.
public function methodName($site)
{
preg_match('/.*(-(d(.*?))\.).*/', $site, $parts); //or something similar, $parts[2] will have what you want
}
OR
This will give your controller method d{number} or p{number}
Route::get('/{site}', function($site) {
$code = preg_match('/.*(-(d(.*?)|p(.*?))\.).*/', $site, $parts) ? $parts[2] : null;
$controllerName = 'ControllerA';
if(isset($code) && !is_null($code) && Str::contains($code, 'p')) {
$controllerName = 'ControllerB';
}
$controller = app()->make('App\Http\Controllers\Application\\' . $controllerName);
return $controller->callAction('methodName', $params = ['code' => $code]);
})->where(['site' => '.*(-(d|p)(.*?)\.).*']);
I need to simulate routing in Codeigniter 3, so my question is how to get the right-hand side from any URL programmatically?
for example, some routes that I have:
$route["blog"] = "Main/blog/en";
$route["blog/(:any)"] = "Main/blog/en/$1";
$route["novosti"] = "Main/blog/sr";
$route["novosti/(:any)"] = "Main/blog/sr/$1";
$route["contact"] = "Main/contact/en";
$route["kontakt"] = "Main/contact/sr";
Now I need a function that can return right-hand side for a given URL part something like this:
echo $this->route->item("novosti/petar")
should print then Main/blog/sr/$1 or Main/blog/sr/petar
Is there such a function in Codeigniter, because I can't find it in documentation?
UPDATE:
I am looking throughout system/router class and I see that protected function _parse_routes is doing something similar so if there is no function that can give me what I need I will create one based on this one.
use this
$this->router->routes['blog']
you will get
Main/blog/en
Codeigniter is simple, too simple... And because it is not obvious for me where that function is (if exists at all) I've just adopted _parse_routes to parse URL (slug) into the right-hand side from which I can find the corresponding file much easier.
Here it is (if someone gets in the same situation I was).
function parseRoute($uri) {
// Get HTTP verb
$http_verb = isset($_SERVER['REQUEST_METHOD']) ? strtolower($_SERVER['REQUEST_METHOD']) : 'cli';
// Loop through the route array looking for wildcards
foreach ($this->router->routes as $key => $val) {
// Check if route format is using HTTP verbs
if (is_array($val)) {
$val = array_change_key_case($val, CASE_LOWER);
if (isset($val[$http_verb])) {
$val = $val[$http_verb];
} else {
continue;
}
}
// Convert wildcards to RegEx
$key = str_replace(array(':any', ':num'), array('[^/]+', '[0-9]+'), $key);
// Does the RegEx match?
if (preg_match('#^' . $key . '$#', $uri, $matches)) {
// Are we using callbacks to process back-references?
if (!is_string($val) && is_callable($val)) {
// Remove the original string from the matches array.
array_shift($matches);
// Execute the callback using the values in matches as its parameters.
$val = call_user_func_array($val, $matches);
}
// Are we using the default routing method for back-references?
elseif (strpos($val, '$') !== FALSE && strpos($key, '(') !== FALSE) {
$val = preg_replace('#^' . $key . '$#', $val, $uri);
}
return $val;
}
}
// If we got this far it means we didn't encounter a
// matching route so we'll set the site default route
return null;
}
Now, this:
echo parseRoute("novosti/petar")
will produce:
Main/blog/sr/petar
AKA: Controler class / function inside that controller / language param / blog article
You can fetch the required info using the below code.
$this->router->routes['novosti/(:any)'];
How may I use this function and inform only the first and the last arguments?
Function
function foo($first = false, $second = false, $third = false, $last = false)
{
if($first && $last)
{
echo 'ok';
}
}
I've tried the code below, but it didn't work...
foo($first = true, $last = true);
PHP doesn't do named arguments as python does. See this question for more info.
However, life can be made easier by using other techniques like...
Modify the signature of the function to accept arguments as an associative array
Function
function foo($parameters)
{
// Provide default values if parameters are not specified
$first = isset($parameters['first']) ? $parameters['first'] : false;
$second = isset($parameters['second']) ? $parameters['second'] : false;
$third = isset($parameters['third']) ? $parameters['third'] : false;
$last = isset($parameters['last']) ? $parameters['last'] : false;
if($first && $last)
{
echo 'ok';
}
}
Call
foo(['first' => true, 'last' => true]);
This way is suitable when you have a number of parameters big and variative enough and you have a complex logic inside the function so that writing all this code pays off.
It is not very convenient, however, because the default values are specified not in an obvious way, there's extra code and it's hard to track parameter usages.
Modify the signature of the function to accept a parameter object which holds all the necessary info
This is the way to go for complex signatures and especially if you have a cascade of methods which use the same arguments. I love it because it solved a big problem with passing up to 10 query parameters through processing pipeline. Now it's just one object with possibility to find every parameter usage and friendly autosuggestion of available parameters when typing ->.
Parameter object class
class ParameterObject
{
public $first = false;
public $second = false;
public $third = false;
public $last = false;
}
Function
function foo(ParameterObject $paramObj)
{
if($paramObj->first && $paramObj->last)
{
echo 'ok';
}
}
Call
$paramObj = new ParameterObject();
$paramObj->first = true;
$paramObj->last = true;
foo($paramObj);
Note! You can modify the object to use method for setting parameters which will provide you with possibility of chaining if you return $this in every set method. So the function call would like like this:
$paramObj = new ParameterObject();
foo($paramObj->setFirst(true)->setSecond(true));
maybe
foo(true, false, false, true);
or change the position of arguments like
function foo($first, $last, $second=false, $third=false)
foo(true, true);
?
if you want to use last argument of function in php you must enter all argument before it and you can't use name of arguments when call functions. in some language like swift can call function with name of argument but not in php
I need to check valid routes from a route files where i want to put a wildcard (or placeholder) for url part that is dynamic.
The router read all routes in that json format:
{"action" : "BlogController#showPost", "method" : "GET", "url" : "showPost/id/{}"}
I need when the comparsion occurs to change the holder {any} with the current value and maybe allow to put regex expression inside the {any} tag.
An url like this:
showPost/id/211 have to be compared with showPost/id/{} and should return true. If possible i would like to allow putting {'[0-9]\'} as optional param to ensure that the real value match a regex expression.
What best solution to do this?
The comparsison method is this:
public static function findAction($query) {
foreach (Router::getInstance()->routes as $route) {
if ($route->url == $query) {
return $route;
}
}
}
The $query contains /showPost/id/221 and the Router::getInstance()->routes->route->url contains showPost/id/{}
The post is related to this auto-solved question:
how to make nice rewrited urls from a router
I don't re-post router code in order to avoid duplication.
Thanks in advance
I found a solution using "?" as a wildcard for routes json file. Its not maybe the best way but actually works.
The method now replace (and try to check) the real path queries with ? and check the routes each cycle.
public static function findAction($query) {
//d($query);
$queryArray = explode("/", $query);
//d($queryArray);
foreach (Router::getInstance()->routes as $route) {
if ($route->url == $query) {
// replace current routes url with incoming url
$route->url = $query;
return $route;
} else {
$queryReplace = null;
foreach ($queryArray as $key => $value) {
if (strpos($route->url,"?")) {
$queryReplace = str_replace("?", $value, $route->url);
if($queryReplace == $query) {
$route->url = $query;
return $route;
}
}
}
}
I still would like to put {any or regex} but atm i did not found a solution to this.
I'm building my first CodeIgniter application and I need to make URLs like follows:
controllername/{uf}/{city}
Example: /rj/rio-de-janeiro
This example should give me 2 parameters: $uf ('rj') and $city ('rio-de-janeiro')
Another URL possible is:
controllername/{uf}/{page}
Example: /rj/3
This example should give me 2 parameters: $uf ('rj') and $page (3)
In other words, the parameters "city" and "page" are optionals.
I can't pass something like '/uf/city/page'. I need always or 'city' OR 'page'.
But I don't know how to configure these routes in CodeIgniter configuration to point to same method (or even to different methods).
I've found the correct result:
$route['controllername/(:any)/(:any)/(:num)'] = 'ddd/index/$1/$2/$3';
$route['controllername/(:any)/(:num)'] = 'ddd/index/$1/null/$2'; // try 'null' or '0' (zero)
$route['controllername/(:any)'] = 'ddd/index/$1';
The Index method (inside "ControllerName") should be:
public function Index($uf = '', $slug = '', $pag = 0)
{
// some code...
if (intval($pag) > 0)
{
// pagination
}
if (!empty($slug))
{
// slug manipulation
}
}
Hope it helps someone.
Thank you all.
public function my_test_function($not_optional_param, $optional_param = NULL)
{
//do your stuff here
}
have you tried this?
For example, let’s say you have a URI like this:
example.com/index.php/mycontroller/myfunction/hello/world
example.com/index.php/mycontroller/myfunction/hello
Your method will be passed URI segments 3 and 4 (“hello” and “world”):
class MyController extends CI_Controller {
public function myFunction($notOptional, $optional = NULL)
{
echo $notOptional; // will return 'hello'.
echo $optional; // will return 'world' using the 1st URI and 'NULL' using the 2nd.
}
}
Reference: https://codeigniter.com/user_guide/general/controllers.html