How does extract() create variables in the current scope? - php

I'm curious, how does the PHP's function extract do it's work? I would like to make a slightly modified version. I want my function to make the variable names when extracting from the keys of the array from snake notation to camelCase e.g:
Now extract does this:
$array = ['foo_bar' => 'baz'];
extract($array);
// $foo_bar = 'baz';
What I would like is:
camelExtract($array);
// $fooBar = 'baz';
Now I could of course camelCase the array first, but it would be nice if this could be done in a single function.
edit:
It seems some people misread my question. Yes I could do this:
function camelExtract($array)
{
$array = ['foo_bar' => 'baz'];
$camelCased = [];
foreach($array as $key => $val)
{
$camelCased[camelcase($key)] = $val;
}
extract($camelCased);
// $fooBar = 'baz';
// I can't "return" the extracted variables here
// .. now $fooBar is only available in this scope
}
camelExtract($array);
// Not here
But as I've stated, then the $fooBar is only visible within that scope.
I guess I could do something as extract(camelCaseArray($array)); and that would work.

This should work:-
function camel(array $arr)
{
foreach($arr as $a => $b)
{
$a = lcfirst(str_replace(" ", "", ucwords(str_replace("_", " ", $a))));
$GLOBALS[$a] = $b;
}
}

You can (cautiously) use variable variables:
function camelExtract($vals = array()) {
foreach ($vals as $key => $v) {
$splitVar = explode('_', $key);
$first = true;
foreach ($splitVar as &$word) {
if (!$first) {
$word = ucfirst($word);
}
$first = false;
}
$key = implode('', $splitVar);
global ${$key};
${$key} = $v;
}
}
This has now been tested and functions as expected. This condensed answer (after it addressed the lowercase first word) also works great and is much more condensed - mine is just a little more of a "step by step" to work through how the camel is done.

extract, and modification to the callees local symbol table from within a called function is magic. There is no way to perform the equivalent in plain-PHP without using it.
The final task can be solved using John Conde's suggesting of using extra after performing a transformation to the supplied array keys; although my recommendation is to avoid extract-like behavior entirely. The approach would then look similar to
extract(camelcase_keys($arr));
where such code is not wrapped in a function so that extract is executed from the scope of the symbol table in which to import the variables.
This extract behavior is is unlike variable-variables (in a called function) and is unlike using $GLOBALS as it mutates the callees (and only the callees) symbol table as see seen in this demo:
function extract_container () {
extract(array("foo" => "bar"));
return $foo;
}
echo "Extract: " . extract_container() . "\n"; // "bar" =>
echo "Current: " . $foo . "\n"; // => {no $foo in scope}
echo "Global: " . $GLOBALS['foo'] . "\n"; // => {no 'foo' in GLOBALS}
The C implementation for extract can be found in ext/standard/array.c. This behavior is allowed because the native function does not create a new/local PHP symbol table for itself; as such it is allowed to (trivially) modify the symbol table of the calling PHP context.

