Solution for create optional Route parameter PHP - php

I created Route class for my PHP application. Everything ok, this's my function dispatch:
public function dispatch()
{
//some code...
$search = array();
$regex = preg_replace_callback(
'#{([\w]+)?(:([^/\(\)]*))?}#',
function($m) use (&$search) {
$search[$m[1]] = null;
if (isset($m[3])) {
return '(?P<'.$m[1].'>'.$m[3].')';
}
return '(?P<'.$m[1].'>[^/\?]+)';
},
str_replace(array(')','/+'), array(')?','(/?|/.*?)'), $pattern)
);
$regex .= ($last === '/') ? '?' : '/?';
$regex = '#^'.$regex.'$#i';
if (preg_match($regex, $url, $matches)) {
// some code...
}
// some code...
}
Now, i can use Route look like this:
$router = new Router();
$router->add('/', function() {
echo 'Homepage';
});
$router->add('/user/{id}', function($id) {
echo 'User ID: ' . $id;
});
Optional Route:
$router->add('/user(/{id})', function($id) {
echo 'User ID: ' . $id;
});
It working, but i want change syntax from '/user(/{id})' to '/user/{id?}', how can i do that? I think maybe change regex '#{([\w]+)?(:([^/()]*))?}#' to something, but i can't. Somebody help me?

You can use this to match either: \/\{(\w+)(\?)?\}.
This will match any /{param} or /{param?}. You would check if the '?' was captured to check whether it's optional or not.
That expression will match parameters containing only letters, you could easily expand it to take whatever characters you would want by doing something like this: \/\{([a-zA-Z0-9\-\_])(\?)?\}

Related

PHP change create_function to anonymous function

I have inherited some old code and need to convert the create_function to an anonymous function. I have done that but since I cannot see the code in the anonymous function I do not know if it equals the code in the former create_function.
Here is the code and my question is: is 'my translation' equal to the 'original code'?
public static function makePhpVdtorFromRegex($regex, $match = TRUE)
{
//original code
$s = 'return '.($match?'':'0 == ').'preg_match(\'/'.addslashes($regex).'/\',$v);';
return create_function('$v', $s);
// my translation
return function($v) use ($regex, $match) {
return ($match?'':'0 == ').preg_match('/'.addslashes($regex).'/',$v);
};
}
I believe makePhpVdtorFromRegex stands for 'Make PHP Validator From Regex'. The problem in validating this is I am not sure where the actual validator is used as this anonymous function is stored in an array which is used to validate input at some later time doing form input validation.
Because $regex and $match only exist within makePhpVdtorFromRegex() they will not be available when the validator is ultimately run, right? So I suspect my translation is not going to work?
To mimic the original behaviour, you should be able to replace it with (for testing purposes, I turned the method into a function):
function makePhpVdtorFromRegex($regex, $match = true) {
if ($match) {
return function($value) use ($regex) {
return preg_match('/'.addslashes($regex).'/', $value);
};
}
return function($value) use ($regex) {
// Same as '0 == preg_match(...)' from original code
return !preg_match('/'.addslashes($regex).'/', $value);
};
}
$validator = makePhpVdtorFromRegex('^[a-z]+$');
// Check if something matches
var_dump($validator('abc')); // true
// Check if something doesn't match
$validator = makePhpVdtorFromRegex('^[a-z]+$', false);
var_dump($validator('123')); // true
If you've the chance to look into the actual form validation later on & maybe even take control of the regular expressions themselves, you could rewrite this code to something much simpler, like:
function getRegexValidator() {
return function($regex, $value) {
return preg_match($regex, $value);
};
}
$validator = getRegexValidator();
// Check if something matches
var_dump($validator('/^[a-z]+$/', 'abc')); // true
// Check if something doesn't match
var_dump(!$validator('/^[a-z]+$/', '123')); // true

codeigniter 3 URI routing - geting right hand side from given url

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)'];

Improving speed of regex and using preg_replace for slugs

Previously I have been echoing $obj->html but a current project requires that the HTML be examined for slugs like {whatever} and replacing these with other content.
I have two problems. The first is that this code is slower than I would like:
class Foo {
function draw_content() {
$slug = "/(?<=\{).*(?=\})/";
if (preg_match($slug, $this->html, $matches)) {
foreach ($matches as $match) {
if (method_exists($this,$match)) {
$replacement = $this->$match();
$this->html = preg_replace("/\{$match\}/", $replacement, $this->html);
}
}
}
return $this->html;
} // fn
function new_releases() {
echo "new release book covers";
} // fn
} // class
Is there a better way to get the slug contents? I presume the regex is what is slowing this down?
The second issue is stranger to me. Given this $obj->html:
<p class="headline">New Releases</p>
<p>xxx{new_releases}xxxx</p>
The processed output of $obj->draw_content() is drawn by <?=$obj->draw_content()?>
new release book covers<p class="headline">New Releases</p>
<p>xxxxxxx</p>
Why is the new_releases() output prepended? The slug is gone but the replacement is not in it's place!
you can replace your pattern by:
$slug = '~{\K[^}]*+(?=})~';
IMHO, you should replace your preg_match test and your preg_replace by an only preg_replace_callback function, try something like this (and correct the bugs :).
function draw_content() {
$slug = '~{([^}]*+)}~';
$that = $this;
$this->html = preg_replace_callback( $slug, function ($m) use ($that) {
if (method_exists($that, $m[1]))
return $that->$m[1]();
return $m[0]; }, $this->html);
return $this->html;
}

