Pass by reference when using splat operator (...) - php

I have two functions. One of them receive and modify some values in an array that is passed by reference.
function dostuff ($param1, $param2, &$arr) {
//...
//add new elements to $arr
}
The other, which is a method in a class, that wraps the first one:
class Wrapper
{
public function foo (...$args) {
return dostuff(...$args);
}
}
However, if I pass the array to 'foo', the array stays unchanged.
I've tried to declare foo(... &$args) with an & but this led to an syntax error.
Is there a way to pass arguments by reference when using splat operator in PHP?

For PHP 8.x Versions https://3v4l.org/9ivmL
Do it like this:
<?php
class Wrapper
{
public function foo (&...$args) {
return $this->dostuff(...$args);
}
public function dostuff($param1, $param2, &$arr) {
$arr[] = $param1;
$arr[] = $param2;
return count($arr);
}
}
$values = [1,2];
$a=3;
$b=4;
$obj = new Wrapper();
#all parameter must be variables here because there are by ref now
$count = $obj->foo($a,$b, $values);
echo "Elements count: $count\r\n";
print_r($values); //Expected [1,2,3,4]
Output
Elements count: 4
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
)
See: https://www.php.net/manual/en/functions.arguments.php Example #13

Related

PHP convert class object to array showing namespace [duplicate]

How to convert (cast) Object to Array without Class Name prefix in PHP?
class Teste{
private $a;
private $b;
function __construct($a, $b) {
$this->a = $a;
$this->b = $b;
}
}
var_dump((array)(new Teste('foo','bar')));
Result:
array
'�Teste�a' => string 'foo' (length=3)
'�Teste�b' => string 'bar' (length=3)
Expected:
array (
a => 'foo'
b => 'bar' )
From the 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:
You can therefore work around the issue like this:
$temp = (array)(new Teste('foo','bar'));
$array = array();
foreach ($temp as $k => $v) {
$k = preg_match('/^\x00(?:.*?)\x00(.+)/', $k, $matches) ? $matches[1] : $k;
$array[$k] = $v;
}
var_dump($array);
It does seem odd that there is no way to control/disable this behaviour, since there is no risk of collisions.
The "class name prefix" is part of the (internal) name of the property. Because you declared both as private PHP needs something to distinguish this from properties $a and $b of any subclass.
The easiest way to bypass it: Don't make them private. You can declare them as protected instead.
However, this isn't a solution in every case, because usually one declares something as private with an intention. I recommend to implement a method, that makes the conversion for you. This gives you even more control on how the resulting array looks like
public function toArray() {
return array(
'a' => $this->a,
'b' => $this->b
);
}
As far as I know, PHP doesn't have a simple way to do what you want. Most languages don't. You should look into reflection. Take a look at this document: http://www.php.net/manual/en/reflectionclass.getproperties.php
I've made a function that should work as expected:
function objectToArr($obj)
{
$result = array();
ReflectionClass $cls = new ReflectionClass($obj);
$props = $cls->getProperties();
foreach ($props as $prop)
{
$result[$prop->getName()] = $prop->getValue($obj);
}
}
You can use Reflection to solve this task. But as usual this is a strong indicator that your class design is somewhat broken. However:
function objectToArray($obj) {
// Create a reflection object
$refl = new ReflectionClass($obj);
// Retrieve the properties and strip the ReflectionProperty objects down
// to their values, accessing even private members.
return array_map(function($prop) use ($obj) {
$prop->setAccessible(true);
return $prop->getValue($obj);
}, $refl->getProperties());
}
// Usage:
$arr = objectToArray( new Foo() );

PHP - How to split array into variables and pass to a function?

