I have been given a API which I am currently back engineering. there is one function in particular that gives me troubles with fully understanding its purpose/use.
private function split($data, Callable $callback)
{
$split = array();
if ($data) {
$split = array_map(function($joined) use ($callback) {
return $callback(explode('::', $joined));
}, explode(',', $data));
}
return $split;
}
I dont fully understand the concept of Callable, and function within array_map, function($joined) then this function USE callable variable, Could someone explain this concept form me please
A Callable argument is an argument that you can call ! As you can see in the code, the argument $callback is used like a function: $callback(...)
This is called high-order programming and this is really useful in certain cases. A simple example: Let's say you have to code a function that adds 2 and another function that multiplies by 2 every elements of an array. A simple but verbose way to do that is:
function multiply($array) {
$results = array();
foreach ($array as $number) {
$results[] = $number * 2;
}
return $results;
}
function add($array) {
$results = array();
foreach ($array as $number) {
$results[] = $number + 2;
}
return $results;
}
A lot of code is the same in the 2 functions. High-order programming is useful in this case, what you can do is create a function apply($function, $array) that apply the function $function to all the elements of $array and returns an array with the result.
function apply($function, $array) {
$results = array();
foreach ($array as $number) {
$results[] = $function($number);
}
return $results;
}
Now, if you want to multiply all the elements by 2 or add 2, you simply do:
function multiply($array) {
return apply(function($number) {
return $number * 2;
}, $array);
}
function add($array) {
return apply(function($number) {
return $number + 2;
}, $array);
}
You see, we give a function as an argument to the apply function. This function (called $function in apply) is applied to all the elements of $array, and apply returns the results (called $results).
The PHP function array_map does exactly the same thing. When your code calls array_map, it gives a function that takes one argument (the element of the array to process) and returns the processed element (here, it simply applies the function $callback to it).
Related
I am learning PHP using reviewing some complete PHP projects. (I know that this is a bad way, but my goal is not to be a PHP programmer!) Anyway, I faced with the following function that is weird a little to me:
function filterIt($filter): callable {
return function ($value) use ($filter) {
return filter_var($value, $filter) !== false;
};
}
I don't know what this function do and why it has been witter in such a way that a function is inside of another function! inner function returns something and main function also. Why we need such complicated function? or maybe one can make it simpler?
For this reason I want to write isEven() function as callable function like above. But I have no idea!
I don't know what that function do, but by mimicking from that:
function isEven($num): callable {
return function () use ($num) {
return $num % 2 == 0;
};
}
I couldn't debug this using var_dump or print_r .
Not sure how you are calling this in you're test, but as the function is actually returning a callable you would be able to debug it once you run it like this:
<?php
function isEven($num): callable {
return function () use ($num) {
return $num % 2 == 0;
};
}
var_dump(isEven(14)());
IMO the summary of the filterIt() function is "give me an anonymous function that filters based on $filter", and then that callable is likely passed somewhere else to be applied. I would venture to guess that the author wrote this function as a shorthand so that they did not need to write out the anonymous function definition over and over for different values of $filter.
Below is a simplified example of such behaviour:
class ExampleCollection {
protected $filters = [];
public function __construct(protected array $items) {}
public function addFilter(callable $filter) {
$this->filters[] = $filter;
}
public function getFiltered() :\Generator {
foreach($this->items as $item) {
foreach($this->filters as $filter) {
if( $filter($item) ) {
continue 2;
}
}
yield $item;
}
}
}
function makeMultipleFilter(int $value) :callable {
return function($a)use($value) {
return $a % $value === 0;
};
}
$c = new ExampleCollection([1,2,3,4,5,6,7,8,9,10]);
// add filters to exclude multiples of 3 and 4
$c->addFilter(makeMultipleFilter(3));
$c->addFilter(makeMultipleFilter(4));
foreach($c->getFiltered() as $item) {
printf("Got: %d\n", $item);
}
Output:
Got: 1
Got: 2
Got: 5
Got: 7
Got: 10
But I agree with Chris Haas' comment that the author's intent is not always obvious and is best asked of them, if possible. Further to that, not all code is exemplary, even if someone exemplary happens to have written it. Everyone writes themselves into a corner sometimes and has to resort to a confusing and/or ugly piece of code to get around it. Which is not to say that this is what that is, though it is somewhat confusing on first read.
How to access the variable in inner function of outer function variable?
I want to access $arr variable in the inner function.
<?php
function outer() {
$arr = array();
function inner($val) {
global $arr;
if($val > 0) {
array_push($arr,$val);
}
}
inner(0);
inner(10);
inner(20);
print_r($arr);
}
outer();
codepad link
This kind of "inner function" does not do what you probably expect. The global(!) function inner() will be defined upon calling outer(). This also means, calling outer() twice results in a "cannot redefine inner()" error.
As #hindmost pointed out, you need closures, functions that can get access to variables of the current scope. Also, while normal functions cannot have a local scope, closures can, because they are stored as variables.
Your code with Closures:
function outer() {
$arr = array();
$inner = function($val) use (&$arr) {
if($val > 0) {
array_push($arr,$val);
}
}
$inner(0);
$inner(10);
$inner(20);
print_r($arr);
}
outer();
Edited your code. You may want to refer to this
function outer() {
$arr = array();
function inner(&$arr,$val) { // pass array by reference
if($val > 0) {
array_push($arr,$val);
}
}
inner($arr,0);
inner($arr,10);
inner($arr,20);
print_r($arr);
}
outer();
You can pass arr by value, but you will not be able to print this
Array
(
[0] => 10
[1] => 20
)
First, do not use functions inside functions. Why? Because with this you'll get fatal error when triggering outer function second time (and that is because second run will invoke inner function declaration once again).
Second, if you need to do something with function result (as it stands from your code) - return that value. That is what is function intended to do - return some value. Another option may be using reference as a parameter (like in sort() functions family) - but in normal situation you should avoid such behavior. This is side-effect and this makes code less readable in general.
Third, do not use global variables. Because this binds your functions to your context, making your code totally unpredictable in terms of changing global context - and also impossible to scale to other contexts.
Functions in php are all global.
If you want to access the global $arr, then you have to make it global too in outer function.
function outer() {
global $arr;
$arr = array();
function inner($val) {
global $arr;
if($val > 0) {
array_push($arr,$val);
}
}
inner(0);
inner(10);
inner(20);
print_r($arr);
}
outer();
There is an better way of doing this.
function outer() {
$arr = array();
$inner = function ($val) use (&$arr) {
if($val > 0) {
array_push($arr, $val);
}
};
$inner(0);
$inner(10);
$inner(20);
print_r($arr);
}
outer();
Just put global to the outer function.
function outer() {
global $arr;
$arr = array();
function inner($val) {
global $arr;
if($val > 0) {
array_push($arr,$val);
}
}
inner(0);
inner(10);
inner(20);
print_r($arr);
}
Updated codepad.
Apologies for the newbie question but i have a function that takes two parameters one is an array one is a variable function createList($array, $var) {}. I have another function which calls createList with only one parameter, the $var, doSomething($var); it does not contain a local copy of the array. How can I just pass in one parameter to a function which expects two in PHP?
attempt at solution :
function createList (array $args = array()) {
//how do i define the array without iterating through it?
$args += $array;
$args += $var;
}
If you can get your hands on PHP 5.6+, there's a new syntax for variable arguments: the ellipsis keyword.
It simply converts all the arguments to an array.
function sum(...$numbers) {
$acc = 0;
foreach ($numbers as $n) {
$acc += $n;
}
return $acc;
}
echo sum(1, 2, 3, 4);
Doc: ... in PHP 5.6+
You have a couple of options here.
First is to use optional parameters.
function myFunction($needThis, $needThisToo, $optional=null) {
/** do something cool **/
}
The other way is just to avoid naming any parameters (this method is not preferred because editors can't hint at anything and there is no documentation in the method signature).
function myFunction() {
$args = func_get_args();
/** now you can access these as $args[0], $args[1] **/
}
You can specify no parameters in your function declaration, then use PHP's func_get_arg or func_get_args to get the arguments.
function createList() {
$arg1 = func_get_arg(0);
//Do some type checking to see which argument it is.
//check if there is another argument with func_num_args.
//Do something with the second arg.
}
Is there a way to make this code work without a Warning?
function myFunction($value, $key, &$array)
{
if (strlen($value)<=2) $array[] = $key.$value;
}
$a = array("aa", "bbb", "cc", "dd");
$resultA = array();
array_walk($a, 'myFunction', &$resultA);
// now '$resultA' should contain: Array([0] => aa0 [1] => cc2 [2] => dd3)
It works, but it always throws this warning message:
Warning: Call-time pass-by-reference
has been deprecated in
path_to\index.php
on line 7
I thought that removing the ampersand from the call should be enough to make the warning disappear, and it is, but, strangely the "array_walk" doesn't acomulate the third parameter if I just specify the & in "myFunction". To make it work there has to be an & in the call too, but then it will trigger the warning.
Further, as a temorary workaround I have tried to set the php.ini var "allow_call_time_pass_reference" to true, but I still get the warning...
I'm wondering that may be there's better/preferred method to apply user-defined functions to each element of an array WITH a passed-by-reference parameter.
The short answer is that you can't do that with array walk. However, you do have some alterantives:
Using a closure (available in PHP >= 5.3.0):
$myArray = array();
$callback = function ($key, $value) use (&$myArray) {
if (strlen($value) <= 2) {
$myArray[] = $key . $value;
}
};
array_walk($a, $callback);
Create a filter iterator (Note that this is likely way overkill):
class myFilterIterator extends FilterIterator {
public function accept() {
return strlen(parent::current()) <= 2;
}
public function current() {
return parent::key() . parent::current();
}
}
$it = new myFilterIterator(new ArrayIterator($a));
$newArray = iterator_to_array($it);
There are other ways, but you're appending of key and value really makes things difficult for mapping style solutions...
The third parameter to array_walk isn't passed by reference so that's not going to work.
Instead of a function, you can use an object method as a callback and accumulate the results in the object.
Class myClass
{
public values;
public function myCallback($value,$key)
{
if (strlen($value)<=2){
$this->values[] = $key.$value;
}
}
}
$a = array("aa", "bbb", "cc", "dd");
$obj = new myClass();
array_walk($a, array($obj,'myCallback'));
or you could define a global inside the callback function.
function myFunction($value, $key)
{
global $array;
if (strlen($value)<=2) $array[] = $key.$value;
}
both are valid.
One of the patterns that I frequently run across when developing is trying to collect a column/attribute value from a collection of objects into an array. For example:
$ids = array();
foreach ($documents as $document) {
$ids[] = $document->name;
}
Am I the only one who runs into this? And does PHP have a way to solve this in fewer lines? I've looked but found nothing.
Since I use an MVC framework I have access to a BaseUtil class which contains common functions that don't really fit in any specific classes. One solution proposed by a co-worker is:
class BaseUtil
{
public static function collect($collection, $property) {
$values = array();
foreach ($collection as $item) {
$values[] = $item->{$property};
}
return $values;
}
}
Then I can just do:
$ids = BaseUtil::collect($documents, 'name');
Not too shabby. Anyone else have any other ideas? And am I crazy or does this seem like a problem that PHP should have solved a long time ago?
You can use array_map() function for this purpose:
function getName($obj) {
return $obj->name;
}
$documentsName = array_map("getName", $documents);
You might also consider the create_function() function for lambda functions if you don't want to create a getName() function in the global namespace.
In PHP 5.3 you might even do:
$documentsName = array_map(function ($obj) { return $obj->name; }, $documents);
You can do it easily with ouzo goodies
$names = array_map(Functions::extract()->name, $documents);
or with Arrays (from ouzo goodies)
$names = Arrays::map($documents, Functions::extract()->name);
You can even extract nested fields of methods calls or array access etc:
$names = Arrays::map($documents, Functions::extract()->getAuthor()->roles[0]);
Check out: http://ouzo.readthedocs.org/en/latest/utils/functions.html#extract
See also functional programming with ouzo (I cannot post a link).
another approach is to use "rich" Array objects, like those found in other languages
for example
class Ary extends ArrayObject
{
function pluck($key) {
$a = array();
foreach($this as $sub) $a[] = $sub[$key];
return new self($a);
}
function join($delim = ',') {
return implode($delim, (array) $this);
}
static function init($ary) {
return new self($ary);
}
}
echo
Ary::init(array(
array('foo', 'bar'), array('baz', 'quux')
))->pluck(1)->join();
One of PHP's weaknesses as a language is that it's not terribly expressive.
Where in languages like Ruby or Perl you could probably get this data with a single line of code, you generally need small algorithms like the one you posted to get the desired results.
I'd stick with what you have, but here's another approach just for the heck of it.
class BaseUtil
{
public static function collect($collection, $property)
{
array_walk( $collection, array( __CLASS__, 'reduceObject' ), $property );
return $collection;
}
public static function reduceObject( &$object, $index, $property )
{
$object = $object->{$property};
}
}
Thanks for the input guys. I guess I'm just going to use my coworker's solution:
class BaseUtil
{
public static function collect($collection, $property) {
$values = array();
foreach ($collection as $item) {
$values[] = $item->{$property};
}
return $values;
}
}
Loading a Magento collection, and than looping again in that collection so you can add your desired values into an array it's ineffective. The appropriate way to do this is using getColumnValues() method. This method will give you an array of values by specifying the column name.
Here is the appropriate way of doing this.
$collection =Mage::getModel('your/object')->getCollection()
->addFieldToSelect('customer_id');
$yourArray = $collection->getColumnValues('customer_id');
This will give you an array with all customer_id values you selected.