How to imitate list() with own function / method in PHP? - php

We all know calls like this:
list($a, $b) = explode(':', 'A:B');
But how to make a custom function that can be use the same way?
$obj->mylist($a, $b) = explode(':', 'A:B');
mylist($a, $b) = explode(':', 'A:B');
If i use the above lines of code i always get: "Can't use method return value in write context in [...]"
How to define the function / class method to accept that type of assignment? How to the the array returned by explode()? Via func_get_args()?
Sure, there are other ways to assign the result of explode(), but that is not the subject of this question. E.g.
$obj->mylist($a, $b, explode(':', 'A:B'));
mylist($a, $b, explode(':', 'A:B'));

It is impossible as you cannot write a method that can be used as an lvalue.
list() is not a function but a language construct, that's why it can be used in the way it's used.

You can't do that. list() is a language construct, not a function, and there is no way in PHP to make a function behave like that. It will cause a syntax error.
What meaning would you want from mylist($a, $b) = explode(':', 'A:B'); anyways? If it were list instead of mylist it would just be equivalent to:
$arr = explode(':', 'A:B');
$a = $arr[0];
$b = $arr[1];
unset($arr);
How could you redefine the meaning of that for a function on the left? With your object example you can do something like:
list($obj->a, $obj->b) = explode(':', 'A:B');
What behaviour exactly are you trying to get? Perhaps there is another way.

Related

Is there any reason to ever prefer variadic parameter to iterable type-hint?

On the syntax level, these two approaches work identicaly:
<?php
function provideGenerator(): \Generator {
echo "[gInit]";
yield 0=>1;
echo "[gMid]";
yield 1=>2;
echo "[gEnd]";
}
function provideArray(): array {
return [1,2];
}
function provideIterator(): Iterator {
return new ArrayIterator(provideArray());
}
function acceptIterable(iterable $iterable) {
echo "iterable ". \gettype($iterable). ": ";
foreach ($iterable as $item) {
echo $item;
}
echo PHP_EOL;
}
function acceptVariadic(...$variadic) {
echo "variadic ". \gettype($variadic). ": ";
foreach ($variadic as $item) {
echo $item;
}
echo PHP_EOL;
}
acceptIterable(provideGenerator());
acceptIterable(provideArray());
acceptIterable(provideIterator());
acceptIterable([1,2]);
acceptVariadic(...provideGenerator());
acceptVariadic(...provideArray());
acceptVariadic(...provideIterator());
acceptVariadic(1,2);
Resulting:
iterable object: [gInit]1[gMid]2[gEnd]
iterable array: 12
iterable object: 12
iterable array: 12
[gInit][gMid][gEnd]variadic array: 12
variadic array: 12
variadic array: 12
variadic array: 12
Obviously the iterable approach has a lot of benefits:
is not unrolling upon passing of the parameter
allowing to pass keys
spread operator unrolls and converts to array, not only the original type is lost, it takes O(n) time.
spread operator has the quirk of Error: Cannot unpack Traversable with string keys
variadic parameter must be the last parameter, less flexible in refactoring/API extending
What are the reasons to ever use the variadic parameter?
apart from "can be type hinted"?
Chris Haas is right, you're confusing accepting variadic parameters and passing parameters with a spread operator. That's not just a vocabulary mistake, it's at the heart of your question.
A variadic function is one which can receive any number of parameters. You can call it just like any other function, you don't need to know it's variadic:
acceptVariadic(1, 2, 3);
A common example would be printf - note that you don't need an array to call it, but the number of parameters is variable and unlimited:
printf('%s %s', 'a', 'b');
The spread operator is for the opposite case: when you have an array or iterable, but the function takes separate arguments:
$params = [1,2,3];
someFunc(...$params);
// equivalent to
soneFunc(1,2,3);
Here, the function doesn't know that you're using an iterable, it sees the individual parameters.
Using both features at once somewhat defeats the point: if you know you always have an iterable, there's no point spreading it and then collecting it back together in a variadic parameter. So, I'm going to ignore all the discussion in your question of the cost of the spread operator, as irrelevant to real-world uses of variadic functions.
More specifically, variadic functions make most sense when the caller knows exactly how many arguments they want to pass, but the definition wants to support a variety of use cases. Collecting the parameters into an array is an implementation detail of how the definition supports that, not something the caller is interested in.
Taking the printf example, someone writing printf('%s %s %s', $x, $y, $z); can simply read it as a normal function call with 4 parameters. There is a version which instead expects the parameters as a list - vprintf('%s %s %s', [$x, $y, $z]); - but it's rarely used in my experience.
For some functions, there is a common case such as 1 or 2 values, but no distinction between those and further arguments - this is common in PHP's array functions:
$foo = array_merge($array1, $array2); // common case
$foo = array_merge($array1, $array2, $array3); // variadic case
array_push($array, $value); // common case
array_push($array, $value1, $value2); // variadic case
$foo = array_map($callback, $array); // common case
$foo = array_map($callback, $array1, $array2); // variadic case
Passing a list of arrays instead would just be clutter for the common cases, which users would have to remember to add even if they never passed any extra arguments:
$foo = array_merge([$array1, $array2]);
array_push($array, [$value]);
$foo = array_map($callback, [$array]);
In the end, it's mostly a decision of style, not practical benefit. As you've mentioned, PHP's current implementation does have the advantage of allowing you to type hint the variadic arguments, but mostly it's about allowing direct calls to read more naturally.