<?php
$arr = array('foo_bar'=>'smth');
function camelExtract($arr) {
foreach($arr as $k=>$v) {
$newName = lcfirst(str_replace(" ","",ucwords(str_replace("_"," ",$k))));
global $$newName;
$$newName = $v;
//var_dump($newName,$$newName);
}
}
camelExtract($arr);
?>
or just like (t's what you suggest, and better to mimic the original extract)
$camelArray[lcfirst(str_replace(" ","",ucwords(str_replace("_"," ",$k))))] = $v;
and extract on the resulting camelArray

Related

how to use global multidimensional arrays in PHP?

I tries to use multidimensional global arrays in PHP. Unfortunately, so far I don't know how to do it...
global $datawords;
function unfold_sentence($str)
{
$sentence_arr = preg_split('/[.?!]/',$str);
$i=0;
foreach ($sentence_arr as $value) {
$i++;
$datawords['sentence'][$i]['sentence'] = $value;
$datawords['sentence'][$i]['words'] = preg_split( '/[-?!.,;: ]+/', $value);
}
}
unfold_sentence("hello! how are you?");
print_r($datawords['sentence'][1]['sentence']); //empty echo
I tried to use $GLOBALS - instead of $datawords - unsuccessfully.
global is a keyword that you would use in the function where you want to reference the global variable. The PHP documentation gives an example for using the global keyword, and states:
By declaring $a and $b global within the function, all references to either variable will refer to the global version.
Note that you can have $i generated by the foreach loop itself.
So it should be:
function unfold_sentence($str)
{
global $datawords;
$sentence_arr = preg_split('/[.?!]/',$str);
foreach ($sentence_arr as $i => $value) {
$datawords['sentence'][$i]['sentence'] = $value;
$datawords['sentence'][$i]['words'] = preg_split( '/[-?!.,;: ]+/', $value);
}
}

invoking closure within another closure?

PHP seems very inconsistent and (compiler) fails to generate the logic. Let's start our investigation, first with a simple set of JSON data.
[
{
"customer": "cust01",
"assembly": "assem01",
"date_received": "02-08-2015",
"time_received": "09:15",
"date_completed": "02-23-2015",
"time_completed": "10:27"
},
{
"customer": "lov_01",
"assembly": "lov_02",
"date_received": "lov_03",
"time_received": "lov_04",
"date_completed": "lov05",
"time_completed": "lov_06"
}
]
Then in the PHP, we retrieve an array of that data
$t_json_string = file_get_contents($t_json_file_path);
$t_json_arr = json_decode($t_json_string, true);
Assume we retrieve post values in an array like this
$t_new_entry = [
"customer" => "lov_01",
"assembly" => "lov_02",
"date_received" => "lov_03",
"time_received" => "lov_04",
"time_completed"=> "lov_05",
"time_completed"=> "lov_06"
];
and the goal is to verify as if new entry exists in the json array yet, by a condition whether both arrays have more than 2 similar values, for that I'm using $t_count to count the number of similar occurrences.
I wrote up 2 methods for checking that while passing the same data into the data pool.
// $t_boo = $db_entry_check($t_new_entry, $t_json_arr); echo $t_boo;
// true, $t_count shows 3.
$t_bool = $db_entry_exist($t_new_entry, $t_json_arr); echo $t_bool;
// False. It has to be true with the $t_count printed out at 3.
The first one employs call_user_function_array, which I tested and it works so I commented it out. Code for it here:
$db_entry_check = function($needle, $haystack){
$t_exist = 'false';
$t_count = 0;
function h_loop (&$t_count, $value, $array){
foreach ($array as $key => $val){
if (is_array($val)){
h_loop($t_count, $value, $val);
} else {
echo "<br/> --- value: ". $value. "<br/> --- val: ". $val . "<br/><br/>";
if ($val === $value){
$t_count += 1;
echo "<br/>" . $t_count . "<br/>";
continue;
}
}
}
}
function n_loop (&$t_count, $arr, $array){
foreach ($arr as $key => $value){
if (is_array($value)){
n_loop($t_count, $value, $array);
} else {
if ($t_count > 2) continue;
call_user_func_array('h_loop', [&$t_count, $value, $array]);
}
}
}
n_loop($t_count, $needle, $haystack);
echo "<br/>" . $t_count . "<br/>";
if ($t_count > 2) $t_exist = 'true';
return $t_exist;
};
The second one is my attempt to use lambdas on every component functions. I tried playing around putting $value, $array, and $t_count into use() part as those variables exist within the scope of $db_entry_exist for data binding & dependencies injection. When it comes to considering parameters (for the function) vs dependencies (for the use) Of the h_loop, I find it confusing, what an entire mess in PHP efforts to implement concepts of Javascript.
No matter what parameters I am passing onto the function part and no matter what variables got injected in the use() part. Many variations have been tested but none of them work. I usually get an error of 'Function name must be a string'. Invoking a closure within another closure in PHP seems not working as the logic in Javascript. It fails me whenever I tries to pass $h_loop($t_count, $value, $array); or echo $factorial(5); in the else part of the n_loop function. What I don't understand is that $db_entry_exist itself is a lambda (Closure as what PHP calls it) and n_loop function can be called inside without any error but calling/invoking a grandchild (h_loop) function by the same approach does not work, often resulting in the same error above.
$db_entry_exist = function($needle, $haystack){
$t_exist = 'false';
$t_count = 0;
// n_loop($t_count, $needle, $haystack);
$h_loop = function (&$t_count, $value, $array) use (&$h_loop) {
foreach ($array as $key => $val){
if (is_array($val)){
h_loop($t_count, $value, $val);
} else {
echo "<br/> --- value: ". $value. "<br/> --- val: ". $val . "<br/><br/>";
if ($val === $value){
$t_count += 1;
echo "<br/>" . $t_count . "<br/>";
continue;
}
}
}
};
$factoral = function($n) use (&$factoral) {
if ($n <= 1)
return 1;
else
return $n * $factoral($n - 1);
}; // source: https://gist.github.com/superic/8290704
$n_loop = function (&$t_count, $arr, $array) use (&$n_loop) {
foreach ($arr as $key => $value){
if (is_array($value)){
$n_loop($t_count, $value, $array);
} else {
if ($t_count > 2) continue;
$h_loop($t_count, $value, $array);
}
}
};
/*$n_loop = function ($arr, $array) use (&$n_loop, &$t_count){
// echo "<br/> --- nloop.t_count: " . $t_count . "<br/>";
foreach ($arr as $key => $value){
if (is_array($value)){
$n_loop($value);
} else {
if ($t_count > 2) continue;
// $h_loop($value, $array);
}
}
};*/
$n_loop($t_count, $needle, $haystack);
echo "<br/>" . $t_count . "<br/>";
if ($t_count > 2) $t_exist = 'true';
return $t_exist;
};
and here is the link to view my entire code:
<script src="http://ideone.com/e.js/YjLkZF" type="text/javascript" ></script>
To sum up, there are primarily 2 issues I don't understand and can't figure:
$n_loop is invoked fine within $db_entry_exist method but $h_loop isn't.
In the context of $db_entry_exist, how to pass and pass what variables to the function() and pass what as dependencies to the use() part.
$n_loop = function (&$t_count, $arr, $array) use (&$n_loop){}
// ------ OR ------- many other variations are there too.
$n_loop = function ($arr, $array) use (&$n_loop, &$t_count){}
Please investigate the code and let me know your thoughts. Thank you.
You have two misconceptions in your code that are affecting your understanding.
First: PHP does not actually have nested functions. When you say:
function outer()
{
function foo() {}
function bar() {}
}
what you are really saying is, when outer() is called, define foo() and bar() in the global scope. This means that once you call outer() once, anyone (not just outer()) can call foo() and bar(). This also means that calling outer() a second time results in a Cannot redeclare foo() error.
Second: Closures in PHP do not automatically close over any variables in their parent scope. Any variables intended to be part of the closure must be explicitly included in the use() list. This means that when you write:
$n_loop = function (&$t_count, $arr, $array) use (&$n_loop) {
//...
$h_loop($t_count, $value, $array);
//...
};
the call to $h_loop will always fail, because in the scope of that function, there is no variable named $h_loop. If you add $h_loop to your use() list, then you will be able to call it as expected.

compact() variables into array by prefix?

Is this possible?
Like make an array with all the variables that have a certain prefix?
I don't need the keys just the values, but I guess I could use array_values on the array.
If you need to do this, it's probably not written very well to begin with, but, here's how to do it :)
$foobar = 'test';
$anothervar = 'anothertest';
$foooverflow = 'fo';
$barfoo = 'foobar';
$prefix = 'foo';
$output = array();
$vars = get_defined_vars();
foreach ($vars as $key => $value) {
if (strpos($key, $prefix) === 0) $output[] = $value;
}
/*
$output = array(
'test', // from $foobar
'fo', // from $foooverflow
);
*/
http://php.net/manual/en/function.get-defined-vars.php
my eyes are bleeding a little, but I couldn't resist a one liner.
print_r(iterator_to_array(new RegexIterator(new ArrayIterator(get_defined_vars()), '/^' . preg_quote($prefix) . '/', RegexIterator::GET_MATCH, RegexIterator::USE_KEY)));
If you're talking about variables in the global scope, you could do this with $GLOBALS[]:
$newarray = array();
// Iterate over all current global vars
foreach ($GLOBALS as $key => $value) {
// And add those with keys matching prefix_ to a new array
if (strpos($key, 'prefix_' === 0) {
$newarray[$key] = $value;
}
}
If you have lots and lots of variables in global scope, this is going to be slower in execution than manually adding them all to compact(), but faster to type out.
Addendum
I would just add (though I suspect you already know) that if you have the ability to refactor this code, you are better off to group the related variables together into an array in the first place.
This, my second answer, shows how to do this without making a mess of the global scope by using a simple PHP object:
$v = new stdClass();
$v->foo = "bar";
$v->scope = "your friend";
$v->using_classes = "a good idea";
$v->foo_overflow = "a bad day";
echo "Man, this is $v->using_classes!\n";
$prefix = "foo";
$output = array();
$refl = new ReflectionObject($v);
foreach ($refl->getProperties() as $prop) {
if (strpos($prop->getName(), $prefix) === 0) $output[] = $prop->getValue($v);
}
var_export($output);
Here's the output:
Man, this is a good idea!
array (
0 => 'bar',
1 => 'a bad day',
)

PHP dynamically accessing variable value

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;
}

