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)'];
Related
Have a problem to get the id from the URL in a variable!
The Url is like this domain.com/article/1123/
and its like dynamic with many id's
I want to save the 1123 in a variable please help!
a tried it with this
if(isset($_GET['id']) && !preg_match('/[0-9]{4}[a-zA-Z]{0,2}/', $_GET['id'], $id)) {
require_once('404.php');
} else {
$id = $_GET['id'];
}
The absolute simplest way to accomplish this, is with basename()
echo basename('domain.com/article/1123');
Which will print
1123
the reference url click hear
I would do in this way:
Explode the string using /.
Get the length of the exploded array.
Get the last element, which will be the ID.
Code
$url = $_SERVER[REQUEST_URI];
$url = explode("/", $url);
$id = $url[count($url) - 1];
You should definitely be using parse_url to select the correct portion of the URL – just in case a ?query or #fragment exists on the URL
$parts = explode('/', parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
$parts[0]; // 'domain.com'
$parts[1]; // 'article'
$parts[2]; // '1123'
You'll probably want to reference these as names too. You can do that elegantly with array_combine
$params = array_combine(['domain', 'resource', 'id'], $parts);
$params['domain']; // 'domain.com'
$params['resource']; // 'article'
$params['id']; // '1123'
I'm really feeling like a procrastinator right now so I made you a little router. You don't have to bother dissecting this too much right now; first learn how to just use it, then you can pick it apart later.
function makeRouter ($routes, callable $else) {
return function ($url) use ($routes, $else) {
foreach ($routes as $route => $handler) {
if (preg_match(makeRouteMatcher($route), $url, $values)) {
call_user_func_array($handler, array_slice($values, 1));
return;
}
}
call_user_func($else, $url);
};
}
function makeRouteMatcher ($route) {
return sprintf('#^%s$#', preg_replace('#:([^/]+)#', '([^/]+)', $route));
}
function route404 ($url) {
echo "No matching route: $url";
}
OK, so here we'll define our routes and what's supposed to happen on each route
// create a router instance with your route patterns and handlers
$router = makeRouter([
'/:domain/:resource/:id' => function ($domain, $resource, $id) {
echo "domain:$domain, resource:$resource, id:$id", PHP_EOL;
},
'/public/:filename' => function ($filename) {
echo "serving up file: $filename", PHP_EOL;
},
'/' => function () {
echo "root url!", PHP_EOL;
}
], 'route404');
Now let's see it do our bidding ...
$router('/domain.com/article/1123');
// domain:domain.com, resource:article, id:1123
$router('/public/cats.jpg');
// serving up file: cats.jpg
$router('/');
// root url!
$router('what?');
// No matching route: what?
Yeah, I was really that bored with my current work task ...
That can be done quite simple. First of all, you should create a variable with a string that contains your URL. That can be done with the $_SERVER array. This contains information about your server, also the URL you're actually at.
Second point is to split the URL. This can be done by different ways, I like to use the p_reg function to split it. In your case, you want to split after every / because this way you'll have an array with every single "directory" of your URL.
After that, its simply choosing the right position in the array.
$path = $_SERVER['REQUEST_URI']; // /article/1123/
$folders = preg_split('/', $path); // splits folders in array
$your_id = $folders[1];
To be thorough, you'll want to start with parse_url().
$parts=parse_url("domain.com/article/1123/");
That will give you an array with a handful of keys. The one you are looking for is path.
Split the path on / and take the last one.
$path_parts=explode('/', $parts['path']);
Your ID is now in $path_parts[count($path_parts)-1];
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 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.
How do I remove the auto label wrapping that is for the lang() in Codeigniter.
The manual doesn't say anything about it: https://www.codeigniter.com/user_guide/helpers/language_helper.html
Do I have to write a function by myself or is there a simple clean way that Im missing?
Don't write the second parameter. Keep it empty.
Take a look at the lang function (found in: /system/helpers/language_helper.php):
function lang($line, $for = '', $attributes = array())
{
$CI =& get_instance();
$line = $CI->lang->line($line);
if ($for !== '')
{
$line = '<label for="'.$for.'"'._stringify_attributes($attributes).'>'.$line.'</label>';
}
return $line;
}
As you can see it takes 3 parameters. The first parameter is required, but the second two are optional. If you state a second parameter it will return the language string wrapped in a label.
So stating only the first parameter should make it output just the language string.
UPDATE:
From reading your comment it sounds like you would be better off using the language class directly. However the language class alone will not be enough, you will need to extend it for your purpose. To do this you can create a new file in your application/core folder called MY_lang.php.
class MY_Lang extends CI_Lang {
// You want to extend the line function
function line($line = '', $value = '')
{
$line = ($line == '' OR ! isset($this->language[$line])) ? FALSE : $this->language[$line];
// We can assume that if a value is passed it is intended to be inserted into the language string
if($value) {
$line = sprintf($line, $value);
}
// Because killer robots like unicorns!
if ($line === FALSE)
{
log_message('error', 'Could not find the language line "'.$line.'"');
}
return $line ;
}
}
Assuming your language file has a string like so:
$lang['welcome_text'] = "Welcome %s";
You could then use this by loading the language class, and using the following code:
$name = "foo";
$this->lang->line('welcome_text', $name);
The above is 100% untested, so it may need some tweeking, but it should give you somewhere to start from.
I am trying to build a router function to properly match incoming URI's and match them to an array of stored system URI's. I also have wildcards '(:any)' and '(:num)' similar to CodeIgniter.
Basically, I am trying to get the 'admin/stats/(:num)' entry to match on both 'admin/stats' and admin/stats/1'.
While the script is starting I grab all paths from a separate array and use a foreach to save each path:
route('admin/stats/(:num)', array('#title' => 'Statistics',...));
The function is:
function route($path = NULL, $options = NULL) {
static $routes;
//If no arguments are supplied, return all routes stored.
if(!isset($path) && !isset($options)) {
return $routes;
}
//return options for path if $path is set.
if(isset($path) && !isset($options)) {
//If we have an exact match, return it.
if(array_key_exists($path, $routes)) {
return $routes[$path];
}
//Else, we need to use RegEx to find the correct route options.
else {
$regex = str_replace('/', '\/', $path);
$regex = '#^' . $regex . '\/?$#';
//I am trying to get the array key for $route[$path], but it isn't working.
// route_replace('admin/stats/(:num)') = 'admin/stats/([0-9]+)'.
$uri_path = route_replace(key($routes[$path])); //route_replace replaces wildcards for regex.
if(preg_match($regex, $uri_path)) {
return $routes[$path];
}
}
}
$routes[$path] = $options;
return $routes;
}
Route replace function:
function route_replace($path) {
return str_replace(':any', '.+', str_replace(':num', '[0-9]+', $path));
}
A key/value pair in the $routes array looks like:
[admin/stats/(:num)] => Array
(
[#title] => Statistics //Page title
[#access] => user_access //function to check if user is authorized
[#content] => html_stats //function that returns HTML for the page
[#form_submit] => form_stats //Function to handle POST submits.
)
Thanks for the help. This is my first router and I am not that familiar in making proper Regex's.
'admin/stats/(:num)' will never match 'admin/stats' as in your "pattern" the slash is required. In pseduo-regex you need to do something like 'admin/stats(/:num)'.
There does also seem to be a few bugs in your code. This line
$uri_path = route_replace(key($routes[$path]));
is in the block that is executed when $path is not a key that exists in $routes.
I've tried to rewrite it and this seems to work (this is just the else clause):
foreach( array_keys( $routes ) as $route ) {
$regex = '#^' . $route . '?$#';
//I am trying to get the array key for $route'$path', but it isn't working.
// route_replace('admin/stats/(:num)') = 'admin/stats/('0-9'+)'.
$uri_path = route_replace($regex); //route_replace replaces wildcards for regex.
if(preg_match($uri_path,$path)) {
return $routes[$route];
}
}
But this requires 'admin/stats/(:num)' to be 'admin/stats(/:num)'.
btw if you don't have one already, you should get a debugger (Zend and xDebug are two of the most common ones for PHP). They can be invaluable in solving problems like this.
Also, ask yourself if you need to write a router, or whether you can't just use one of the perfectly good ones out there already...