$pathInfo = 'store/view/14342/galaxy-s10-lite-sm-g770-8gb';
I have a route array as -
$route = [
'store/{id}/{pid}' => ['home/store', ['id', 'exuo', 'pid']],
'store/{id}' => ['home/store', ['id']],
'store/view/{id}/{name}' => ['home/store', ['id','name']], // pathInfo should match this route
];
How do I match the the $pathInfo with its corresponding route.
this is how i tried to do it -
public function process_route() {
if (is_array($this->routes()) && count($this->routes()) > 0) {
//print_r([$this->rawPathInfo, $this->request, $this->route]) . "<br>";
foreach ($this->routes() as $pattern => $rules) {
$match = str_replace('/', '\/', $pattern);
if (preg_match("/$match/", $this->pathInfo)) {
if (count(explode('/', $this->pathInfo)) == count(explode('/', $pattern))) {
$this->set_params($rules);
return $rules[0];
}
}
}
}
return FALSE;
}
protected function set_params($rules) {
if (count($rules) >= 2) {
if (is_array($rules[1]) && count($rules) >= 2) {
$pathInfoArray = explode("/", $this->pathInfo);
foreach ($rules[1] as $key) {
$index1 = array_search($key, $pathInfoArray);
$value = (isset($pathInfoArray[$index1 + 1])) ? $pathInfoArray[$index1 + 1] : self::$NOT_FOUND;
if ($value !== self::$NOT_FOUND)
$_GET[$key] = $value;
}
}
}
}
the only diff is here I defined the routes as
$routes =[
'store/id/.*/exuo/.*/pid/.*' => ['home/store', ['id', 'exuo', 'pid']],
];
and was matching the values with the (.*) fields.
You could transform your route paths into appropriate regular expressions, check $pathInfo against each of them, then return the first one which matches (if any):
/**
* #param string[] $routes
*/
function findRoute(array $routes, string $pathInfo): ?string
{
foreach (array_keys($routes) as $routePath) {
$pattern = '~^' . preg_replace('/{.*?}/', '[^/]+', $routePath) . '$~';
if (preg_match($pattern, $pathInfo)) {
return $routePath;
}
}
return null;
}
Usage:
findRoute($routes, $pathInfo);
Demo: https://3v4l.org/DoimK
Related
I have a rules in routing config
/**
* Format
* [controller, action($id,...), module]
*
* s: - string
* i: - integer
* {placeholder} - for replace in URL
*/
$rules = [
'/' => ['site', 'index'],
'/[s:action]' => ['site', '{action}'],
'/[s:action]/[s:controller]' => ['{controller}', '{action}'],
'/[s:module]/[s:controller]/[i:id]' => ['{controller}', 'view', '{module}'],
'/[s:module]/[s:controller]/[s:action]/[i:id]' => ['{controller}', '{action}', '{module}'],
'/[s:controller]/[i:id]/[i:id2]/[i:id3]' => ['{controller}', 'parse'],
];
How to get the reverse routing?
//Full URL => Rewrite URL
$results = [
'/' => '/',
'site/index' => '/',
'site/login' => '/login',
'info/user' => '/user/info',
'user/profile/view/123' => '/user/profile/123',
'user/profile/edit/123' => '/user/profile/edit/123',
];
How to do it correctly for best performance?
Update This is routing code
For parse pattern in previous step.
As I understand to be treated as templates and routing parameters, to form the final path.
function checkRule($path, $pattern) {
$params = [];
if ($pattern === '*') {
//Everyone
$match = true;
} elseif (isset($pattern[0]) && $pattern[0] === '#') {
//Custom regexp
$pattern = '`' . substr($pattern, 1) . '`u';
$match = preg_match($pattern, $path, $params);
} else {
//Parse pattern
$n = isset($pattern[0]) ? $pattern[0] : null;
$route = null;
$regex = false;
$j = 0;
$i = 0;
// Find the longest non-regex substring and match it against the URI
while (true) {
if (!isset($pattern[$i])) {
break;
}
if (false === $regex) {
$c = $n;
$regex = $c === '[' || $c === '(' || $c === '.';
if (false === $regex && false !== isset($pattern[$i + 1])) {
$n = $pattern[$i + 1];
$regex = $n === '?' || $n === '+' || $n === '*' || $n === '{';
}
if (false === $regex && $c !== '/' && (!isset($path[$j]) || $c !== $path[$j])) {
return null;
}
$j++;
}
$route .= $pattern[$i++];
}
$regex = self::compileRoute($route);
$match = preg_match($regex, $path, $params);
}
if ($match) {
return $params;
}
return null;
}
function compileRoute($route) {
if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) {
$matchTypes = self::$matchTypes;
foreach ($matches as $match) {
list($block, $pre, $type, $param, $optional) = $match;
if (isset($matchTypes[$type])) {
$type = $matchTypes[$type];
}
if ($pre === '.') {
$pre = '\.';
}
if ($param !== '') {
$param = "?P<{$param}>";
}
if ($optional !== '') {
$optional = '?';
}
$pattern = "(?:{$pre}({$param}{$type})){$optional}";
$route = str_replace($block, $pattern, $route);
}
}
return "`^{$route}$`u";
}
I am trying to make a routing system in php which uses regex. What I need is to match the route specified in route collection and check if the url matches with it, and if it does, return the parameters in the route by matching with the url.
For example
$route="user/{username}/id/{id}/age/{age}/profile";
$url="user/joe/id/99/age/33/profile";
First thing is to check if the $url matches the $route pattern, return false if it doesn't.
Then I need to return an array containing
[
'username'=>'joe',
'id'=>'99',
'age'=>'33',
]
I'm not good at this at all, I had a clumsy go at it.
Here's my current code
<?php
$r="user/username/{name}/id/{id}/age/{age}/profile";
$u="user/username/joe/id/99/age/33/profile";
route::match($r, $u);
class route{
public static function match($route, $url)
{
if(strpos($route, '{')===FALSE)
{
if(strcmp($route, $url)==0)
{
return TRUE;
}
return FALSE;
}
$vars=[];
$umatches=[];
preg_match_all('/\{(.*?)\}/', $route, $matches);
preg_match('/(.*?)\{/', $route, $amatches);
preg_match_all('/\}(.*?)\{/', $route, $bmatches);
$a=preg_split(end($bmatches[1]), $route);
$b=preg_split('/\}(.*?)/', $a[1]);
array_push($umatches, $amatches[1]);
foreach ($bmatches[1] as $key => $value)
{
array_push($umatches, $value);
}
array_push($umatches, $b[1]);
$pattern="/".str_replace('/', '\/', $amatches[1])."/";
$split=preg_split($pattern, $url);
$i=0;
foreach ($umatches as $key => $value) {
$value=str_replace('/', '\/', $value);
$value='/'.$value.'/';
$r=preg_split($value, $url);
$url=$r[1];
if($i>0)array_push($vars, $r[0]);
$i++;
}
print_r($vars);
if(sizeof($matches[1])!=sizeof($vars)) return FALSE;
$params=[];
for ($i=0; $i < sizeof($matches[1]); $i++) {
$params[$matches[1][$i]]=$vars[$i];
}
print_r($params);
return $params;
}
}
Here I ran the code http://ideone.com/blljFM
Not a php guru here. So below is just a quick 2-step solution using
pseudo-code.
global $TemplateArray;
global $UrlArray;
function GetRoutParams ( $strUrlTemplate, $strUrl )
{
$TemplateArray = [];
$UrlArray = [];
// Step 1. Create the regex from the template
$strUrlRegex = preg_replace_callback('~\{([^{}]+)\}~',
function( $matches ){
$repl = '([^/]+)';
// push $matches[1] into a $TemplateArray
return $repl;
},
$strUrlTemplate);
// Step 2. Create the hash from the regex
if ( preg_match($strUrlRegex, $strUrl, $matches) )
{
// Peel off the matches
// (Or, make a hash)
for ( int $i = 0; $i < $matches.count; $i++ )
{
push $UrlArray, $TemplateArray[$i];
push $UrlArray, $matches[$i];
}
// (Or, make a hash)
// $UrlHash[ $TemplateArray[$i] ] = $matches[$i];
}
else
{
return false;
}
return true;
}
Check out this regex with global match against your string:
(user|id|age)\/([^\/]+) it matches the keyword, a '/' and the next path element (which must not contain ANY '/'). Also be aware that for example a "superuser/1/" string would also be matched.
Each match should give you a key-value pair. Looping over the matches you can build your arrays/dicts as you intend.
<?php
$r="user/username/{name}/id/{id}/age/{age}/profile";
$u="user/username/joe/id/99/age/33/profile";
route::match($r, $u);
class route{
public static function match($route, $url)
{
preg_match_all('/(username|id|age)\/([^\/]+)/', $url, $matches);
$params=[];
for ($i=0; $i < sizeof($matches[1]); $i++) {
$params[$matches[1][$i]]=$matches[2][$i];
}
print_r($params);
return $params;
}
}
A little bit more elaborate:
<?php
$r=":user/username/[^/]+/id/[^/]+/age/[^/]+/profile:";
$u="user/username/joe/id/99/age/33/profile";
$k="username|id|age"; # valid keys
print_r(route::match($r, $u, $k));
class route{
public static function match($route, $url, $valid_url_keys)
{
$number_of_expected_key_value_pairs = 3;
$number_of_matches = preg_match_all(":($valid_url_keys)/([^/]+):", $url, $matches);
if($number_of_matches == $number_of_expected_key_value_pairs)
{
$params=[];
for ($i=0; $i < sizeof($matches[1]); $i++) {
$params[$matches[1][$i]]=$matches[2][$i];
}
return $params;
}
else {
return FALSE;
}
}
}
Changed regex delimiter to colon, since the slash is a url-pattern, we need less escaping.
Only returns matches if they match the route specification.
Update: I've implemented sln's solution with a few fixes. Here's it
class Route
{
private static $tmp = array();
public static function GetRoutParams($strUrlTemplate, $strUrl)
{
$strUrlRegex = preg_replace_callback('~\{([^{}]+)\}~',
function ($matches)
{
$repl = '([^)]+)';
self::$tmp[] = $matches[1];
return $repl;
}
, $strUrlTemplate);
$UrlArray = array();
$matches = array();
$strUrlRegex = str_replace('/', '\/', $strUrlRegex);
if (preg_match("/^" . $strUrlRegex . "$/", $strUrl, $matches))
{
for ($i = 0; $i < count(self::$tmp); $i++)
{
$UrlArray[self::$tmp[$i]] = $matches[$i + 1];
}
self::$tmp = array();
return $UrlArray;
}
return false;
}
}
I've completed the code; though it feels inefficient. I'd be grateful if someone helps making the code more efficient.
<?php
$route = "user/{name}/id/{id}/age/{age}/ash";
$url = "user/joe/id/99/age/33/ash";
route::match($route, $url);
class route
{
public static function match($route, $url)
{
if (strpos($route, '{') === FALSE)
{
if (strcmp($route, $url) == 0)
{
return TRUE;
}
return FALSE;
}
$vars = [];
$umatches = [];
//get all parameters in curly braces in $route
preg_match_all('/\{(.*?)\}/', $route, $matches);
//get the string before first occurrence of { in $route
preg_match('/(.*?)\{/', $route, $amatches);
//get all strings between } and { in $route
preg_match_all('/\}(.*?)\{/', $route, $bmatches);
//get the string after last }
if (!empty($bmatches[1])){
$a = preg_split(end($bmatches[1]) , $route);
$b = preg_split('/\}(.*?)/', end($a));
}
else{
$a = preg_split('/' . str_replace('/', '\/', end($amatches)) . '/', $route);
$b = preg_split('/\}(.*?)/', end($a));
}
//push the matches into array $umatches
if (!empty($amatches[1])) array_push($umatches, $amatches[1]);
if (!empty($bmatches[1]))
{
foreach($bmatches[1] as $key => $value)
{
array_push($umatches, $value);
}
}
if (!empty($b[1])) array_push($umatches, $b[1]);
//check if the $url matches with $route template
$prev = - 1;
foreach($umatches as $key => $value)
{
$pos = strpos($url, $value);
if ($pos !== FALSE)
{
if ($prev > $pos) return FALSE;
$prev = $pos;
}
else return FALSE;
}
//push the parameter values in $url into $vars array
$i = 0;
foreach($umatches as $key => $value)
{
$value = str_replace('/', '\/', $value);
$value = '/' . $value . '/';
$r = preg_split($value, $url);
$url = $r[1];
if (!empty($r[0])) array_push($vars, $r[0]);
$i++;
}
if (!empty($r[1])) array_push($vars, $r[1]);
//map the values in $url with the parameters in $route template
$params = [];
for ($i = 0; $i < sizeof($matches[1]); $i++)
{
$params[$matches[1][$i]] = $vars[$i];
}
return $params;
}
}
The code now properly returns the parameters. The problems I had was the code was being preg_split by empty strings in some places. Also I didn't push the value if the parameter occurred in the end of string; for example - user/{username}, here {username} wasn't being pushed properly. Also the comparing the template with given url using sizeof() doesn't always give correct result.
I have dynamically generated array $array[], that could be multidimensional and I have a function that return string, which contains address in array (existing one). My question is: how to create or convert string $a = 'array[1]' to address $array[1]?
Example:
$array = [1,2,3,4];
$string = 'array[2]';
function magic($array, $string){
//some magic happens
return $result;
$result = magic($array, $string);
echo $result;
// and 3 is displayed;
Is there a function already to do this? Is it possible to do this?
This code is a modification of ResponseBag::get() from the wonderful HttpFoundation project:
function magic($array, $path, $default = null)
{
if (false === $pos = strpos($path, '[')) {
return $array;
}
$value = $array;
$currentKey = null;
for ($i = $pos, $c = strlen($path); $i < $c; $i++) {
$char = $path[$i];
if ('[' === $char) {
if (null !== $currentKey) {
throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "[" at position %d.', $i));
}
$currentKey = '';
} elseif (']' === $char) {
if (null === $currentKey) {
throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "]" at position %d.', $i));
}
if (!is_array($value) || !array_key_exists($currentKey, $value)) {
return $default;
}
$value = $value[$currentKey];
$currentKey = null;
} else {
if (null === $currentKey) {
throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "%s" at position %d.', $char, $i));
}
$currentKey .= $char;
}
}
if (null !== $currentKey) {
throw new \InvalidArgumentException(sprintf('Malformed path. Path must end with "]".'));
}
return $value;
}
echo magic([1,2,3,4], 'array[2]'); // 3
It can be modified to return a reference as well, just sprinkle it with some ampersands :)
Is it possible to rewrite config parameter with phalcon?
I use ini File Type.
If not - tell me how to implement please.
if you want to create ini file with php, it is possible, even w/o phalcon.
from PHP docs comments:
Read file : $ini = INI::read('myfile.ini');
Write file : INI::write('myfile.ini', $ini);
custom INI class Features :
support [] syntax for arrays
support . in keys like bar.foo.something = value
true and false string are automatically converted in booleans
integers strings are automatically converted in integers
keys are sorted when writing
constants are replaced but they should be written in the ini file between braces : {MYCONSTANT}
class INI {
/**
* WRITE
*/
static function write($filename, $ini) {
$string = '';
foreach(array_keys($ini) as $key) {
$string .= '['.$key."]\n";
$string .= INI::write_get_string($ini[$key], '')."\n";
}
file_put_contents($filename, $string);
}
/**
* write get string
*/
static function write_get_string(& $ini, $prefix) {
$string = '';
ksort($ini);
foreach($ini as $key => $val) {
if (is_array($val)) {
$string .= INI::write_get_string($ini[$key], $prefix.$key.'.');
} else {
$string .= $prefix.$key.' = '.str_replace("\n", "\\\n", INI::set_value($val))."\n";
}
}
return $string;
}
/**
* manage keys
*/
static function set_value($val) {
if ($val === true) { return 'true'; }
else if ($val === false) { return 'false'; }
return $val;
}
/**
* READ
*/
static function read($filename) {
$ini = array();
$lines = file($filename);
$section = 'default';
$multi = '';
foreach($lines as $line) {
if (substr($line, 0, 1) !== ';') {
$line = str_replace("\r", "", str_replace("\n", "", $line));
if (preg_match('/^\[(.*)\]/', $line, $m)) {
$section = $m[1];
} else if ($multi === '' && preg_match('/^([a-z0-9_.\[\]-]+)\s*=\s*(.*)$/i', $line, $m)) {
$key = $m[1];
$val = $m[2];
if (substr($val, -1) !== "\\") {
$val = trim($val);
INI::manage_keys($ini[$section], $key, $val);
$multi = '';
} else {
$multi = substr($val, 0, -1)."\n";
}
} else if ($multi !== '') {
if (substr($line, -1) === "\\") {
$multi .= substr($line, 0, -1)."\n";
} else {
INI::manage_keys($ini[$section], $key, $multi.$line);
$multi = '';
}
}
}
}
$buf = get_defined_constants(true);
$consts = array();
foreach($buf['user'] as $key => $val) {
$consts['{'.$key.'}'] = $val;
}
array_walk_recursive($ini, array('INI', 'replace_consts'), $consts);
return $ini;
}
/**
* manage keys
*/
static function get_value($val) {
if (preg_match('/^-?[0-9]$/i', $val)) { return intval($val); }
else if (strtolower($val) === 'true') { return true; }
else if (strtolower($val) === 'false') { return false; }
else if (preg_match('/^"(.*)"$/i', $val, $m)) { return $m[1]; }
else if (preg_match('/^\'(.*)\'$/i', $val, $m)) { return $m[1]; }
return $val;
}
/**
* manage keys
*/
static function get_key($val) {
if (preg_match('/^[0-9]$/i', $val)) { return intval($val); }
return $val;
}
/**
* manage keys
*/
static function manage_keys(& $ini, $key, $val) {
if (preg_match('/^([a-z0-9_-]+)\.(.*)$/i', $key, $m)) {
INI::manage_keys($ini[$m[1]], $m[2], $val);
} else if (preg_match('/^([a-z0-9_-]+)\[(.*)\]$/i', $key, $m)) {
if ($m[2] !== '') {
$ini[$m[1]][INI::get_key($m[2])] = INI::get_value($val);
} else {
$ini[$m[1]][] = INI::get_value($val);
}
} else {
$ini[INI::get_key($key)] = INI::get_value($val);
}
}
/**
* replace utility
*/
static function replace_consts(& $item, $key, $consts) {
if (is_string($item)) {
$item = strtr($item, $consts);
}
}
}
find out more here: http://lt1.php.net/parse_ini_file
How would I improve this functions for that is:
Searches an array for fields names which either:
a) Exactly matches
b) beings with "_" check if string begins with
c) ends with "_" check if string ends with
E.g I have a list of column names:
array(
'customer_name',
'customer_lastname',
'customer_streetname',
'customer_dob',
'system_modified'
)
And another array with formatting conditions:
array(
'_dob' => 'date_dob',
'_name' => 'varchar',
'customer_name' => 'html_text',
'system_' => 'required'
)
Results apply the conditions against the column names:
1. customer_name = html_text (exact matches have higher preference)
2. customer_lastname = varchar
3. customer_streetname =
4. customer_dob = dob
5. system_modified = required
Current have this:
protected function matchPatterns($string) {
$return = array();
$parrerns = $this->_normaliseArrayItem($this->getPatterns());
foreach ($parrerns as $match) {
// If exact match
if($string == $match) {
$return[] = $match;
break;
// Else if begins with _ and ends with string.
} elseif($string[0] == "_" && substr_compare($string, $match, -strlen($match), strlen($match)) === 0) {
$return[] = $match;
}
} // end loop
return $return;
}
/**
* Return an array of validation patterns.
*
* #return string[]
*/
public function getPatterns() {
return $this->_patterns;
}
/**
* Returns an item as array rather than single item.
*
* #param string[] $data
* #return string[]
*/
protected function _normaliseArrayItem($data) {
if(!isset($data[0])|| !is_array($data)) {
$tmp[] = $data;
$data = $tmp;
}
return $data;
}
foreach ($parrerns as $match => $format) {
// If exact match
if ($string == $match) {
$return[] = $format;
break;
// Else if begins with _ and string ends with it.
} elseif ($match[0] == "_" && substr_compare($string, $match, -strlen($match), strlen($match)) === 0) {
$return[] = $format;
// Else if ends with _ and string begins with it
} elseif (substr($match, -1) == "_" && substr_compare($string, $match, 0, strlen($match)) == 0) {
$return[] = $format;
}
}
Adapted from Marc's solution.
if(substr($str, 0, 1) === '_' || substr($str, -1) === '_') {
// it starts or ends with an underscore
} else if($str == $match) {
// it's the same
}
I don't entirely understand your question though.