PHP "use" over additional parameters for closures?

In what situation is using the use keyword with a closure more beneficial then just passing along additional parameters to the closure?
Example #1:
$myClosure = function($var1, $var2) use ($var3){
//Some code
}
Example #2:
$myClosure = function($var1, $var2, $var3){
//Some code
}
Like all things it probably depends, but I don't see any functional difference between the two. Can anyone suggest a situation or example where one example would be preferred over the other?
I don't see any functional difference between the two.
The arguments are provided by the caller of the function. If you are the caller and can provide all the necessary arguments, then there is basically no difference*.
However, you might be passing the function somewhere else, so you are not caller and do not control which arguments are passed. This is the situation that closures solve: You can make values available without calling the function.
See also In PHP 5.3.0, what is the function "use" identifier? .
*: The variables that are bound through use are defined at definition time. So if the value of the variable changes, there will be a difference:
$a = 1;
$b = 2;
$c = 3;
$f1 = function($a, $b) use ($c) {
echo $a, $b, $c;
};
$f2 = function($a, $b, $c) {
echo $a, $b, $c;
};
$c = 42;
$f1($a, $b); // 1, 2, 3
$f2($a, $b, $c); // 1, 2, 42
Say you're using a closure with pre-defined parameters, like preg_replace_callback for example, and you want to use a variable from other places in your code, you'd use the use keyword
$myvar = "hello";
$text = "";
$text = preg_replace_callback('/regex/', function($matches) use ($myvar) {
return $myvar . " " . $matches[1] . "!";
}, $text);
It's a great way to use a variable within a closure with pre-defined parameters, which do not have access to variables outside the closure

Multi null checking

Is there any more efficient,cleaner and shorter form for following:
if($x==null or $y==null or $z==null or $w==null) return true;
In general how can I also get name of null variables?
function any_null(array $a){
foreach($a as $v) if ($v==null) return //variable name;
return false;
}
var_dump(any_null(array($x,$y,$z,$w)));
isset can take multiple arguments.
If multiple parameters are supplied then isset() will return TRUE only
if all of the parameters are set.
Since this is the opposite of what you want a simple negation is needed:
!isset($x, $y, $z, $w)
An additional bonus is that isset is not a regular function and will not raise any notices on undefined variables.
It's harder to get the name from the variables. You will need to check every one of them in some way. I think the best and most scalable solution is to use an associative array with the name of the variable as key:
function any_null(array $a){
foreach($a as $k => $v) if ($v === null) return $k;
return false;
}
any_null(array('x' => $x, 'y' => $y));
Note the strict comparison (===). Otherwise false, "", 0 and array() would be counted as "null" as well.
Another solution is to only pass the name of your variables and make use of the $GLOBALS array. Here I've coupled it with a call to func_get_args to get a little less verbose calling convention:
function any_null(){
$a = func_get_args();
foreach($a as $k) if (!isset($GLOBALS[$k])) return $k;
return false;
}
any_null('x', 'y', 'z');
This last solution have a lot of shortcomings though. You can only test variables in the global scope, and you can only test standard variables (not elements in an array, not objects and so on). The syntax for those would be very clunky.

Returning two values as function to two different variables in php

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.

array intersect for object array php

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.

Categories