php function with arrays

I want to pass one argument to a function, rather than multiple arguments, that tend to grow unexpectedly. So I figure an array will get the job done. Here's what I've drafted so far...
<?php
function fun_stuff($var){
// I want to parse the array in the function, and use
}
$my = array();
$my['recordID'] = 5;
$my['name'] = 'John Smith';
$my['email'] = 'john#someemail.com';
echo fun_stuff($my);
?>
I haven't quite grasped the concept of parsing an array. And this is a good way for me to learn. I generally pass the same variables, but on occasion a record does not have an email address, so I do need to make a condition for missing keys.
Am I doing this right so far? Can I pass an array as an argument to a function?
And if so, how do I parse and search for existing keys?
Hopefully this isn't too far off topic...but you sounded like you were just trying to avoid multiple parameters when some can be NULL. So, I would recommend that you use an object instead of an array for clarity...that way, there is no confusion as to what properties should exist. If you're using PHP 5, you can also strongly type the parameter so nothing else can get in. So:
class Record {
public $Id;
public $Name;
public $Email
}
function fun_stuff( Record $record ) {
// you will now have better intellisense if you use an IDE
// and other develoers will be able to see intended parameters
// clearly, while an array would require them to know what's
// intended to be there.
if( !empty($record->Email) ) {
// do whatever.
}
}
Yes you are on the right track. The approach I take is put required paramters as the first parameters and all optional parameters in the last argument which is an array.
For example:
function fun_stuff($required1, $required2, $var = array()) {
// parse optional arguments
$recordId = (key_exists('recordID', $var) ? $var['recordId'] : 'default value');
$name = (key_exists('name', $var) ? $var['name'] : 'default value');
$email = (key_exists('email', $var) ? $var['email'] : 'default value');
}
Then you can call your function like so:
fun_stuff('val 1', 'val 2', array(
'recordId' => 1,
'name' => 'John',
'email' => 'john#stackoverflow.com'
));
This is a bad design practice, but that's not the question here. You can "parse" array's like so...
if( array_key_exists( 'email', $var ))
{
// use email field
}
If you need to, you can loop through all elements like so...
foreach( $var as $key => $value )
{
echo '$var[\''.$key.'\'] = '.$value;
}
I'm not recommend you to use array for this.
You can define optional arguments with default values:
//$name and $email are optional here
function fun($record_id, $name='', $email='')
{
if (empty($name)) print '$name is empty';
}
//Usage:
fun(5, 'Robert');
fun(5);
fun(5, 'Robert', 'robert#gmail');
fun(3,'','robert#gmail');
If you will use array, IDE will not be able to show autocomplete suggestions, it means more typos, and you have to remember all keys of this array forever or look at code of the function each time.
I'm not really sure what you want to achieve, but I suspect something like this:
$aPersons = array();
$aPersons[] = array('name' => 'name1', 'age' => 1);
$aPersons[] = array('name' => 'name2', 'age' => 2);
array_map('parsePerson', $aPersons);
function parsePerson($aPerson) {
echo $aPerson['name'];
echo $aPerson['age'];
}
The problem with your current array is that it only has one dimension.
You can simple do echo $my['name'];. There are easier ways to parse arrays though.
foreach($aPersons as $aPerson) {
echo $aPerson['name'];
echo $aPerson['age'];
}
$iLength = sizeof($aPersons);
for($i = 0; $i <= $iLength; $i++) {
echo $aPersons[$i]['name'];
echo $aPersons[$i]['age'];
}
To parse and view, there is the signficant print_r function which gives out the array details.
When calling a function you need the return syntax at the end that will parse out anything you call in the return.
You obviously can pass array to the function. Inside it read the variable as you were assigning values before it. If you assign:
$my['key'] = 'value';
In you function use:
echo $var['key'];
Why you don't use a foreach to walk in array?
function fun_stuff($var){
foreach($var as $key => $item){
echo '[', $key, "] => ", $item, "\n";
}
}
$my = array();
$my['recordID'] = 5;
$my['name'] = 'John Smith';
$my['email'] = 'john#someemail.com';
fun_stuff($my);
Yes, this is correct (though your question is a bit broad). You're already referencing the array values via indexes when you set up the $my variable. You can do the same thing within your function (with the $var variable).
I recommend taking a look at all of PHP's built-in array functions: http://php.net/manual/en/ref.array.php
Try this:
function fun_stuff($var){
// I want to parse the array in the function, and use
$fun_string = "";
if( is_array( $var ) {
if( array_key_exists( "name", $var ) )
$fun_string .= "For " . $var["name"];
else $fun_string .= "A nameless one ";
if( array_key_exists( "email", $var ) )
$fun_string .= " (email: " . $var["email"] . ")";
else $fun_string .= " without a known e-mail address";
if( array_key_exists( "recordID", $var ) )
$fun_string .= " has record ID of " . $var["recordID"];
else $fun_string .= " has no record ID set";
$fun_string .= "\n";
}
return $fun_string;
}
Yes You can! Just pass the array and inside the function just use a foreach loop to parse it!
function myFunction($array)
{
foreach($array as $value)
{
echo $value;
}
}
or If you want to have full control over the pair key/value:
function myFunction($array)
{
foreach($array as $key=>$value)
{
echo "key:".$array[$key]."value:".$values;
}
}

Categories