PHP - Wildcards in URI string

I am trying to create an API request handler that can read wildcards in a string. The ideal situation is something like this.
$myClass->httpGet('/account/[account_id]/list-prefs', function ($account_id) {
// Do something with $account_id
});
Where [account_id] is the wild card. The actual URI would look like:
http://api.example.com/account/123456/list-prefs
The actual function looks like...
function httpGet($resource, $callback) {
$URI = urldecode(str_replace('/'.$this->API_VERSION, '', $_SERVER['REQUEST_URI']));
$match = preg_match_all('/\[([a-zA-Z0-9_]+)\]/', $resource, $array);
if ($resource /*matches with wildcards*/ $URI) {
// Do something with it.
}
...
}
My problem is...
I cannot figure out how to match the string within the function with the URI in order to call the callback.
How to parse the string with the values supplied in the URI (replace [account_id] with 123456).
I think you are missing something like:
tokens = array('[account_id]' => '/\[([a-zA-Z0-9_]+)\]/');
Then:
function replaceTokens($resource) {
# get uri with tokens replaced for actual regular expressions and return it
}
function httpGet($resource, $callback) {
$URI = urldecode(str_replace('/'.$this->API_VERSION, '', $_SERVER['REQUEST_URI']));
$uriRegex = replaceTokens($resource);
$match = preg_match_all($uriRegex, $URI, $array);
if ($match) {
// Do something with it.
}
}

mustache i18n with parameters

I'm trying to use Mustache together with i18n (php, within Wordpress). I've got the basic __ functionality working nicely, something like this
class my_i18n {
public function __trans($string) {
return __($string, 'theme-name');
}
}
class mytache {
public function __()
{
return array('my_i18n', '__trans');
}
}
Then to output a template with an i18n string, I can simply do this
$context = new mytache;
$template = "<div>{{#__}}String to translate{{/__}}</div>";
$m = new Mustache;
echo $m->render($template, $context);
So far everything is fine. However, I want to be able to translate strings with parameters. i.e. the equivalent of sprint_f(__('Account Balance: %s'), $balance);.
It seems that if I do something like {{#__}}Account Balance: {{balance}}{{/__}} it doesn't work. I'm guessing because the inner tag gets converted first and therefore the translation cannot be found for the phrase.
Any ideas how to achieve this cleanly with Mustache?
UPDATE: here's the end-result snippet (with massive help from bobthecow):
class I18nMapper {
public static function translate($str) {
$matches = array();
// searching for all {{tags}} in the string
if (preg_match_all('/{{\s*.*?\s*}}/',$str, &$matches)) {
// first we remove ALL tags and replace with %s and retrieve the translated version
$result = __(preg_replace('/{{\s*.*?\s*}}/','%s', $str), 'theme-name');
// then replace %s back to {{tag}} with the matches
return vsprintf($result, $matches[0]);
}
else
return __($str, 'theme-name');
}
}
class mytache {
public function __()
{
return array('I18nMapper', 'trans');
}
}
I added an i18n example here... it's pretty cheesy, but the test passes. It looks like that's almost the same as what you're doing. Is it possible that you're using an outdated version of Mustache? The spec used to specify different variable interpolation rules, which would make this use case not work as expected.
On my behalf I would suggest using normal, fully functional template engine. I understand, that small is great and everything, but for example Twig is much more advanced. So I would recommend it.
About mustache. Can't you just extend you translation method! For example you pass {{#__}}Account Balance: #balance#{{/__}}
function __( $string, $replacement )
{
$replaceWith = '';
if ( 'balance' == $replacement )
{
$replaceWith = 234.56;
}
return str_replace( '#' . $replacement . '#', $replaceWith, $string );
}
class my_i18n
{
public function __trans( $string )
{
$matches = array();
$replacement = '';
preg_match( '~(\#[a-zA-Z0-9]+\#)~', $string, $matches );
if ( ! empty( $matches ) )
{
$replacement = trim( $matches[0], '#' );
}
return __( $string, $replacement );
}
}
$Mustache = new Mustache();
$template = '{{#__}}Some lime #tag#{{/__}}';
$MyTache = new mytache();
echo $Mustache->render( $template, $MyTache );
This is a very ugly example, but you can make it fancy yourself. As I see Mustache on it's own will not be able to do what you want.
Hope that helped.

Categories