There are a few functions with different number of parameters in PHP. I have no idea which function is going to be called, but the function and its parameter is passed to the calling function as array like this
function func1($a,$b){...}
function func2($a){...}
$calls = [func1=>[arg1, arg2], func2=>[arg1]]
I need to call each function with its parameters. I don't know how to pass the parameters as distinct variables. This my code
$func_names = array_keys($calls);
$i = 0;
foreach($calls as $call){
$func_names[$i]($calls[$func_names[$i++]]);
//$func_names[$i]($call); same as above line
}
In each iteration array of arguments of each function is passed to the function not each item of the array separately. How can I solve this problem?
thanks
Use call_user_func_array
mixed call_user_func_array ( callable $callback , array $param_arr )
Example from the linked PHP manual -
<?php
function foobar($arg, $arg2) {
echo __FUNCTION__, " got $arg and $arg2\n";
}
class foo {
function bar($arg, $arg2) {
echo __METHOD__, " got $arg and $arg2\n";
}
}
// Call the foobar() function with 2 arguments
call_user_func_array("foobar", array("one", "two"));
// Call the $foo->bar() method with 2 arguments
$foo = new foo;
call_user_func_array(array($foo, "bar"), array("three", "four"));
?>
<?php
function a($foo) {
echo $foo, "\n";
}
function b($bar, $baz) {
echo $bar, ' and ', $baz, "\n";
}
$calls = ['a'=>['apples'], 'b'=>['bananas','banjos']];
foreach($calls as $k => $v) $k(...$v);
Output:
apples
bananas and banjos

can get_object_vars() return fields from magic __get()?

I'm running through a set of arrays and objects for further processing.
If it's an object i use get_object_vars($obj) to get its proberties. But if that object uses magic getters/setters, it returns an empty array. Here a little demo of the issue:
class Foo {
protected
$fields = array(
'foo'=>1,
'bar'=>2
);
function __get($key) {
return $this->fields[$key];
}
function __isset($key) {
return array_key_exists($key,$this->fields);
}
}
$foo = new Foo();
var_dump(get_object_vars($foo));
I want that get_object_vars returns the $fields key-value pairs. Is there any way to go, or any method to implement in Foo to make it work?
thanks for any hint.
That is not related to magic methods, but with the fact that get_object_vars() returns an array of accessible non-static properties for the specified object relative to scope. It will return all properties if the call is from inside the objects, but, only public from outside the object. You can however, use the IteratorAggregate interface, and ArrayIterator class, and combine that with iterator_to_array() function to obtain similar behavior and some additional benefits, as in this example.
class Foo implements IteratorAggregate {
protected
$fields = array(
'foo' => 1,
'bar' => 2
);
function __get( $name ) {
return array_key_exists( $name, $this->fields ) ? $this->fields[$name] : null;
}
public function __set( $name, $value )
{
$this->fields[$name] = $value;
}
function __isset( $name ) {
return isset( $this->fields[$name] );
}
public function getIterator() {
return new ArrayIterator( $this->fields );
}
}
$foo = new Foo();
$foo->baz = 3;
foreach( $foo as $key => $value ) {
echo "$key: $value "; // OUTPUT: foo: 1 bar: 2 baz: 3
}
array_walk( ( iterator_to_array( $foo ) ), function( $value, $key ) {
echo "$key: $value "; // OUTPUT: foo: 1 bar: 2 baz: 3
});
print_r( iterator_to_array( $foo ) ); // OUTPUT: Array ( [foo] => 1 [bar] => 2 [baz] => 3 )
get_object_vars only returns visible properties. As you're calling the function from outside the object, it has no visibility of your protected field, so it won't be returned.
What you can do instead is cast the object to an array:
var_dump((array) $foo);
This will alter the key a bit. Have a look here for specifics.
Another option is to create a method in the object that returns get_object_vars from the inside, as it will have visibility of everything. e.g.
class Foo {
...
public function get_object_vars() {
return get_object_vars($this);
}
}
$foo = new Foo();
var_dump($foo->get_object_vars());
And yet another option is obviously to simply change your property to public instead of protected.

How to unset nested array with ArrayObject?

