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 :)
Related
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.
Good day. I have a parcer function that taker an array of string like this:
['str','str2','str2','*str','*str2','**str2','str','str2','str2']
And recursivelly elevates a level those starting with asterix to get this
['str','str2','str2',['str','str2',['str2']],'str','str2','str2']
And the function is:
function recursive_array_parser($ARRAY) {
do {
$i = 0;
$s = null;
$e = null;
$err = false;
foreach ($ARRAY as $item) {
if (!is_array($item)) { //if element is not array
$item = trim($item);
if ($item[0] === '*' && $s == null && $e == null) { //we get it's start and end if it has asterix
$s = $i;
$e = $i;
} elseif ($item[0] === '*' && $e != null)
$e = $i;
elseif (!isset($ARRAY[$i + 1]) || $s != null && $e != null) { //if there are no elements or asterix element ended we elevate it
$e = $e == NULL ? $i : $e;
$head = array_slice($ARRAY, 0, $s);
$_x = [];
$inner = array_slice($ARRAY, $s, $e - $s + 1);
foreach ($inner as $_i)
$_x[] = substr($_i, 1);
$inner = [$_x];
$tail = array_slice($ARRAY, $e + 1, 999) or [];
$X = array_merge($head, $inner);
$ARRAY = array_merge($X, $tail);
$s = null;
$e = null;
$err = true;
}
} else {
$ARRAY[$i] = recursive_array_parser($ARRAY[$i]); //if the item is array of items we recur.
}
$i++;
if ($err == true) {
break 1;
}
}
} while ($err);
return $ARRAY;
}
When this function runs, i get "Fatal error: Maximum function nesting level of '200' reached, aborting!" error.
I know it has something to do with infinite recursion, but i can't track the particular place where it occurs, and this is strange.
I don't normally rewrite code, but your code can be reduced and simplified while, from what I can see, getting the desired result. See if this works for you:
$a = array('a','b','c','*d','*e','**f','g','*h');
print_r($a);
$a = recursive_array_parser($a);
print_r($a);
function recursive_array_parser($array)
{
$ret = array();
for($i=0; $i<sizeof($array); $i++)
{
if($array[$i]{0}!='*') $ret[] = $array[$i];
else
{
$tmp = array();
for($j=$i; $j<sizeof($array) && $array[$j]{0}=='*'; $j++)
{
$tmp[] = substr($array[$j],1);
}
$ret[] = recursive_array_parser($tmp);
$i = $j-1;
}
}
return $ret;
}
Note that it isn't possible for $array[$i] to be an array, so that check is removed. The recursion takes place on the temp array created when a * is found. The $i is closer tied to $array to reset it properly after parsing the series of * elements.
Here's my solution. No nested loops.
function recursive_array_parser($arr) {
$out = array();
$sub = null;
foreach($arr as $item) {
if($item[0] == '*') { // We've hit a special item!
if(!is_array($sub)) { // We're not currently accumulating a sub-array, let's make one!
$sub = array();
}
$sub[] = substr($item, 1); // Add it to the sub-array without the '*'
} else {
if(is_array($sub)) {
// Whoops, we have an active subarray, but this thing didn't start with '*'. End that sub-array
$out[] = recursive_array_parser($sub);
$sub = null;
}
// Take the item
$out[] = $item;
}
}
if(is_array($sub)) { // We ended in an active sub-array. Add it.
$out[] = recursive_array_parser($sub);
$sub = null;
}
return $out;
}
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 can i define multiple needles and still perform the same actions below. Im trying to define extra keywords such as numbers, numerals, etc... as of now i have to create a duplicate if loop with the minor keyword change.
if (stripos($data, 'digits') !== false) {
$arr = explode('+', $data);
for ($i = 1; $i < count($arr); $i += 2) {
$arr[$i] = preg_replace('/\d/', '', $arr[$i]);
}
$data = implode('+', $arr);
}
Create a function that loops through an array?
function check_matches ($data, $array_of_needles)
{
foreach ($array_of_needles as $needle)
{
if (stripos($data, $needle)!==FALSE)
{
return true;
}
}
return false;
}
if (check_matches($data, $array_of_needles))
{
//do the rest of your stuff
}
--edit added semicolon
function strposa($haystack, $needles=array(), $offset=0) {
$chr = array();
foreach($needles as $needle) {
$res = strpos($haystack, $needle, $offset);
if ($res !== false) $chr[$needle] = $res;
}
if(empty($chr)) return false;
return min($chr);
}
Usage:
$array = array('1','2','3','etc');
if (strposa($data, $array)) {
$arr = explode('+', $data);
for ($i = 1; $i < count($arr); $i += 2) {
$arr[$i] = preg_replace('/\d/', '', $arr[$i]);
}
$data = implode('+', $arr);
} else {
echo 'false';
}
function taken from https://stackoverflow.com/a/9220624/1018682
Though the previous answers are correct, but I'd like to add all the possible combinations, like you can pass the needle as array or string or integer.
To do that, you can use the following snippet.
function strposAll($haystack, $needles){
if(!is_array($needle)) $needles = array($needles); // if the $needle is not an array, then put it in an array
foreach($needles as $needle)
if( strpos($haystack, $needle) !== False ) return true;
return false;
}
You can now use the second parameter as array or string or integer, whatever you want.
For no reason other than fun I implemented a Trie today. At the moment it supports add() and search(), remove() should also be implemented but I think that's fairly straight forward.
It is fully functional, but filling the Trie with data takes a little too much for my taste. I'm using this list as datasource: http://www.isc.ro/lists/twl06.zip (found somewhere else on SO). It takes ~11s to load. My initial implementation took ~15s so I already gave it a nice performance boost, but I'm still not satisfied :)
My question is: what else could give me a (substantial) performance boost? I'm not bound by this design, a complete overhaul is acceptable.
class Trie
{
private $trie;
public function __construct(TrieNode $trie = null)
{
if($trie !== null) $this->trie = $trie;
else $this->trie = new TrieNode();
$this->counter = 0;
}
public function add($value, $val = null)
{
$str = '';
$trie_ref = $this->trie;
foreach(str_split($value) as $char)
{
$str .= $char;
$trie_ref = $trie_ref->addNode($str);
}
$trie_ref->value = $val;
return true;
}
public function search($value, $only_words = false)
{
if($value === '') return $this->trie;
$trie_ref = $this->trie;
$str = '';
foreach(str_split($value) as $char)
{
$str .= $char;
if($trie_ref = $trie_ref->getNode($str))
{
if($str === $value) return ($only_words ? $this->extractWords($trie_ref) : new self($trie_ref));
continue;
}
return false;
}
return false;
}
public function extractWords(TrieNode $trie)
{
$res = array();
foreach($trie->getChildren() as $child)
{
if($child->value !== null) $res[] = $child->value;
if($child->hasChildren()) $res = array_merge($res, $this->extractWords($child));
}
return $res;
}
}
class TrieNode
{
public $value;
protected $children = array();
public function addNode($index)
{
if(isset($this->children[$index])) return $this->children[$index];
return $this->children[$index] = new self();
}
public function getNode($index)
{
return (isset($this->children[$index]) ? $this->children[$index] : false);
}
public function getChildren()
{
return $this->children;
}
public function hasChildren()
{
return count($this->children)>0;
}
}
Don't know php but,
in the following methods:
public function add($value, $val = null)
{
$str = '';
$trie_ref = $this->trie;
foreach(str_split($value) as $char)
{
$str .= $char;
$trie_ref = $trie_ref->addNode($str);
}
$trie_ref->value = $val;
return true;
}
public function search($value, $only_words = false)
{
if($value === '') return $this->trie;
$trie_ref = $this->trie;
$str = '';
foreach(str_split($value) as $char)
{
$str .= $char;
if($trie_ref = $trie_ref->getNode($str))
{
if($str === $value) return ($only_words ? $this->extractWords($trie_ref) : new self($trie_ref));
continue;
}
return false;
}
return false;
}
Why do you even need the $str .= $char (which I suppose is append)? This itself changes your O(n) time addition/searching to Omega(n^2) (n is length of $value) instead of O(n).
In a trie, you usually walk the trie while walking the string i.e you find the next node based on the current character, rather than the current prefix.
I suppose this implementation is for a Key|value type of insertion and lookup? Here is one that handles [English] words.
class Trie {
static function insert_word(Node $root, $text)
{
$v = $root;
foreach(str_split($text) as $char) {
$next = $v->children[$char];
if ($next === null)
{
$v->children[$char] = $next = new Node();
}
$v = $next;
}
$v->leaf = true;
}
static function get_words_sorted(Node $node, $text)
{
$res = array();
for($ch = 0; $ch < 128; $ch++) {
$child = $node->children[chr($ch)];
if ($child !== null)
{
$res = array_merge($res, Trie::get_words_sorted($child, $text . chr($ch)));
}
}
if ($node->leaf === true)
{
$res[] = $text;
}
return $res;
}
static function search(Node $root, $text)
{
$v = $root;
while($v !== null)
{
foreach(str_split($text) as $char) {
$next = $v->children[$char];
if ($next === null)
{
return false;
}
else
{
$v = $next;
}
}
if($v->leaf === true)
{
return true;
}
else
{
return false;
}
}
return false;
}
}
class Node {
public $children;
public $leaf;
function __construct()
{
$children = Array();
}
}
Example usage
$root = new Node();
$words = Array("an", "ant", "all", "allot", "alloy", "aloe", "are", "ate", "be");
for ($i = 0; $i < sizeof($words); $i++)
{
Trie::insert_word($root, $words[$i]);
}
$search_words = array("alloy", "ant", "bee", "aren't", "allot");
foreach($search_words as $word)
{
if(Trie::search($root, $word) === true)
{
echo $word . " IS in my dictionary<br/>";
}
else
{
echo $word . " is NOT in my dictionary <br/>";
}
}