example.php:
$args = __FILE__.' -vvv';
$argv = explode(' ', $args);
$argc = count($argv);
$GLOBALS['argv'] = $_SERVER['argv'] = $argv;
$GLOBALS['argc'] = $_SERVER['argc'] = $argc;
var_export(getopt('v'));
$ example.php -v
> array('v' => false);
Eventually getopt does not look up to $GLOBALS to get argv. So it there any way I can override argv array?
TL;DR
No, there is no native way to do this. Depending of your goals there may be other ways to resolve the issue, but overriding is not one of them.
Operating on super-globals
Structure
To realize why it is so, you should know, that super-globals are not just "variables" to which you are referring. That means, if you are using $argv to de-reference arguments list, it does not mean that you'll access some data which is stored in "variable" $argv. Instead, you'll access to data container by "link", called $argv. However, there are different ways to access this data - $_SERVER['argv'] or $GLOBALS array at least. To illustrate, I'll go with the code:
var_dump($argv, $_SERVER['argv']);
unset($argv);
var_dump($argv, $_SERVER['argv']);
This will result in something like:
array(2) {
[0]=>
string(11) "example.php"
[1]=>
string(4) "-vvv"
}//<--------------------- derived from $argv
array(2) {
[0]=>
string(11) "example.php"
[1]=>
string(4) "-vvv"
}//<--------------------- derived from $_SERVER['argv']
NULL//<------------------ we've unset $argv, so unset a reference
array(2) {
[0]=>
string(11) "example.php"
[1]=>
string(4) "-vvv"
}//<--------------------- but data is still there and available via another reference
Internally
As you can see, the reference can be destroyed, but actual data will remain untouched. That will be stored in symbols table. Many PHP functions are accessing this structure to retrieve data, and getopt() is not an exception. So the conclusion is - yes, you can modify the reference (or even destroy it), but actual data will be still in super-globals.
getopt()
Now, about this function. Just take a look at its implementation:
/* Get argv from the global symbol table. We calculate argc ourselves
* in order to be on the safe side, even though it is also available
* from the symbol table. */
if (PG(http_globals)[TRACK_VARS_SERVER] &&
(zend_hash_find(HASH_OF(PG(http_globals)[TRACK_VARS_SERVER]), "argv", sizeof("argv"), (void **) &args) != FAILURE ||
zend_hash_find(&EG(symbol_table), "argv", sizeof("argv"), (void **) &args) != FAILURE) && Z_TYPE_PP(args) == IS_ARRAY
)
{
//...omitted
}
It's clearly stated, that it will try to search "argv" in symbol_table (if you want - here's a link to - how it will be done exactly). And that means - it will access actual data, thus, overriding it externally will have no effect.
As a result - getopt() will work with data which were gathered at script startup, and that data will contain actual parameters, no matter if you'll override them inside your script.
Related
I have my local test server and my running server. On the test server, I use this url:
http://localhost/?&start[]=0&duration_[]=2&name[]=David&setting[]=26&start[]=6&duration[]=18&name[]=john&setting[]=26
From this URL, I use a for loop to run through the array:
$num = count($_GET['start']);
if (isset($_GET['start']) && count($_GET['start']))
{
for($i=0;$i<$num;$i++)
{
echo "Time: " . intval(trim($_GET['start'][$i]));
}
}
This works like a charm. However, when I run it on my actual server, I get all the variables returning as empty strings... Here is the var_dump:
["start"]=>
string(0) ""
["duration"]=>
string(0) ""
["name"]=>
string(0) ""
["setting"]=>
string(0) ""
Anyone have any idea what's happening here? I've put the same for-loop test code from above and I'm obviously not getting anything besides: "Time: "
The problem wasn't with the code or the $_GET method or the difference of servers (well a slight difference). I didn't realize that when I was using filter_var() on my main server, my arrays were being turned into strings. This bit of the sanitization code that caused the problem was not on my test server. As per the php documentation:
Value to filter. Note that scalar values are converted to string internally before they are filtered.
This is the first time I've made a function using the array capabilties of the $_GET variable so I didn't realize that when sanitizing, I was sanitizing each GET variable where in this new instance, I was sanitizing each GET array, rather than GET value.
The code I was using was:
$get_arr = $var_arr['_GET'];
$get_key = array_keys($get_arr);
$_GET[$get_key[$g_a]] = trim(filter_var($_GET[$get_key[$g_a]], FILTER_SANITIZE_SPECIAL_CHARS));
The code that I changed it to
$get_arr = $var_arr['_GET'];
$get_key = array_keys($get_arr);
$_GET[$get_key[$k]][$i] = trim(filter_var($_GET[$get_key[$k]][$i], FILTER_SANITIZE_SPECIAL_CHARS));
It's only a slight distinction, but enough of one to have me debugging this for a couple of days. Hopefully this helps someone else along the way.
Remove the [] after your vars.
http://localhost/?start=0
instead of
http://localhost/?start[]=0
your passing [] as your string so it spitting that out, remove the [].
This isn't really a surprise that function implementations change sometimes from version to version, but not like this... Look:
$array = ["abc","def"];
$object = new stdclass();
foreach($array as $index => $value) {
$object->$index = $value;
}
var_dump(get_object_vars($object));
For 5.6.x and then e.g. 7.0.17 and 7.1.3 we get:
array(2) {
[0]=> string(3) "abc"
[1]=> string(3) "def"
}
But for 7.0.0 and 7.0.16 and 7.1.0 we get:
array(2) {
["0"]=> string(3) "abc"
["1"]=> string(3) "def"
}
demo: https://3v4l.org/jog4A
See? The keys are integers OR strings, depending on version.
Why? What is the reasoning behind those changes? And why isn't this documented anywhere? Or... is it?
If you look more closely at the versions on that 3v4.org output, it was broken in 7.0.0 up to and including 7.0.16, and 7.1.0 up to and including 7.1.2. So this was a bug introduced in 7.0, and fixed on both active releases in time for 7.0.17 and 7.1.3 (both release March 16 2017).
Looking at the PHP changelog we can see a relevant looking entry:
Fixed bug #73998 (array_key_exists fails on arrays created by get_object_vars).
This leads us to the bug tracker, and from there to commit dd9cf23457e21d2bda29dc92d437b9dbd14027b2 in th git repo:
BUG #73998: Numeric properties are not accessible from get_object_vars
The fix involves adding a check for if there are numeric keys present, and skipping a block labelled "fast_copy" if there are.
So this was an undesired side-effect of a performance optimisation made during the development of PHP 7, and has now been fixed in all supported releases.
Interestingly, Andrea commented on the bug report that it is closely related to an RFC to change the behaviour of object-to-array casts which describes the general problem:
Because arrays and objects have different restrictions versus the underlying HashTable type on what kinds of keys they can have, the Zend Engine must enforce their restrictions at a layer above HashTables, in the code implementing arrays and objects themselves. This means that if that code is bypassed and the underlying HashTables are modified directly, arrays and objects can exist with an invalid internal state.
And the specific case addressed by the RFC:
For example, $arr = [0 => 1, 1 => 2, 2 => 3]; $obj = (object)$arr; produces an object with inaccessible properties named , 1 and 2, while $obj = new stdClass; $obj->{'0'} = 1; $obj->{'1'} = 2; $obj->{'2'} = 3; produces an array with the inaccessible keys "0", "1" and "2". The same issue also occurs when using get_object_vars().
The RFC is implemented in 7.2.0, as it changes documented behaviour, but the behaviour of get_object_vars() was actually an inadvertent change in 7.0, so was implemented as a bug fix.
Actually it is a bug, because variables must start with letter.
Variable names follow the same rules as other labels in PHP. A valid
variable name starts with a letter or underscore, followed by any
number of letters, numbers, or underscores. As a regular expression,
it would be expressed thus: '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'
http://php.net/manual/en/language.variables.basics.php
So when you are trying to set variables $object->$index = $value; it must throw an error (you cant set the variable like this $1 = 'foo'; or $obj->1 = 'foo';).
array(2) {
["0"]=> string(3) "abc"
["1"]=> string(3) "def"
}
Is a right result, because get_object_vars returns associative array.
Return Values ¶
Returns an associative array of defined object accessible non-static
properties for the specified object in scope. If a property has not
been assigned a value, it will be returned with a NULL value.
http://php.net/manual/en/function.get-object-vars.php
I want to pass parameters from PHP Command Line Interface, and then read in the values using PHP script, something like this:
<?php
$name1 = $argv[1];
echo $name1;
?>
I pass the variable from CLI like this:
C:\xampp\php\php.exe name.php Robby
The above works, I get Robby as the output.
But I want to do something like this:
C:\xampp\php\php.exe name.php -inputFirstName="Robby"
So that the user is well informed to enter the correct parameters in the correct places. What is the appropriate way to parse these parameters?
When calling a PHP script from the command line you can use $argc to find out how many parameters are passed and $argv to access them. For example running the following script:
<?php
var_dump($argc); //number of arguments passed
var_dump($argv); //the arguments passed
?>
Like this:-
php script.php arg1 arg2 arg3
Will give the following output
int(4)
array(4) {
[0]=>
string(21) "d:\Scripts\script.php"
[1]=>
string(4) "arg1"
[2]=>
string(4) "arg2"
[3]=>
string(4) "arg3"
}
See $argv and $argc for further details.
To do what you want, lets say
php script.php arg1=4
You would need to explode the argument on the equals sign:-
list($key, $val) = explode('=', $argv[1]);
var_dump(array($key=>$val));
That way you can have whatever you want in front of the equals sign without having to parse it, just check the key=>value pairs are correct. However, that is all a bit of a waste, just instruct the user on the correct order to pass the arguments.
I use this fairly concise method:
if($argc>1)
parse_str(implode('&',array_slice($argv, 1)), $_GET);
Which would handle a call such as:
php script.php item1=4 item2=300
By sending it into $_GET you automatically handle web or CLI access.
For commentary, this is doing the following:
If the count of arguments is greater than one (as first item is the name of the script) the proceed
Grab the arguments array excluding first item
Turn it into a standard query string format with ampersands
use parse_str to extract to the $_GET array
While the answer is correct and you could do the parsing by hand, PHP also offers the getopt() function that might actually provide useful here.
There's also object-oriented alternatives (written in PHP, available in a number of libraries) that might turn out to be what you need. Googling for "php getopt" will yield helpful results.
you can send parameters as one argument then parse that argument like a $_GET array
C:\xampp\php\php.exe name.php "inputFirstName=Robby&LastName=John"
and in your PHP file
if (!empty($argv[1])) {
parse_str($argv[1], $_GET);
}
you'll get arguments in $_GET array like usual
The getopt() function is probably the most correct answer in the case of the question. Especially since it was made platform independent with PHP 5.3. In the particular case of this question and parsing multiple parameters, one way to leverage this function would be as follows:
$defaultValues = array("inputFirstName" => "");
$givenArguments = getopt("", array("inputFirstName:"));
$options = array_merge($defaultValues, $givenArguments);
$inputFirstName = $options['inputFirstName'];
The call to set $inputFirstName with the value "Robby" would be:
> php script.php --inputFirstName="Robby"
Explanation
Default values for all expected parameters are set in the $defaultValues array. Input sent through via command line arguments are collected by PHP's getopt function and stored by the $givenArguments. Note that the colon (:) at the end of the "inputFirstName:" string indicates that this is a required argument. Without a colon here, only the presence of the argument would be detected, not the actual value (more information in the PHP Manual). Merging these two arrays together on the third line results in array with all expected parameters being set with either default values or arguments provided from the command line/terminal if they are available.
I don't know if at the time this question has being asked what i going to answer to it was available but if you call php-cgi -f myfile.php var=something you can retrieved whit $var=$_GET['var']; from the command line then you don't have to change your code to call it from the web browser or the command line
If you don't mind using a library, I suggest you take a look at The Console Component by Symfony.
It can be used to create command line applications and supports the use of Arguments & Options.
The documentation page contains a couple of excellent examples to get you started.
Of course under the hood it uses the same techniques as explained by vascowhite.
your best hope is to use
exec("php -f php.file.php example=js.json > ech0");
echo file_get_contents("ech0");
unlink("ech0");
That's what I use in PipesJS
You can parse the user input on your program looking for specific strings such as -inputFirstName="x" (using regular expressions, for example) and then set the correct variable to x.
I'm creating a wrapper function around mysqli so that my application needn't be overly complicated with database-handling code. Part of that is a bit of code to parameterize the SQL calls using mysqli::bind_param(). bind_param(), as you may know, requires references. Since it's a semi-general wrapper, I end up making this call:
call_user_func_array(array($stmt, 'bind_param'), $this->bindArgs);
and I get an error message:
Parameter 2 to mysqli_stmt::bind_param() expected to be a reference, value given
The above discussion is to forstall those who would say "You don't need references at all in your example".
My "real" code is a bit more complicated than anyone wants to read, so I've boiled the code leading up to this error into the following (hopefully) illustrative example:
class myclass {
private $myarray = array();
function setArray($vals) {
foreach ($vals as $key => &$value) {
$this->myarray[] =& $value;
}
$this->dumpArray();
}
function dumpArray() {
var_dump($this->myarray);
}
}
function myfunc($vals) {
$obj = new myclass;
$obj->setArray($vals);
$obj->dumpArray();
}
myfunc(array('key1' => 'val1',
'key2' => 'val2'));
The problem appears to be that, in myfunc(), in between the call to setArray() and the call to dumpArray(), all the elements in $obj->myarray stop being references and become just values instead. This can be easily seen by looking at the output:
array(2) {
[0]=>
&string(4) "val1"
[1]=>
&string(4) "val2"
}
array(2) {
[0]=>
string(4) "val1"
[1]=>
string(4) "val2"
}
Note that the array is in the "correct" state in the first half of the output here. If it made sense to do so, I could make my bind_param() call at that point, and it would work. Unfortunately, something breaks in the latter half of the output. Note the lack of the "&" on the array value types.
What happened to my references? How can I prevent this from happening? I hate to call "PHP bug" when I'm really not a language expert, but could this be one? It does seem very odd to me. I'm using PHP 5.3.8 for my testing at the moment.
Edit:
As more than one person pointed out, the fix is to change setArray() to accept its argument by reference:
function setArray(&$vals) {
I'm adding this note to document WHY this seems to work.
PHP generally, and mysqli in particular, appear to have a slightly odd concept of what a "reference" is. Observe this example:
$a = "foo";
$b = array(&$a);
$c = array(&$a);
var_dump($b);
var_dump($c);
First of all, I'm sure you're wondering why I'm using arrays instead of scalar variables -- it's because var_dump() doesn't show any indication of whether a scalar is a reference, but it does for array members.
Anyway, at this point, $b[0] and $c[0] are both references to $a. So far, so good. Now we throw our first wrench into the works:
unset($a);
var_dump($b);
var_dump($c);
$b[0] and $c[0] are both still references to the same thing. If we change one, both will still change. But what are they references to? Some unnamed location in memory. Of course, garbage collection insures that our data is safe, and will remain so, until we stop refering to it.
For our next trick, we do this:
unset($b);
var_dump($c);
Now $c[0] is the only remaining reference to our data. And, whoa! Magically, it's no longer a "reference". Not by var_dump()'s measure, and not by mysqli::bind_param()'s measure either.
The PHP manual says that there's a separate flag, 'is_ref' on every piece of data. However, this test appears to suggest that 'is_ref' is actually equivalent to '(refcount > 1)'
For fun, you can modify this toy example as follows:
$a = array("foo");
$b = array(&$a[0]);
$c = array(&$a[0]);
var_dump($a);
var_dump($b);
var_dump($c);
Note that all three arrays have the reference mark on their members, which backs up the idea that 'is_ref' is functionally equivalent to '(refcount > 1)'.
It's beyond me why mysqli::bind_param() would care about this distinction in the first place (or perhaps it's call_user_func_array()... either way), but it would appear that what we "really" need to ensure is that the reference count is at least 2 for each member of $this->bindArgs in our call_user_func_array() call (see the very beginning of the post/question). And the easiest way to do that (in this case) is to make setArray() pass-by-reference.
Edit:
For extra fun and games, I modified my original program (not shown here) to leave its equivalent to setArray() pass-by-value, and to create a gratuitous extra array, bindArgsCopy, containing exactly the same thing as bindArgs. Which means that, yes, both arrays contained references to "temporary" data which was deallocated by the time of the second call. As predicted by the analysis above, this worked. This demonstrates that the above analysis is not an artifact of var_dump()'s inner workings (a relief to me, at least), and it also demonstrates that it's the reference count that matters, not the "temporary-ness" of the original data storage.
So. I make the following assertion: In PHP, for the purpose of call_user_func_array() (and probably more), saying that a data item is a "reference" is the same thing as saying that the item's reference count is greater than or equal to 2 (ignoring PHP's internal memory optimizations for equal-valued scalars)
Administrivia note: I'd love to give mario the site credit for the answer, as he was the first to suggest the correct answer, but since he wrote it in a comment, not an actual answer, I couldn't do it :-(
Pass the array as a reference:
function setArray(&$vals) {
foreach ($vals as $key => &$value) {
$this->myarray[] =& $value;
}
$this->dumpArray();
}
My guess (which could be wrong in some details, but is hopefully correct for the most part) as to why this makes your code work as expected is that when you pass as a value, everything's cool for the call to dumpArray() inside of setArray() because the reference to the $vals array inside setArray() still exist. But when control returns to myfunc() then that temporary variable is gone as are all references to it. So PHP dutifully changes the array to string values instead of references before deallocating the memory for it. But if you pass it as a reference from myfunc() then setArray() is using references to an array that lives on when control returns to myfunc().
Adding the & in the argument signature fixed it for me. This means the function will receive the memory address of the original array.
function setArray(&$vals) {
// ...
}
CodePad.
I just encountered this same problem in almost exactly the same situation (I'm writing a PDO wrapper for my own purposes). I suspected that PHP was changing the reference to a value once no other variables were referencing the same data, and Rick, your comments in the edit to your question confirmed that suspicion, so thank you for that.
Now, for anybody else who comes across something like this, I believe I have the simplest solution: simply set each relevant array element to a reference to itself before passing the array to call_user_func_array().
I'm not sure exactly what happens internally because it seems like that shouldn't work, but the element becomes a reference again (which you can see with var_dump()), and call_user_func_array() then passes the argument by reference as expected. This fix seems to work even if the array element is still a reference already, so you don't have to check first.
In Rick's code, it would be something like this (everything after the first argument for bind_param is by reference, so I skip the first one and fix all of the ones after that):
for ($i = 1, $count = count($this->bindArgs); $i < $count; $i++) {
$this->bindArgs[$i] = &$this->bindArgs[$i];
}
call_user_func_array(array($stmt, 'bind_param'), $this->bindArgs);
I want to run a PHP script from the command line, but I also want to set a variable for that script.
Browser version: script.php?var=3
Command line: php -f script.php (but how do I give it the variable containing 3?)
Script:
<?php
// number of arguments passed to the script
var_dump($argc);
// the arguments as an array. first argument is always the script name
var_dump($argv);
Command:
$ php -f test.php foo bar baz
int(4)
array(4) {
[0]=>
string(8) "test.php"
[1]=>
string(3) "foo"
[2]=>
string(3) "bar"
[3]=>
string(3) "baz"
}
Also, take a look at using PHP from the command line.
If you want to keep named parameters almost like var=3&foo=bar (instead of the positional parameters offered by $argv) getopt() can assist you.
Besides argv (as Ionut mentioned), you can use environment variables:
E.g.:
var = 3 php -f test.php
In test.php:
$var = getenv("var");
As well as using argc and argv as indicated by Ionut G. Stan, you could also use the PEAR module Console_Getopt which can parse out unix-style command line options. See this article for more information.
Alternatively, there's similar functionality in the Zend Framework in the Zend_Console_Getopt class.
A lot of solutions put the arguments into variables according to their order. For example,
myfile.php 5 7
will put the 5 into the first variable and 7 into the next variable.
I wanted named arguments:
myfile.php a=1 x=8
so that I can use them as variable names in the PHP code.
The link that Ionuț G. Stan gave at
http://www.php.net/manual/en/features.commandline.php
gave me the answer.
sep16 at psu dot edu:
You can easily parse command line arguments into the $_GET variable by using the parse_str() function.
<?php
parse_str(implode('&', array_slice($argv, 1)), $_GET);
?>
It behaves exactly like you'd expect with cgi-php.
$ php -f somefile.php a=1 b[]=2 b[]=3
This will set $_GET['a'] to '1' and $_GET['b'] to array('2', '3').