Is there a way to create a PHP array which always treated by reference without having to use the & operator?
For instance:
$a = array_by_ref('a', 'b', 'c');
$b = $a;
$b[] = 'd';
should result in both $a and $b being equal to:
('a', 'b', 'c', 'd')
If SPL is available, there is the ArrayObject class:
$a = new ArrayObject(array('a', 'b', 'c'));
$b = $a;
$b[] = 'd';
These are still wrapper objects though; to get their primitive array equivalents you have to use the object's getArrayCopy() method. Also bear in mind that it can be quite slow, particularly when you iterate through its elements.
ArrayObject doesn't go along with array_map, array_reduce, and similar functions that expect a real array as an input. If you want an array property of an object to be copied by reference, wrap it with any kind of object:
class Test
{
private $array;
public function __construct()
{
$this->array = (object) ['array' => []];
}
// we also need to return it by reference
public function &getMyArray()
{
return $this->array->array;
}
}
Sample usage:
$test = new Test();
$test->getMyArray()[] = 'Hello';
$another = clone $test;
$another->getMyArray()[] = 'Fucking';
$third = clone $another;
$third->getMyArray()[] = 'World!';
unset($test->getMyArray()[1]);
var_dump($test->getMyArray() === $third->getMyArray());
var_dump(implode(" ", $test->getMyArray()));
var_dump(gettype($test->getMyArray()));
Sample output:
bool(true)
string(12) "Hello World!"
string(5) "array"
Related
I want to compare an array of complex objects with an array of ids, with the expected result being an array of any object that did not have its id listed.
This sounds like a perfect use case for array_udiff, but I couldn't get it work without some confusing hassle. To illustrate my problem with that function, here a boiled down example:
class Foo {
public $id;
public function __construct($id) {
$this->id = $id;
}
}
$foos = [new Foo(1), new Foo(2), new Foo(3), new Foo(4)];
$fooIds = [1, 2, 3, 4];
$diff = array_udiff($foos, $fooIds, function ($f, $i){ return $f->id - $i; });
print_r($diff); // expected result: an empty array
// actual result:
// Array
// (
// [1] => Foo Object
// (
// [id] => 2
// )
// )
// Object of class Foo could not be converted to int :11
It sounds to me like array_udiff tries to do some type coercion between the elements of the arrays. I found no mention of this in the docs, and one question on SO seems to ask something similar, but doesn't have any answers. What I'd like to know:
Why does array_udiff behave this way? If we can supply an arbitrary callback function, this kind of coercion seems completely unnecessary, and in my case even very unhelpful.
Is there a good way to work around this, or should I use a different of function given my general problem?
Though a bit ugly, the seemingly simplest way to get the required result is to type-check before comparing.
<?php
declare(strict_types=1);
error_reporting(-1);
ini_set('display_errors', 'On');
class Foo
{
public $id;
public function __construct($id) {
$this->id = $id;
}
}
$foos = [new Foo(1), new Foo(2), new Foo(3), new Foo(4)];
$fooIds = [1, 2, 3, 4];
$diff = array_udiff($foos, $fooIds, function ($a, $b) {
echo gettype($a), ' <=> ', gettype($b), "\n";
if ($a instanceof Foo && $b instanceof Foo) {
return $a->id <=> $b->id;
}
if ($a instanceof Foo) {
return $a->id <=> $b;
}
if ($b instanceof Foo) {
return $a <=> $b->id;
}
return $a <=> $b;
});
print_r($diff); // expected result: an empty array
demo: https://3v4l.org/1uVYf
I think #apokryfos (from the answer you shared):
It makes sense that it would not have $a from the first array and $b
from the second array. What you essentially have in the function is a
comparator so if you sort both arrays based on the comparator (in O(nlogn) time) you can then get the diff in O(n) time in a sort-join
manner. If it was just pairwise comparisons it would be O(n^2) so I
suggest you treat your callback as the general comparator function
which works for the combined first and second array
Any way I recommend that simple workaround using array_column and the star-ship operator:
$diff = array_udiff(array_column($foos, "id"), $fooIds, function ($f, $i){ return $f <=> $i; });
array_column works on object too from PHP 7
Use an intermediate function that specializes in taking the comparison value of either Foo ($f->id) or non-Foo ($f):
$val = function($f) {
return $f instanceof Foo ? $f->id : $f;
};
$diff = array_udiff(
$foos,
$fooIds,
function ($f, $i) use ($val) { return $val($f) - $val($i); }
);
Or in a single comparison function:
function ($f, $i) { return ($f instanceof Foo ? $f->id : $f) - ($i instanceof Foo ? $i->id ? $i); }
My PHP code is here :
class test {
public $a = 'a';
public $b = 'b';
public $c = 'c';
}
$a = new test();
unset($a->a);
$b = serialize($a);
$c = unserialize($b);
var_dump($a, $b, $c);
Why does $c contain the property a?
unserialize creates and initialises a new instance of the class (although it doesn't call the constructor), and then maps any property values from the serialized string over the top. Because you're unsetting the property entirely there isn't a value to map back over the default, and so it stays set in your new object.
If you set the property to null rather than unsetting it then it will still be stored in the serialized copy, and the behaviour should end up the same as you intended.
$setToNull = new test;
$unset = new test;
$setToNull->a = null;
unset($unset->a);
var_dump(unserialize(serialize($setToNull)), unserialize(serialize($unset)));
object(test)#3 (3) {
["a"]=>
NULL
["b"]=>
string(1) "b"
["c"]=>
string(1) "c"
}
object(test)#4 (3) {
["a"]=>
string(1) "a"
["b"]=>
string(1) "b"
["c"]=>
string(1) "c"
}
(The difference is that the restored object will still have the a property set to null, which isn't quite the same as having it unset, but should behave the same in most situations)
As a more complicated solution (or if this doesn't match the behaviour you actually expect) then you might be able to make use of PHP's __sleep and __wakeup magic methods on your class, which give you finer-grained control of what happens when an object is serialized/unserialized.
When you call unserialize a new object will be created. It's not exactly the same object.
Your class have default values so when you unserialize an object and you don't have $a property being set then a default value will be used.
To see the difference check this code
class test {
public $a = 'a';
public $b = 'b';
public $c = 'c';
}
$a = new test();
$a->a= 'property'; // set a to "property"
unset($a->a); // remove a
$b = serialize($a);
$c = unserialize($b); // there is no "a" property set so default value will be used
var_dump($a, $b, $c);
If you remove default value from public $a = 'a'; then it'll be null
class test {
public $a;
public $b = 'b';
public $c = 'c';
}
$a = new test();
$a->a= 'property';
unset($a->a);
$b = serialize($a);
$c = unserialize($b);
echo '<pre>';
var_dump($a, $b, $c);
I have 3 function, it is a() b() and c()
and then, function a() returned :
{
id =>
total1 =>
}
function b() returned like funcion a() too, but have a different value (total1 and total2)
function c() returned :
{
id => //same value just like function a or b
name =>
}
$a = $model->a(); // from function a();
$b = $model->b(); // from function b();
$c = $model->c(); // from function c();
i want combine that 3 returned values into array like this :
array = ['id','name', 'total1', 'total2']
any idea ? Thanks
You can use array_merge() and array_keys():
array_keys(array_merge($a, $b, $c));
If variables are not arrays, convert them to arrays with toArray() or json_decode() first.
If functions return Laravel collections, you can use merge() and keys() helpers.
You can cast the results to array
$a = $model->a()->toArray(); // from function a();
$b = $model->b()->toArray(); // from function b();
$c = $model->c()->toArray(); // from function c();
and then
$data= array_merge($a , $b, $c)
class Test {
private $arr;
function __construct() {
$this->arr = array('test');
}
function getArr() {
return $this->arr;
}
}
$a = new Test();
$b = $a->getArr();
$b[0][0] = 'a';
$s = $a->getArr();
echo $s[0]
Why does this echo test instead of aest? Does PHP copy the array and the contents of the array when returning it? How do I get an array in which I can change the strings and have it reflected in the object?
By returning and assigning by reference:
class Test {
//...
function &getArr() {
return $this->arr;
}
}
$a = new Test();
$b =& $a->getArr();
$b[0][0] = 'a';
$s = $a->getArr();
echo $s[0];
Does PHP copy the array and the contents of the array when returning it?
From the point of view of the programmer, it works as if returning would copy the value, except when returning by reference. In terms of implementation, there are optimizations that avoid this happens, as long as it has no impact in the behavior of the script.
In a PHP program I have an array of some custom objects, and I want to find if the array contains a certain object. Of course I can use array_search, but this checks if the objects are the same object, not if it has the same variables. So I want to be able to create my own compare function for the objects, which I can use with the array_search method (or something similar).
I want to be able to do something like this:
class foo
{
public $_a,$_b;
function __construct($a,$b)
{
$this->_a = $a;
$this->_b = $b;
}
function __equals($object)
{
return $this->_a == $object->_a;
}
}
$f1 = new foo(5,4);
$f2 = new foo(4,6);
$f3 = new foo(4,5);
$array = array($f1,$f2);
$idx = array_search($f3,$array); // return 0
Is something like this possible?
I know I can also create my own array_search method which uses a method from the class, but than I'd have to use 2 different search functions, one for the classes which do have their own compare function, and one for those which haven't.
Here's a neat little trick I recently found out:
class Foo {
public $a;
public $b;
public function __toString() {
return (string)$this->a;
}
public function __construct($a, $b) {
$this->a = $a;
$this->b = $b;
}
}
$a = new Foo(1, 'a');
$b = new Foo(2, 'b');
$c = new Foo(3, 'c');
$d = new Foo(2, 'd');
$array = array($a, $b);
$key = array_search($d, $array); // false
$key = array_search((string)$c, $array); // false
$key = array_search((string)$d, $array); // 1
This also works:
$is_equal = ((string)$d == $b); // true
When passed a string $needle, array_search will try to cast the objects contained in $haystack to string to compare them, by calling the __toString magic method if it exists, which in this case returns Foo::$a.
Usually its not. You may look at the PECL Operators-Extension, but thats really old.