ideone
Sample Code:
<?php
$a = new ArrayObject();
$a['b'] = array('c'=>array('d'));
print_r($a);
unset($a['b']['c']);
print_r($a);
Output
ArrayObject Object
(
[b] => Array
(
[c] => Array
(
[0] => d
)
)
)
ArrayObject Object
(
[b] => Array
(
[c] => Array
(
[0] => d
)
)
)
You notice that $a['b']['c'] is still there, even after unsetting. I would expect $a to have just the one value left (b).
In my actual app, I get the following warning:
Indirect modification of overloaded element of MyClass has no effect
Where MyClass extends ArrayObject. I have a lot of code that depends on being able to unset nested elements like this, so how can I get this to work?
One way to do it
<?php
$a = new ArrayObject();
$a['b'] = array('c' => array('d'));
$d =& $a['b'];
unset($d['c']);
print_r($a['b']);
prints:
Array
(
)
Would have to think a bit longer for an explanation as to why the syntax you've originally used doesn't remove the element.
EDIT: Explanation of behavior
What's happening is the call to unset($a['b']['c']); is translated into:
$temp = $a->offsetGet('b');
unset($temp['c']);
since $temp is a copy of $a instead of a reference to it, PHP uses copy-on-write internally and creates a second array where $temp doesn't have ['b']['c'], but $a still does.
ANOTHER EDIT: Reusable Code
So, no matter which way you slice it, seems like trying to overload function offsetGet($index) to be function &offsetGet($index) leads to trouble; so here's the shortest helper method I came up w/ could add it as a static or instance method in a subclass of ArrayObject, whatever floats your boat:
function unsetNested(ArrayObject $oArrayObject, $sIndex, $sNestedIndex)
{
if(!$oArrayObject->offSetExists($sIndex))
return;
$aValue =& $oArrayObject[$sIndex];
if(!array_key_exists($sNestedIndex, $aValue))
return;
unset($aValue[$sNestedIndex]);
}
So the original code would become
$a = new ArrayObject();
$a['b'] = array('c' => array('d'));
// instead of unset($a['b']['c']);
unsetNested($a, 'b', 'c');
print_r($a['b']);
YET ANOTHER EDIT: OO Solution
OK - So I must have been scrambling this morning b/c I found an error in my code, and when revised, we can implement a solution, based on OO.
Just so you know I tried it, extension segfaults..:
/// XXX This does not work, posted for illustration only
class BadMoxuneArrayObject extends ArrayObject
{
public function &offsetGet($index)
{
$var =& $this[$index];
return $var;
}
}
Implementing a Decorator on the other hand works like a charm:
class MoxuneArrayObject implements IteratorAggregate, ArrayAccess, Serializable, Countable
{
private $_oArrayObject; // Decorated ArrayObject instance
public function __construct($mInput=null, $iFlags=0, $sIteratorClass='')
{
if($mInput === null)
$mInput = array();
if($sIteratorClass === '')
$this->_oArrayObject = new ArrayObject($mInput, $iFlags);
else
$this->_oArrayObject = new ArrayObject($mInput, $iFlags, $sIteratorClass);
}
// -----------------------------------------
// override offsetGet to return by reference
// -----------------------------------------
public function &offsetGet($index)
{
$var =& $this->_oArrayObject[$index];
return $var;
}
// ------------------------------------------------------------
// everything else is passed through to the wrapped ArrayObject
// ------------------------------------------------------------
public function append($value)
{
return $this->_oArrayObject->append($value);
}
public function asort()
{
return $this->_oArrayObject->asort();
}
public function count()
{
return $this->_oArrayObject->count();
}
public function exchangeArray($mInput)
{
return $this->_oArrayObject->exchangeArray($mInput);
}
public function getArrayCopy()
{
return $this->_oArrayObject->getArrayCopy();
}
public function getFlags()
{
return $this->_oArrayObject->getFlags();
}
public function getIterator()
{
return $this->_oArrayObject->getIterator();
}
public function getIteratorClass()
{
return $this->_oArrayObject->getIteratorClass();
}
public function ksort()
{
return $this->_oArrayObject->ksort();
}
public function natcassesort()
{
return $this->_oArrayObject->natcassesort();
}
public function offsetExists($index)
{
return $this->_oArrayObject->offsetExists($index);
}
public function offsetSet($index, $value)
{
return $this->_oArrayObject->offsetSet($index, $value);
}
public function offsetUnset($index)
{
return $this->_oArrayObject->offsetUnset($index);
}
public function serialize()
{
return $this->_oArrayObject->serialize();
}
public function setFlags($iFlags)
{
return $this->_oArrayObject->setFlags($iFlags);
}
public function setIteratorClass($iterator_class)
{
return $this->_oArrayObject->setIteratorClass($iterator_class);
}
public function uasort($cmp_function)
{
return $this->_oArrayObject->uasort($cmp_function);
}
public function uksort($cmp_function)
{
return $this->_oArrayObject->uksort($cmp_function);
}
public function unserialize($serialized)
{
return $this->_oArrayObject->unserialize($serialized);
}
}
Now this code works as desired:
$a = new MoxuneArrayObject();
$a['b'] = array('c' => array('d'));
unset($a['b']['c']);
var_dump($a);
Still have to modify some code though..; I don't see any way round that.
It seems to me that the "overloaded" bracket operator of ArrayObject is returning a copy of the nested array, and not a reference to the original. Thus, when you call $a['b'], you are getting a copy of the internal array that ArrayObject is using to store the data. Further resolving it to $a['b']['c'] is just giving you the element "c" inside a copy, so calling unset() on it is not unsetting the element "c" in the original.
ArrayObject implements the ArrayAccess interface, which is what actually allows the bracket operator to work on an object. The documentation for ArrayAccess::offsetGet indicates that, as of PHP 5.3.4, references to the original data in ArrayObject's internal array can be acquired using the =& operator, as quickshiftin indicated in his example.
You can use unset($a->b['c']); instead of unset($a['b']['c']); in case if there won't be a huge problem to do a such replacement for all same situations within your project
I seem to have a partial solution. unset seems to work if all the nested arrays are instances of ArrayObject. In order to ensure all the nested arrays are ArrayObjects as well, we can derive instead from this class:
class ArrayWrapper extends ArrayObject {
public function __construct($input=array(), $flags=ArrayObject::STD_PROP_LIST, $iterator_class='ArrayIterator') {
foreach($input as $key=>$value) {
if(is_array($value)) {
$input[$key] = new self($value, $flags, $iterator_class);
}
}
parent::__construct($input, $flags, $iterator_class);
}
public function offsetSet($offset, $value) {
parent::offsetSet($offset, is_array($value) ? new ArrayWrapper($value) : $value);
}
}
(updated for recursiveness; untested)
And then whenever you try to add a nested array, it will automatically get converted to an ArrayWrapper instead.
Unfortunately many of the other array functions, such as array_key_exists don't work on ArrayObjects.

