I have a variable $v that can be either single string or array of stringsand I have a code:
$a = array();
if (is_array($v)) {
$a = $v;
} else {
$a[] = $v;
}
How it can be done in more elegant way? (in other words, how to cast a variable to array)
You can cast a variable to an array by using:
$var = (array)$arr;
$a = (array) $v;
is the answer.
I would write your could snippet like this (short and you read it and know exactly what is happening):
$a = is_array($v) ? $v : array($v);
Alternatively you could use settype:
settype($a, "array");
For expliciting the variable type. It's exactly the same as what happens with a typecast behind the scenes. (More useful for group-wise typecasting e.g. in loops.)
As others have said, casting a scalar value to an array will produce a singleton array (i.e. an array with the scalar as its only element). However, as still others have pointed out, take care to only do this if you know the value is going to be a scalar and not a class instance.
From the PHP docs:
For any of the types integer, float, string, boolean and resource,
converting a value to an array results in an array with a single
element with index zero and the value of the scalar which was
converted. In other words, (array)$scalarValue is exactly the same as
array($scalarValue).
If an object is converted to an array, the result is an array whose
elements are the object's properties. The keys are the member variable
names, with a few notable exceptions: integer properties are
unaccessible; private variables have the class name prepended to the
variable name; protected variables have a '*' prepended to the
variable name. These prepended values have null bytes on either side.
If $v is a scalar (Boolean, String, Number) you can use:
a) $v = (array)$v;
If $v is an object, you have to use:
b) $v = is_array($v) ? $v : array($v);
Method (b) works in every case (with scalars too).
If you are looking to convert an object to a single count array you can use the follow code:
$list = array([0] => $obj);
The other provided answers won't work when trying to convert an object, it will simply convert the fields of that object into an associative array (unless that is what you are trying to do).
$var = (array)$arr;
Actually if you want to cast to an array and not have to worry about what you put into it, the answer is
$var = (is_object($var)) ? array($var) : (array) $var;
You can test this with the following code
function toArray($var) {
return (is_object($var)) ? array($var) : (array) $var;
}
$object = new stdClass;
$resource = fopen('php://stdout', 'w');
$closure = function () {};
$tests = array(
array(toArray(true), array(true), 'boolean true'),
array(toArray(false), array(false), 'boolean false'),
array(toArray(null), array(), 'null'),
array(toArray(1), array(1), 'positive integer'),
array(toArray(0), array(0), 'zero integer'),
array(toArray(-1), array(-1), 'negative integer'),
array(toArray(1.5), array(1.5), 'positive float'),
array(toArray(0.0), array(0.0), 'zero float'),
array(toArray(-1.5), array(-1.5), 'negative float'),
array(toArray(''), array(''), 'empty string'),
array(toArray('foo'), array('foo'), 'string'),
array(toArray(array()), array(), 'array'),
array(toArray($object), array($object), 'object'),
array(toArray($resource), array($resource), 'resource'),
array(toArray($closure), array($closure), 'closure'),
);
foreach ($tests as $test) {
ob_start();
var_dump($test[0]);
$a = ob_get_clean();
ob_start();
var_dump($test[1]);
$b = ob_get_clean();
assert($a === $b, "{$test[2]} is not the same");
}
Related
Whats the meaning of $arr[0] result "2" in the code given below, $arr2 is copying $arr and increasing its first value by one, so the result of $arr2[0] "2" is understaning,but whats happening with $arr, when i pass by reference $arr[0] to $a like so $a=&$arr[0] the result of $arr[0] is 2, when i pass it by value $a=$arr[0[ the result of $arr[0] would be set to 1 as it should, can anyone enlighten me on this?
<?php
$a = 1;
$arr = array(1);
$a = &$arr[0];
$arr2 = $arr;
$arr2[0]++;
echo $arr[0]. "<br>";//2
echo $arr2[0]. "<br>";//2
?>
References in PHP are not like pointers; when you assign or pass something by reference, you create what we might call a "reference set" where both variables are references to the same "zval" (the structure in memory which holds the type and value of a variable).
An important consequence of this is that references are symmetrical. I tend to write assign by reference as $foo =& $bar rather than $foo = &$bar to emphasise this: the operator doesn't just take a reference to $bar and put it into $foo, it affects both operands.
With that in mind, let's go through your example:
$arr = array(1);
This will create two zvals: one for the array $arr itself, and one for the value in position 0 of that array ($arr[0]).
$a =& $arr[0];
This binds $a and $arr[0] into a reference set. Whichever of those names we use, we will be using the same zval that was previously created, and currently holds 1.
$arr2 = $arr;
This copies the contents of $arr into a new array zval, $arr2. But it doesn't resolve the references inside that array into values, it just copies the fact that they are a reference.
So $arr2[0] is now also part of the reference set containing $arr[0] and $a.
$arr2[0]++;
This increments the zval pointed to by our reference set, from 1 to 2.
echo $arr[0]. "<br>";//2
echo $arr2[0]. "<br>";//2
Since these are both part of the same reference set, they both point to the same zval, which is integer 2.
The inevitable question is, "bug or feature?" A more useful example would be passing an array containing references into a function:
function foo($array) {
$array[0] = 42;
$array[1] = 69;
}
$a = 1;
$b = 1;
$foo = [ &$a, &$b ];
foo($foo);
echo $a; // 42
echo $b; // 69
Like assignment, passing into the function didn't break the references, so code manipulating them can safely be re-factored into multiple functions.
Not sure if this is a bug but var_dump() can help to explain.
<?php
$a = 1;
$arr = array(1);
var_dump( $arr );
$a = &$arr[0];
var_dump( $arr );
$arr2 = $arr;
$arr2[0]++;
Output:
array(1) {
[0]=>
int(1)
}
array(1) {
[0]=>
&int(1)
}
Take note of &int(1) in the second var_dump(). This tells us that position #0 of $arr has been turned into a reference pointer to a position in PHP's memory instead of remaining a dedicated value.
So when you perform $arr2 = $arr;, $arr2 receives that reference pointer as well.
I'm a bit curious about why does this work:
$arr1[] = $arr2[] = $value;
If I'm correct, first the value is pushed to the end of arr2. Then $arr2[] pushed to the end of $arr1. This is where I'm confused. If I do it separately like:
$var = $arr2[];
I get "Fatal error: Cannot use [] for reading..."
But how come [] works for reading when I'm doing multiple assignments in the same statement?
Because $var is not an array. To make $var an array:
$var = array();
or,
$var = $arr2;
If I'm correct, first the value is pushed to the end of $arr2. Then
$arr2[] pushed to the end of $arr1.
That's not correct. What happens is that $value is pushed to the end of both arrays, one at a time. The phrase "$arr2[] is pushed" is meaningless because the expression $arr2[] is not valid in a read context (i.e. it does not "have a value"), which is exactly what the error message you got in your second attempt says.
But how come [] works for reading when I'm doing multiple assignments
in the same statement?
Chaining the assignment operator in PHP does not work quite as it does in certain other languages. In particular,
$x = $y = (expr);
is treated in the same way as if you had written
// the purpose of $temp is to prevent (expr) from being evaluated twice,
// because evaluating it might have side effects (e.g. think $i++)
$temp = (expr);
$x = $temp;
$y = $temp;
That is, $arr1[] is not being assigned the value $arr2[] but rather $value directly because the assignment operator evaluates to its right-hand-side operand.
This suggestion is wrong:
Then $arr2[] pushed to the end of $arr1.
From the manual:
The value of an assignment expression is the value assigned. That is,
the value of "$a = 3" is 3. This allows you to do some tricky things...
So, first assignment operator $arr2[] = $value returns value of $value, which can be assigned further.
But how come [] works for reading when I'm doing multiple assignments in the same statement?
it doesnt.
try var dumping it
$a = array();
var_dump ($a[] = 2);
which returns
int(2)
That value is then passed to the second so $b = $a = 2
does $a=2, which returns 2, then assigns that value to $b[]
var_dump($a[]);
on the other hand doesnt make sense as var_dump($a[]) doesnt exist, it can only be used for writing to an array
$arr1[] = $arr2[] = $value; this works because you are first assigning a variable or string to an array $arr2[] and then again assiging that array to another array $arr1[].
But in case $var = $arr2[]; you are assing array to a normal variable which is not permitted.
For doing this you need to make $var as an array as $var = array();
I read this from the PHP manual:
If an object is converted to an array, the result is an array whose
elements are the object's properties. The keys are the member variable
names, with a few notable exceptions: integer properties are
unaccessible; private variables have the class name prepended to the
variable name; protected variables have a '*' prepended to the
variable name. These prepended values have null bytes on either side.
This can result in some unexpected behaviour:
class A {
private $A; // This will become '\0A\0A' }
class B extends A {
private $A; // This will become '\0B\0A'
public $AA; // This will become 'AA' }
var_dump((array) new B());
The above will appear to have two keys named 'AA', although one of
them is actually named '\0A\0A'.
I don't quite understand the meaning of those parts with typeface like this.
What's a integer property?
What's the meaning of "These prepended values have null bytes on either side. This can result in some unexpected behaviour"?
and What's the meaning of "?The above will appear to have two keys named 'AA', although one of them is actually named '\0A\0A'"
Integer properties
This refers to properties whose names are string representations of decimal integers. For example:
$o = new stdClass;
$o->{"123"} = 'foo'; // create a new integer property
echo $o->{"123"}, PHP_EOL; // verify it's there
$a = (array)$o; // convert to array
echo $a['123']; // OOPS! E_NOTICE: Undefined offset!
var_dump(array_keys($a)); // even though the key appears to be there!
print_r($a); // the value appears to be there too!
In general, integer properties in PHP are not something you should come anywhere near if you value your sanity.
Prepended values surrounded by null
For private and protected properties, the generated array keys will contain the non-printable character "\0". This might be useful (since that character is not legal for a property name you can use this information to determine the visibility of the properties), but it might also be a nuisance if you don't expect it to be there. Example:
class A {
private $A; // This will become '\0A\0A'
}
class B extends A {
private $A; // This will become '\0B\0A'
public $AA; // This will become 'AA'
}
$a = (array) new B();
// The array appears to have the keys "BA", "AA" and "AA" (twice!)
print_r(array_keys($a));
// But in reality, the 1st and 3rd keys contain NULL bytes:
print_r(array_map('bin2hex', array_keys($a)));
You can extract the visibility information from the array keys like this:
$a = (array) new B();
foreach ($a as $k => $v) {
$parts = explode(chr(0), $k);
if (count($parts) == 1) {
echo 'public $'.$parts[0].PHP_EOL;
}
else if ($parts[1] == "*") {
echo 'protected $'.$parts[2].PHP_EOL;
}
else {
echo 'private '.$parts[1].'::$'.$parts[2].PHP_EOL;
}
}
You may get some insight into the new array keys by using this code.
$x = (array)new B();
foreach ($x as $key => $value) {
echo bin2hex($key), ' = ', $value, PHP_EOL;
}
It shows a hex representation of the key values, from which you can more clearly see what's going on:
00420041 =
4141 =
00410041 =
The array key for the first property (B::A) is encoded as "\x00B\x00A", i.e. chr(0) . 'B' . chr(0) . 'A', because its visibility is private to B.
The second property (B::AA) is encoded as simply 'AA', because its visibility is public.
The third property (A::A), much like the first, is encoded as "\x00A\x00A", because its visibility is private to A.
I'm not entirely sure what integer properties are though. I wouldn't worry about numerical properties since their usage is negligible and discouraged (which is probably why there's not much mention of this "feature".
function foo($a)
{
$b = ...;
$c = ...;
return (both b and c);
}
and so I could get $b value to $first and $c value to $second
I know you can return more than 1 variable by return array($b,$c) but then it should be $var[0] and $var[1] and then I need to type $first = $var[0] and $second = $var[1] and so I'm creating more useless variables
So is it possible to do so without array?
Fundamentally, functions only have one return value. You could return a class with member variables first and second, or an associative array with keys "first" and "second", but you'll still only be returning a single object.*
Alternatively, you could references to $first and $second into your function:
function foo($a, &$b, &$c)
{
$b = ...;
$c = ...;
}
foo(42, $first, $second);
I'm not a big fan of this approach, though, because it's not immediately clear from the call-site that $first and $second are going to be modified.
* Note that if you return an array, you can always use the short-hand list($first,$second) = foo(42);.
No, it can't.
But you can still return an array from function, but use "list" to accept the result for convenient:
list ($first, $second) = foo ($a);
No, you cannot to that. Function returns only one result.
What you can do, if possible in you case, is pass a variable by reference.
function foo($a, &$b, &$c){
$b = ...;
$c = ...;
}
The following will make changes to $b and $c visible outside of the function scope.
The only alternative to avoid returning an array is to return an object, or serialized data, but you don't "win" something from that.
I want to know how to array_intersect for object array.
You can use array_uintersect in conjunction with spl_object_hash, see an example:
array_uintersect($a, $b, function($a, $b) {
return strcmp(spl_object_hash($a), spl_object_hash($b));
});
where '$a' and '$b' are arrays of some objects that you want to intersect.
nice toString function is already implemented and is called serialize ;) so
array_map(
'unserialize',
array_intersect(
array_map(
'serialize',
$obj1
),
array_map(
'serialize',
$obj2
)
)
);
will do the work, example mentioned higher don't work 'cause array_intersect work's only with strings as someone mentioned too
array_intersect() returns an array containing all the values of array1 that are present in all the arguments.
Then what mean present in this context (exacly this function), i found on php.net my answer:
Two elements are considered equal if
and only if (string) $elem1 ===
(string) $elem2. In words: when the
string representation is the
same.
Then you can't use it on array of objects if your objects not implements unique conversion to string.
Had a similar problem a few days ago, while these are answers are on the right path; I used them to work out the following:
From Artefacto's answer return $obj1 == $obj2 didn't really work, so I wrote a simple comparative function (basically gets the md5 of the serialised object and compares that):
function object_compare($obj1, $obj2){
$md5 = function($obj){
return md5(serialize($obj));
};
return strcmp($md5($obj1), $md5($obj2));
}
Then it’s jut a matter of calling array_uintersect with our comparative function to get the intersection:
# $array1 / $array2 are the array of objects we want to compare
return array_uintersect($array1, $array2, 'object_compare');
In my case, I had an unknown / dynamic array of objects, so I took it a step further so I don't have to declare array_uintersect($array1, $array2, ...) specifically - but just be able to pass in an array of arrays (of objects):
# $multiarray_of_objects is our array of arrays
$multiarray_of_objects[] = 'object_compare';
return call_user_func_array('array_uintersect', $multiarray_of_objects);
Just gotta remember to pass in the reference to our callback / comparative function as the last string in the array. Works like a charm!
The correct way to check whether two objects are equal is to use ==. Therefore:
array_uintersect($arr1, $arr2, function ($a1, $a2) { return $a1 == $a2; });
Just for completeness: Implement __toString() method in your object returning a unique value. For database entities this might be as easy as returning the fully qualified class name postfixed with the ID of the record. But it can also be arbitrarily complex by doing some hashing or even worse things.
In my opinion, it's the class's duty to serialize itself or create something unique to compare its objects by. Using anything outside of a class to serialize an object might result in strange behaviour (including comparing objects of different classes, which must never result in equality).
I use array_udiff to implement array_intersect for an object array.
function diff($a, $b) {
if($a === $b) {
return 0;
} else {
return 1;}
}
$array_1 = array('a', 'b', 'c');
$array_2 = array('c', 'd','e');
$array = array_udiff($array_1, array_udiff($array_1, $array_2, 'diff'),'diff');
var_dump($array);
return array(1) { [2]=> string(1) "c" }
You can have your own diff function for any scheme.
The correct solution would be:
array_uintersect($arr1, $arr2, function ($a1, $a2) { return $a1 != $a2; });
Note the != in the callback function as opposed to the answer from #Artefacto. Based on the documentation of array_uintersect, the callback function has to return 0 (false) if array items are equal.