Pulling my hair out with this one, please help
I have an array $address
$access=sprintf("['results'][1]['address_components'][1]['long_name']");
I want to be able be able get data from array using the string i.e
$home=$address[$access]
Still having probs
print_r($address['results'][1]['address_components'][1]);
$key=sprintf("[results][1][address_components][1][long_name]");
printf("key=%s\n", $key);
$home = eval($address . $key);
exit;
--- Returns
Array
(
[long_name] => High St
[short_name] => A4151
[types] => Array
(
[0] => route
)
)
key=[results][1][address_components][1][long_name]
PHP Parse error: syntax error, unexpected '[', expecting '(' in /media/www.h.com.dev/postCode/post.php(72) : eval()'d code on line 1
If you don't have any user-provided input in $access, you can safely use eval()...
$home = eval("return \$address{$access}");
Note that when doing an eval(), you are passing code as a string. Therefore, you need to ensure that $address is passed as an actual string, not as a variable (use single quotes ' or escape the dollar sign \$ in the double-quoted string); as for $access, you want that to be parsed as code so simply concatenate it.
If you do have user-provided input, you have to parse $access. You can parse $access using token_get_all().
function array_get_node($array, $nodePath) {
$nodePath = '<?php ' . $nodePath;
$tokens = token_get_all($nodePath);
array_shift($tokens);
$current = $array;
$moved = false;
var_dump($tokens);
$tokCount = count($tokens);
for($i = 0; $i < $tokCount; $i++) {
if($tokens[$i] === '[' && isset($tokens[$i+2])
&& $tokens[$i+2] === ']' && is_array($tokens[$i+1])) {
$node = null;
switch($tokens[$i+1][0]) {
case T_LNUMBER:
$node = (int) $tokens[$i+1][1];
break;
case T_CONSTANT_ENCAPSED_STRING:
$node = preg_replace('#^[\'"](.*)[\'"]$#', '\1', $tokens[$i+1][1]);
break;
case T_STRING:
$node = $tokens[$i+1][1];
break;
default:
return null;
break;
}
if(!isset($current[$node])) return null;
$current = &$current[$node];
$moved = true;
$i+=2;
}
}
if($moved)
return $current;
return null;
}
If you trust $access you could use eval. If not, you have to parse $access...
Related
I have an array of user inputs ($atts) as key=>value pairs. Some of the values could be written as an array expression, such as:
'setting' => 'array(50,25)'
In those cases, I would like to convert the array expression contained in that string into an actual array. So the output would be something like:
$atts = array(
'setting' => array(50,25),
'another' => 'not written as an array expression'
)
Written logically, the code would be:
For each key=>value pair in the array $atts...
if the value is a string formatted as an array expression...
explode that value into an array.
Anybody know how I would write this in PHP?
function stringToArray($string) {
$string = "return " . $string . ";";
if (function_exists("token_get_all")) {//tokenizer extension may be disabled
$php = "<?php\n" . $string . "\n?>";
$tokens = token_get_all($php);
foreach ($tokens as $token) {
$type = $token[0];
if (is_long($type)) {
if (in_array($type, array(
T_OPEN_TAG,
T_RETURN,
T_WHITESPACE,
T_ARRAY,
T_LNUMBER,
T_DNUMBER,
T_CONSTANT_ENCAPSED_STRING,
T_DOUBLE_ARROW,
T_CLOSE_TAG,
T_NEW,
T_DOUBLE_COLON
))) {
continue;
}
exit("For your security, we stoped data parsing at '(" . token_name($type) . ") " . $token[1] . "'.");
}
}
}
return eval($string);
}
$a='array(10,20)';
print_r(stringToArray($a));
Use the tokenizer:
function stringToArray($str) {
$array = array();
$toks = token_get_all("<?php $str");
if ($toks[1][0] != T_ARRAY || $toks[2] != '(' || end($toks) != ')')
return null;
for($i=3; $i<count($toks)-1; $i+=2) {
if (count($toks[$i]) != 3)
return null;
if ($toks[$i][0] == T_WHITESPACE) {
$i--;
continue;
}
if ($toks[$i][0] == T_VARIABLE || $toks[$i][0] == T_STRING)
return null;
$value = $toks[$i][1];
if ($toks[$i][0] == T_CONSTANT_ENCAPSED_STRING)
$value = substr($value, 1, strlen($value) - 2);
$array[] = $value;
if ($toks[$i + 1] != ',' && $toks[$i + 1] != ')' && $toks[$i + 1][0] != T_WHITESPACE)
return null;
}
return $array;
}
The above will work only for literals. Passing a variable, a constant, an expression, a nested array or a malformed array declaration will return null:
stringToArray('array(1,2)'); // works
stringToArray('array(1,2.4)'); // works
stringToArray('array("foo",2)'); // works
stringToArray('array(\'hello\',2)'); // works
stringToArray('array()'); // works
stringToArray('array(1,2 + 3)'); // returns null
stringToArray('array(1,2 + 3)'); // returns null
stringToArray('array("foo"."bar")'); // returns null
stringToArray('array(array("hello"))'); // returns null
stringToArray('array($a,$b)'); // returns null
stringToArray('array(new bar)'); // returns null
stringToArray('array(SOME_CONST)'); // returns null
stringToArray('hello'); // returns null
You can also use the following to check if your string is an array expression or not:
function isArrayExpression($str) {
$toks = token_get_all("<?php $str");
return (
$toks[1][0] == T_ARRAY &&
$toks[2] == '(' &&
end($toks) == ')'
);
}
isArrayExpression('array(1,2,3)'); // true
isArrayExpression('array is cool'); // false
isArrayExpression('array(!!!!'); // false
You can always tweak it to your needs. Hope this helps.
As suggested in the comments of your post, eval() will do what you're looking for. However it's not really practical to store arrays as strings in the first place. If you're looking to make data more portable, I'd recommend using json_encode() or even serialize()
You can use eval, but I would highly recommend not using it, and instead try to rethink your design on a better way to handle the settings.
So for your example instead of storing
'setting' => 'array(50,25)'
Could you do something like
'setting' => array('type'=>'array', 'value'=>'50, 25')
then when you load the settings
you can do
switch($type)
case 'array'
$val = explode(', ', $value)
Or something similar
But like others suggested, I would try to save the settings using serialization
I want to dynamically access value of variable, let's say I have this array:
$aData = array(
'test' => 123
);
Standard approach to print the test key value would be:
print $aData['test'];
However, if I have to work with string representation of variable (for dynamic purposes)
$sItem = '$aData[\'test\']';
how can I achieve to print aData key named test? Neither of examples provided below works
print $$sItem;
print eval($sItem);
What would be the solution?
Your eval example is lacking the return value:
print eval("return $sItem;");
should do it:
$aData['test'] = 'foo';
$sItem = '$aData[\'test\']';
print eval("return $sItem;"); # foo
But it's not recommended to use eval normally. You can go into hell's kitchen with it because eval is evil.
Instead just parse the string and return the value:
$aData['test'] = 'foo';
$sItem = '$aData[\'test\']';
$r = sscanf($sItem, '$%[a-zA-Z][\'%[a-zA-Z]\']', $vName, $vKey);
if ($r === 2)
{
$result = ${$vName}[$vKey];
}
else
{
$result = NULL;
}
print $result; # foo
This can be done with some other form of regular expression as well.
As your syntax is very close to PHP an actually a subset of it, there is some alternative you can do if you want to validate the input before using eval. The method is to check against PHP tokens and only allow a subset. This does not validate the string (e.g. syntax and if a variable is actually set) but makes it more strict:
function validate_tokens($str, array $valid)
{
$vchk = array_flip($valid);
$tokens = token_get_all(sprintf('<?php %s', $str));
array_shift($tokens);
foreach($tokens as $token)
if (!isset($vchk[$token])) return false;
return true;
}
You just give an array of valid tokens to that function. Those are the PHP tokens, in your case those are:
T_LNUMBER (305) (probably)
T_VARIABLE (309)
T_CONSTANT_ENCAPSED_STRING (315)
You then just can use it and it works with more complicated keys as well:
$aData['test'] = 'foo';
$aData['te\\\'[]st']['more'] = 'bar';
$sItem = '$aData[\'test\']';
$vValue = NULL;
if (validate_tokens($sItem, array(309, 315, '[', ']')))
{
$vValue = eval("return $sItem;");
}
I used this in another answer of the question reliably convert string containing PHP array info to array.
No eval necessary if you have (or can get) the array name and key into separate variables:
$aData = array(
'test' => 123
);
$arrayname = 'aData';
$keyname = 'test';
print ${$arrayname}[$keyname]; // 123
You can just use it like an ordinary array:
$key = "test";
print $aData[$key];
Likewise $aData could itself be an entry in a larger array store.
As alternative, extracting the potential array keys using a regex and traversing an anonymous array (should have mentioned that in your question, if) with references would be possible. See Set multi-dimensional array by key path from array values? and similar topics.
Personally I'm using a construct like this to utilize dynamic variable paths like varname[keyname] instead (similar to how PHP interprets GET parameters). It's just an eval in sheeps clothing (do not agree with the eval scaremongering though):
$val = preg_replace("/^(\w)+(\[(\w+)])$/e", '$\1["\3"]', "aData[test]");
The only solution in your case is to use Eval().
But please be very very very careful when doing this! Eval will evaluate (and execute) any argument you pass to it as PHP. So if you will feed it something that comes from users, then anyone could execute any PHP code on your server, which goes without saying is a security hole the size of Grand canyon!.
edit: you will have to put a "print" or "echo" inside your $sItem variable somehow. It will either have to be in $sItem ($sItem = 'echo $aData[\'test\']';) or you will have to write your Eval() like this: Eval ( 'echo ' . $sData ).
$sItem = '$aData[\'test\']';
eval('$someVar = '.$sItem.';');
echo $someVar;
Use eval() with high caution as others aldready explained.
You could use this method
function getRecursive($path, array $data) {
// transform "foo['bar']" and 'foo["bar"]' to "foo[bar]"
$path = preg_replace('#\[(?:"|\')(.+)(?:"|\')\]#Uis', '[\1]', $path);
// get root
$i = strpos($path, '[');
$rootKey = substr($path, 0, $i);
if (!isset($data[$rootKey])) {
return null;
}
$value = $data[$rootKey];
$length = strlen($path);
$currentKey = null;
for (; $i < $length; ++$i) {
$char = $path[$i];
switch ($char) {
case '[':
if ($currentKey !== null) {
throw new InvalidArgumentException(sprintf('Malformed path, unexpected "[" at position %u', $i));
}
$currentKey = '';
break;
case ']':
if ($currentKey === null) {
throw new InvalidArgumentException(sprintf('Malformed path, unexpected "]" at position %u', $i));
}
if (!isset($value[$currentKey])) {
return null;
}
$value = $value[$currentKey];
if (!is_array($value)) {
return $value;
}
$currentKey = null;
break;
default:
if ($currentKey === null) {
throw new InvalidArgumentException(sprintf('Malformed path, unexpected "%s" at position %u', $char, $i));
}
$currentKey .= $char;
break;
}
}
if ($currentKey !== null) {
throw new InvalidArgumentException('Malformed path, must be and with "]"');
}
return $value;
}
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Simulate php array language construct or parse with regexp?
suppose I have the string
$str = "array(1,3,4),array(array(4,5,6)),'this is a comma , inside a string',array('asdf' => 'lalal')";
and I try to explode this into an array by comma so that the desired end result is
$explode[0] = array(1,3,4);
$explode[1] = array(array(4,5,6));
$explode[2] = 'this is a comma , inside a string';
$explode[3] = array('asdf' => 'lalal');
simply calling explode(',',$str) is not going to cut it since there are also commas within those chunks...
is there a way to explode this reliably even if there is commas inside the desired chunks
is there a way to explode this reliably even if there is commas inside the desired chunks?
PHP by default does not provide such a function. However you have a compact subset of PHP inside your string and PHP offers some tools here: A PHP tokenizer and a PHP parser.
Therefore it's possible for your string specification to create a helper function that validates the input against allowed tokens and then parse it:
$str = "array(1,3,4),array(array(4,5,6)),'this is a comma , inside a string', array('asdf' => 'lalal')";
function explode_string($str)
{
$result = NULL;
// validate string
$isValid = FALSE;
$tokens = token_get_all(sprintf('<?php %s', $str));
array_shift($tokens);
$valid = array(305, 315, 358, 360, 371, '(', ')', ',');
foreach($tokens as $token)
{
list($index) = (array) $token;
if (!in_array($index, $valid))
{
$isValid = FALSE;
break;
}
}
if (!$isValid)
throw new InvalidArgumentException('Invalid string.');
// parse string
$return = eval(sprintf('return array(%s);', $str));
return $return;
}
echo $str, "\n";
$result = explode_string($str);
var_dump($result);
The tokens used are:
T_LNUMBER (305)
T_CONSTANT_ENCAPSED_STRING (315)
T_DOUBLE_ARROW (358)
T_ARRAY (360)
T_WHITESPACE (371)
The token index number can be given a token name by using token_name.
Which gives you (Demo):
Array
(
[0] => Array
(
[0] => 1
[1] => 3
[2] => 4
)
[1] => Array
(
[0] => Array
(
[0] => 4
[1] => 5
[2] => 6
)
)
[2] => this is a comma , inside a string
[3] => Array
(
[asdf] => lalal
)
)
You can write a simple parser:
function explode_str_arr($str) {
$str.=',';
$escape_char = '';
$str_len = strlen($str);
$cur_value = '';
$return_arr = array();
$cur_bracket_level = 0;
for ($i = 0; $i < $str_len; $i++) {
if ($escape_char) {
if ($str[$i] === $escape_char) {
$escape_char = '';
}
$cur_value.=$str[$i];
continue;
}
switch ($str[$i]) {
case '\'':
case '"':
$escape_char = $str[$i];
break;
case '(':
$cur_bracket_level++;
break;
case ')':
$cur_bracket_level--;
break;
case ',':
if (!$cur_bracket_level) {
$return_arr[] = $cur_value;
$cur_value = '';
continue 2;
}
}
$cur_value.=$str[$i];
}
return $return_arr;
}
It is ugly unicode-breaking fast code, but I think you may get the idea.
I have the following in an INI file:
[country]
SE = Sweden
NO = Norway
FI = Finland
However, when var_dump()ing PHP's parse_ini_file() function, I get the following output:
PHP Warning: syntax error, unexpected BOOL_FALSE in test.ini on line 2
in /Users/andrew/sandbox/test.php on line 1
bool(false)
It appears that "NO" is reserved. Is there any other way I can set a variable named "NO"?
Another hack would be to reverse your ini keys with their values and use array_flip:
<?php
$ini =
"
[country]
Sweden = 'SE'
Norway = 'NO'
Finland = 'FI'
";
$countries = parse_ini_string($ini, true);
$countries = array_flip($countries["country"]);
echo $countries["NO"];
Still you will need to use quotes around NO (at least), if you do
Norway = NO
you don't get an error but value for $countries["NO"] will be an empty string.
This propably comes a little late but the way PHPs parse_ini_file works bothered me so much that I wrote my own little parser.
Feel free to use it, but use with care it has only been shallowly tested!
// the exception used by the parser
class IniParserException extends \Exception {
public function __construct($message, $code = 0, \Exception $previous = null) {
parent::__construct($message, $code, $previous);
}
public function __toString() {
return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
}
}
// the parser
function my_parse_ini_file($filename, $processSections = false) {
$initext = file_get_contents($filename);
$ret = [];
$section = null;
$lineNum = 0;
$lines = explode("\n", str_replace("\r\n", "\n", $initext));
foreach($lines as $line) {
++$lineNum;
$line = trim(preg_replace('/[;#].*/', '', $line));
if(strlen($line) === 0) {
continue;
}
if($processSections && $line{0} === '[' && $line{strlen($line)-1} === ']') {
// section header
$section = trim(substr($line, 1, -1));
} else {
$eqIndex = strpos($line, '=');
if($eqIndex !== false) {
$key = trim(substr($line, 0, $eqIndex));
$matches = [];
preg_match('/(?<name>\w+)(?<index>\[\w*\])?/', $key, $matches);
if(!array_key_exists('name', $matches)) {
throw new IniParserException("Variable name must not be empty! In file \"$filename\" in line $lineNum.");
}
$keyName = $matches['name'];
if(array_key_exists('index', $matches)) {
$isArray = true;
$arrayIndex = trim($matches['index']);
if(strlen($arrayIndex) == 0) {
$arrayIndex = null;
}
} else {
$isArray = false;
$arrayIndex = null;
}
$value = trim(substr($line, $eqIndex+1));
if($value{0} === '"' && $value{strlen($value)-1} === '"') {
// too lazy to check for multiple closing " let's assume it's fine
$value = str_replace('\\"', '"', substr($value, 1, -1));
} else {
// special value
switch(strtolower($value)) {
case 'yes':
case 'true':
case 'on':
$value = true;
break;
case 'no':
case 'false':
case 'off':
$value = false;
break;
case 'null':
case 'none':
$value = null;
break;
default:
if(is_numeric($value)) {
$value = $value + 0; // make it an int/float
} else {
throw new IniParserException("\"$value\" is not a valid value! In file \"$filename\" in line $lineNum.");
}
}
}
if($section !== null) {
if($isArray) {
if(!array_key_exists($keyName, $ret[$section])) {
$ret[$section][$keyName] = [];
}
if($arrayIndex === null) {
$ret[$section][$keyName][] = $value;
} else {
$ret[$section][$keyName][$arrayIndex] = $value;
}
} else {
$ret[$section][$keyName] = $value;
}
} else {
if($isArray) {
if(!array_key_exists($keyName, $ret)) {
$ret[$keyName] = [];
}
if($arrayIndex === null) {
$ret[$keyName][] = $value;
} else {
$ret[$keyName][$arrayIndex] = $value;
}
} else {
$ret[$keyName] = $value;
}
}
}
}
}
return $ret;
}
What does it differently? Variable names may only consist of alphanumerical characters but other than that no restrictions to them. Strings must be encapsulated with " everything else has to be a special value like no, yes, true, false, on, off, null or none. For mapping see code.
Kind of a hack but you can add backticks around the key names:
[country]
`SE` = Sweden
`NO` = Norway
`FI` = Finland
Then access them like so:
$result = parse_ini_file('test.ini');
echo "{$result['`NO`']}\n";
Output:
$ php test.php
Norway
I was getting this error when there were single quote combinations in the string such as 't or 's. To get rid of the problem, I wrapped the string in double quotes:
Before:
You have selected 'Yes' but you haven't entered the date's flexibility
After:
"You have selected 'Yes' but you haven't entered the date's flexibility"
I ran into the same problem and tried to escape the name in every possible way.
Then I remembered that because of the INI syntax both names and values will be trimmed, so the following workaround MAYBE should do the trick:
NL = Netherlands
; A whitespace before the name
NO = Norway
PL = Poland
And it works ;) As long as your co-workers read the comments (which is not always the case) and don't delete it accidentally. So, yes, the array flipping solution is a safe bet.
From the manual page for parse_ini_file:
There are reserved words which must not be used as keys for ini files. These include: null, yes, no, true, false, on, off, none.
So no, you can't set a variable NO.
I am very new to regex, and this is way too advanced for me. So I am asking the experts over here.
Problem
I would like to retrieve the constants / values from a php define()
DEFINE('TEXT', 'VALUE');
Basically I would like a regex to be able to return the name of constant, and the value of constant from the above line. Just TEXT and VALUE . Is this even possible?
Why I need it? I am dealing with language file and I want to get all couples (name, value) and put them in array. I managed to do it with str_replace() and trim() etc.. but this way is long and I am sure it could be made easier with single line of regex.
Note: The VALUE may contain escaped single quotes as well. example:
DEFINE('TEXT', 'J\'ai');
I hope I am not asking for something too complicated. :)
Regards
For any kind of grammar-based parsing, regular expressions are usually an awful solution. Even smple grammars (like arithmetic) have nesting and it's on nesting (in particular) that regular expressions just fall over.
Fortunately PHP provides a far, far better solution for you by giving you access to the same lexical analyzer used by the PHP interpreter via the token_get_all() function. Give it a character stream of PHP code and it'll parse it into tokens ("lexemes"), which you can do a bit of simple parsing on with a pretty simple finite state machine.
Run this program (it's run as test.php so it tries it on itself). The file is deliberately formatted badly so you can see it handles that with ease.
<?
define('CONST1', 'value' );
define (CONST2, 'value2');
define( 'CONST3', time());
define('define', 'define');
define("test", VALUE4);
define('const5', //
'weird declaration'
) ;
define('CONST7', 3.14);
define ( /* comment */ 'foo', 'bar');
$defn = 'blah';
define($defn, 'foo');
define( 'CONST4', define('CONST5', 6));
header('Content-Type: text/plain');
$defines = array();
$state = 0;
$key = '';
$value = '';
$file = file_get_contents('test.php');
$tokens = token_get_all($file);
$token = reset($tokens);
while ($token) {
// dump($state, $token);
if (is_array($token)) {
if ($token[0] == T_WHITESPACE || $token[0] == T_COMMENT || $token[0] == T_DOC_COMMENT) {
// do nothing
} else if ($token[0] == T_STRING && strtolower($token[1]) == 'define') {
$state = 1;
} else if ($state == 2 && is_constant($token[0])) {
$key = $token[1];
$state = 3;
} else if ($state == 4 && is_constant($token[0])) {
$value = $token[1];
$state = 5;
}
} else {
$symbol = trim($token);
if ($symbol == '(' && $state == 1) {
$state = 2;
} else if ($symbol == ',' && $state == 3) {
$state = 4;
} else if ($symbol == ')' && $state == 5) {
$defines[strip($key)] = strip($value);
$state = 0;
}
}
$token = next($tokens);
}
foreach ($defines as $k => $v) {
echo "'$k' => '$v'\n";
}
function is_constant($token) {
return $token == T_CONSTANT_ENCAPSED_STRING || $token == T_STRING ||
$token == T_LNUMBER || $token == T_DNUMBER;
}
function dump($state, $token) {
if (is_array($token)) {
echo "$state: " . token_name($token[0]) . " [$token[1]] on line $token[2]\n";
} else {
echo "$state: Symbol '$token'\n";
}
}
function strip($value) {
return preg_replace('!^([\'"])(.*)\1$!', '$2', $value);
}
?>
Output:
'CONST1' => 'value'
'CONST2' => 'value2'
'CONST3' => 'time'
'define' => 'define'
'test' => 'VALUE4'
'const5' => 'weird declaration'
'CONST7' => '3.14'
'foo' => 'bar'
'CONST5' => '6'
This is basically a finite state machine that looks for the pattern:
function name ('define')
open parenthesis
constant
comma
constant
close parenthesis
in the lexical stream of a PHP source file and treats the two constants as a (name,value) pair. In doing so it handles nested define() statements (as per the results) and ignores whitespace and comments as well as working across multiple lines.
Note: I've deliberatley made it ignore the case when functions and variables are constant names or values but you can extend it to that as you wish.
It's also worth pointing out that PHP is quite forgiving when it comes to strings. They can be declared with single quotes, double quotes or (in certain circumstances) with no quotes at all. This can be (as pointed out by Gumbo) be an ambiguous reference reference to a constant and you have no way of knowing which it is (no guaranteed way anyway), giving you the chocie of:
Ignoring that style of strings (T_STRING);
Seeing if a constant has already been declared with that name and replacing it's value. There's no way you can know what other files have been called though nor can you process any defines that are conditionally created so you can't say with any certainty if anything is definitely a constant or not nor what value it has; or
You can just live with the possibility that these might be constants (which is unlikely) and just treat them as strings.
Personally I would go for (1) then (3).
This is possible, but I would rather use get_defined_constants(). But make sure all your translations have something in common (like all translations starting with T), so you can tell them apart from other constants.
Try this regular expression to find the define calls:
/\bdefine\(\s*("(?:[^"\\]+|\\(?:\\\\)*.)*"|'(?:[^'\\]+|\\(?:\\\\)*.)*')\s*,\s*("(?:[^"\\]+|\\(?:\\\\)*.)*"|'(?:[^'\\]+|\\(?:\\\\)*.)*')\s*\);/is
So:
$pattern = '/\\bdefine\\(\\s*("(?:[^"\\\\]+|\\\\(?:\\\\\\\\)*.)*"|\'(?:[^\'\\\\]+|\\\\(?:\\\\\\\\)*.)*\')\\s*,\\s*("(?:[^"\\\\]+|\\\\(?:\\\\\\\\)*.)*"|\'(?:[^\'\\\\]+|\\\\(?:\\\\\\\\)*.)*\')\\s*\\);/is';
$str = '<?php define(\'foo\', \'bar\'); define("define(\\\'foo\\\', \\\'bar\\\')", "define(\'foo\', \'bar\')"); ?>';
preg_match_all($pattern, $str, $matches, PREG_SET_ORDER);
var_dump($matches);
I know that eval is evil. But that’s the best way to evaluate the string expressions:
$constants = array();
foreach ($matches as $match) {
eval('$constants['.$match[1].'] = '.$match[1].';');
}
var_dump($constants);
You might not need to go overboard with the regex complexity - something like this will probably suffice
/DEFINE\('(.*?)',\s*'(.*)'\);/
Here's a PHP sample showing how you might use it
$lines=file("myconstants.php");
foreach($lines as $line) {
$matches=array();
if (preg_match('/DEFINE\(\'(.*?)\',\s*\'(.*)\'\);/i', $line, $matches)) {
$name=$matches[1];
$value=$matches[2];
echo "$name = $value\n";
}
}
Not every problem with text should be solved with a regexp, so I'd suggest you state what you want to achieve and not how.
So, instead of using php's parser which is not really useful, or instead of using a completely undebuggable regexp, why not write a simple parser?
<?php
$str = "define('nam\\'e', 'va\\\\\\'lue');\ndefine('na\\\\me2', 'value\\'2');\nDEFINE('a', 'b');";
function getDefined($str) {
$lines = array();
preg_match_all('#^define[(][ ]*(.*?)[ ]*[)];$#mi', $str, $lines);
$res = array();
foreach ($lines[1] as $cnt) {
$p = 0;
$key = parseString($cnt, $p);
// Skip comma
$p++;
// Skip space
while ($cnt{$p} == " ") {
$p++;
}
$value = parseString($cnt, $p);
$res[$key] = $value;
}
return $res;
}
function parseString($s, &$p) {
$quotechar = $s[$p];
if (! in_array($quotechar, array("'", '"'))) {
throw new Exception("Invalid quote character '" . $quotechar . "', input is " . var_export($s, true) . " # " . $p);
}
$len = strlen($s);
$quoted = false;
$res = "";
for ($p++;$p < $len;$p++) {
if ($quoted) {
$quoted = false;
$res .= $s{$p};
} else {
if ($s{$p} == "\\") {
$quoted = true;
continue;
}
if ($s{$p} == $quotechar) {
$p++;
return $res;
}
$res .= $s{$p};
}
}
throw new Exception("Premature end of line");
}
var_dump(getDefined($str));
Output:
array(3) {
["nam'e"]=>
string(7) "va\'lue"
["na\me2"]=>
string(7) "value'2"
["a"]=>
string(1) "b"
}