Magic method for when a class instance is called like a function?

What I want to do is create a Comparable class, similar to IComparable in .NET, such that you can instantiate it like so:
$cmp = new MultiArrayCompare(2);
And then you can sort an array via:
usort($myArray, $cmp);
And it will sort an array of arrays on the 2nd index. To do that, I imagine usort would try to call $cmp like a function, so I'd have to override that behaviour somehow. It doesn't look like __call does what I want (thought for a minute it was like Python's __call__).
If this isn't possible... is there another nice way to create a general solution to this problem? Where you can create a user-defined sorting function but give pass it some value ("2" in this case)?
Using __invoke I was able to create these classes:
abstract class Comparable {
abstract function Compare($a, $b);
function __invoke($a, $b) {
return $this->Compare($a, $b);
}
}
class ArrayCompare extends Comparable {
private $key;
function __construct($key) {
$this->key = $key;
}
function Compare($a, $b) {
if($a[$this->key] == $b[$this->key]) return 0;
return $a[$this->key] < $b[$this->key] ? -1 : 1;
}
}
class ArrayCaseCompare extends Comparable {
private $key;
function __construct($key) {
$this->key = $key;
}
function Compare($a, $b) {
return strcasecmp($a[$this->key], $b[$this->key]);
}
}
Which I can use to sort an array of arrays:
$arr = array(
array(1,2,3),
array(2,3,4),
array(3,2,4),
)
usort($arr,new ArrayCompare(1));
You are looking for __invoke:
The __invoke() method is called when a script tries to call an object as a function.
Example:
class SortAscending
{
public function __invoke($a, $b)
{
return $a - $b;
}
}
$numbers = array(4,7,2,3,9,1);
usort($numbers, new SortAscending);
print_r( $numbers );
Output (demo):
Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 7 [5] => 9 )
The slimmer alternative is using a Closure. Your code would simply be:
$arr = array(
array(1,2,3),
array(2,3,4),
array(3,2,4),
);
$key = 1;
usort($arr, function($a, $b) use ($key) {
return $a[$key] - $b[$key];
});
or - reusable and configurable (demo):
$ArrayCompare = function ($index) {
return function($a, $b) use ($index) {
return $a[$index] - $b[$index];
};
};
usort($arr, $ArrayCompare(1));
Apart from that, you can use basically any method you want and specify the method in the callback:
usort($arr, array(new ArrayCompare(1), 'Compare'));
The above would achieve the same without magic. See the chapter on callbacks for additional options.
Maybe you are looking for __invoke()?
Example from the manual:
class CallableClass
{
public function __invoke($x)
{
var_dump($x);
}
}
$obj = new CallableClass;
$obj(5);
var_dump(is_callable($obj));
Why you don't create a Comparer class that provides a set of static compareXXX() methods?
To me this looks much cleaner with an object oriented approach in mind...

Categories