Elegant way to search an PHP array using a user-defined function - php

Basically, I want to be able to get the functionality of C++'s find_if(), Smalltalk's detect: etc.:
// would return the element or null
check_in_array($myArray, function($element) { return $elemnt->foo() > 10; });
But I don't know of any PHP function which does this. One "approximation" I came up with:
$check = array_filter($myArray, function($element) { ... });
if ($check)
//...
The downside of this is that the code's purpose is not immediately clear. Also, it won't stop iterating over the array even if the element was found, although this is more of a nitpick (if the data set is large enough to cause problems, linear search won't be an answer anyway)

To pull the first one from the array, or return false:
current(array_filter($myArray, function($element) { ... }))
More info on current() here.

Here's a basic solution
function array_find($xs, $f) {
foreach ($xs as $x) {
if (call_user_func($f, $x) === true)
return $x;
}
return null;
}
array_find([1,2,3,4,5,6], function($x) { return $x > 4; }); // 5
array_find([1,2,3,4,5,6], function($x) { return $x > 10; }); // null
In the event $f($x) returns true, the loop short circuits and $x is immediately returned. Compared to array_filter, this is better for our use case because array_find does not have to continue iterating after the first positive match has been found.
In the event the callback never returns true, a value of null is returned.
Note, I used call_user_func($f, $x) instead of just calling $f($x). This is appropriate here because it allows you to use any compatible callable
Class Foo {
static private $data = 'z';
static public function match($x) {
return $x === self::$data;
}
}
array_find(['x', 'y', 'z', 1, 2, 3], ['Foo', 'match']); // 'z'
Of course it works for more complex data structures too
$data = [
(object) ['id' => 1, 'value' => 'x'],
(object) ['id' => 2, 'value' => 'y'],
(object) ['id' => 3, 'value' => 'z']
];
array_find($data, function($x) { return $x->id === 3; });
// stdClass Object (
// [id] => 3
// [value] => z
// )
If you're using PHP 7, add some type hints
function array_find(array $xs, callable $f) { ...

The original array_search returns the key of the matched value, and not the value itself (this might be useful if you're will to change the original array later).
try this function (it also works will associatives arrays)
function array_search_func(array $arr, $func)
{
foreach ($arr as $key => $v)
if ($func($v))
return $key;
return false;
}

Use \iter\search() from nikic's iter library of primitive iteration functions. It has the added benefit that it operates on both arrays and Traversable collections.
$foundItem = \iter\search(function ($item) {
return $item > 10;
}, range(1, 20));
if ($foundItem !== null) {
echo $foundItem; // 11
}

Pulled from Laravel's Illuminate\Collections\Arr::first method:
if (!function_exists('array_first')) {
/**
* Return the first element in an array passing a given truth test.
*
* #param iterable $array
* #param callable|null $callback
* #param mixed $default
* #return mixed
*/
function array_first($array, callable $callback = null, $default = null)
{
if (is_null($callback)) {
if (empty($array)) {
return $default;
}
foreach ($array as $item) {
return $item;
}
}
foreach ($array as $key => $value) {
if ($callback($value, $key)) {
return $value;
}
}
return $default;
}
}
I think it's pretty good. There is also the Illuminate\Collections\Arr::last method, but it's probably not as optimized since it reverses the array and just calls the first method. It does get the job done, though.
if (!function_exists('array_last')) {
/**
* Return the last element in an array passing a given truth test.
*
* #param array $array
* #param callable|null $callback
* #param mixed $default
* #return mixed
*/
function array_last($array, callable $callback = null, $default = null)
{
if (is_null($callback)) {
return empty($array) ? $default : end($array);
}
return array_first(array_reverse($array, true), $callback, $default);
}
}
Pro-tip: If you have an array of objects, then you can specify the type of the callback's argument for that sweet IDE auto-completion.
$john = array_first($users, function(User $user) {
return $user->name === 'John';
});
// Works with pretty much anything.
$activeUsers = array_filter($users, function(User $user) {
return $user->isActive;
});
// Class example:
class User {
public string $name;
public bool $isActive;
//...
}
If you want to use some variable from the outer scope, you can use the use(&$var) syntax like this
foreach($values as $key => $value) {
array_find($conditionsRecords, function($row) use(&$key) {
$keyToFind = $key;
return $keyToFind;
})
}

You can write such a function yourself, although it is little more than a loop.
For instance, this function allows you to pass a callback function. The callback can either return 0 or a value. The callback I specify returns the integer if it is > 10. The function stops when the callback returns a non null value.
function check_in_array(array $array, $callback)
{
foreach($array as $item)
{
$value = call_user_func($callback, $item);
if ($value !== null)
return $value;
}
}
$a = array(1, 2, 3, 6, 9, 11, 15);
echo check_in_array($a, function($i){ return ($i > 10?$i:null); });

You can write your own function ;)
function callback_search ($array, $callback) { // name may vary
return array_filter($array, $callback);
}
This maybe seems useless, but it increases semantics and can increase readability

Related

How to add and index dimensions of arrays in php dynamically?

I have an array in a PHP class and two member functions
First one receives two integers, one is the dimension and the other is the value:
private $complexArray;
function setValueToGivenDimension($dimension, $value)
What I want to do is to set the value to the given dimension of the array.
For example, If I call the function as the following:
setValueToGivenDimension(3,"key","myValue")
I want the array to be
array [
0 => array [
0 => array [
"key" => "myValue"
]
]
]
And Second is function getValueOfGivenDimension($dimension, $key)
For example, If I call the function as the following:
getValueOfGivenDimension(3,"key")
It should return value of the given key which is 0 in this case of the 3rd dimension of the $complexArray:
"myValue"
*array can have any level of dimension and I want to create and index dimensions of the array dynamically.
Here is a way to do it. Note the use of & to get a reference on the array:
function setValueToGivenDimension(&$array, $dimension, $key, $value)
{
for($i=1;$i<$dimension;$i++)
{
if(!isset($array[0]))
$array[0] = [];
$array = &$array[0];
}
$array[$key] = $value;
}
function getValueOfGivenDimension($array, $dimension, $key)
{
for($i=1;$i<$dimension;$i++)
{
if(!isset($array[0]))
return null;
$array = $array[0];
}
if(!isset($array[$key]))
return null;
return $array[$key];
}
$complexArray = [];
setValueToGivenDimension($complexArray, 3, 'key', 'value');
echo getValueOfGivenDimension($complexArray, 3, 'key');
You can use recursion for both.
The following method will return the array you desire, so you can set your private variable to the output.
function setValueToGivenDimension($dimension, $value) {
// Base case: if the dimension is 0, then we should return the value
if ($dimension == 0) return $value;
// If the dimension is greater than 0, then return the recursive function call, wrapped in a new array
return [setValueToGivenDimension($dimension - 1, $value)];
}
The following method will take an array, dimension, and key, and output the value for the nth dimension of that array, using the key in the innermost dimension.
function getValueOfGivenDimension($array, $dimension, $key) {
// Base case: if the function is at dimension 1, then return the value at the given key for the array
if ($dimension == 1) return $array[$key];
// If the dimension is greater than 0, then recursively call the function on the only child of the given array
return getValueOfGivenDimension($array[0], $dimension - 1, $key);
}
Based on Olivers answer, here is the code within a class:
<?php
class MyClass
{
private $complexArray;
/**
* #return void
*/
public function doSomething()
{
$this->setValueToGivenDimension(3, 'key', 'value');
print_r($this->complexArray);
echo $this->getValueOfGivenDimension(3, 'key') . PHP_EOL;
}
/**
* #param int $dimension
* #param mixed $key
* #param mixed $value
* #return $this
*/
private function setValueToGivenDimension($dimension, $key, $value)
{
if (!is_array($this->complexArray)) {
$this->complexArray = [];
}
if ($dimension > 0) {
$array = &$this->complexArray;
for ($i = 1; $i < $dimension; $i++) {
if (!isset($array[0])) {
$array[0] = [];
}
$array = &$array[0];
}
$array[$key] = $value;
}
return $this;
}
/**
* #param int $dimension
* #param mixed $key
* #return mixed|null
*/
private function getValueOfGivenDimension($dimension, $key)
{
if ($dimension > 0) {
$array = $this->complexArray;
for ($i = 1; $i < $dimension; $i++) {
if (!isset($array[0])) {
return null;
}
$array = $array[0];
}
if (!isset($array[$key])) {
return null;
}
return $array[$key];
}
return null;
}
}
Run it:
$mc = new MyClass();
$mc->doSomething();
Output:
Array
(
[0] => Array
(
[0] => Array
(
[key] => value
)
)
)
value

acessing deeper array elements (nested)

I have an array, say
$current_file_data=
['step1'=>
['step2'=1]
]
I want to have a simple function, that would take a string, and simply deliver the deeper element of this array, say something like:
function deeper_element($path,$array) {
return $array{$path};
}
so if using
$current_file_data
I would use deeper_element('['step1']['step2']',$current_file_data)
and return 1 from it (see array above)
i simple tried $array{$path} but it was not enough
Let's suppose that your $path is a string, which contains keys separated by a separator. For instance, if your separator is /, then the example of
a/b/c/d
would mean $array["a"]["b]["c"]["d"].
Let's see the function:
function getInnerArray($array, $path, $separator = "/") {
$keys = explode($separator, $path);
$temp = $array;
for ($keys as $key) {
if (isset($temp[$key])) {
$temp = $temp[$key];
} else {
return null;
}
}
return $temp;
}
Explanation: The separator can be anything you like, but for the sake of simplicity, I have defined a default value. An array of keys will be the result of explode, using the separator. A temp is initialized to the array and a cycle loops through the keys. Upon each step, temp will be refreshed to the inner element found by key if exists. If not, then the path is invalid and null is returned.
I dont think there is dynamic feature for this. And please check the syntax used for declaring arrays.
<?php
$data=array(
'step1'=>array(
'step2'=>1
)
);
function get_deep($path,$data){
$path=explode(',',$path);
return $data[$path[0]][$path[1]];
}
echo get_deep("step1,step2",$data);
?>
This will result 1 as output.
And if you want to access file contents,in such case json files you can create array like this
$data=json_decode(filestream,true);
<?php
function array_keys_multi(array $array)
{
$keys = array();
foreach ($array as $key => $value) {
$keys[] = $key;
if (is_array($array[$key])) {
$keys = array_merge($keys, array_keys_multi($array[$key]));
}
}
return $keys;
}
$current_file_data = array('step1' => array('step2'=>1));
$arr_keys = array_keys_multi($current_file_data);
if( in_array('step2', $arr_keys) )
echo "FOUND";
?>
thanks for the inspiring feedback. But I got it working doing the following:
Class:
<?php
namespace App\Tools\Arrays;
class DeepArrayExtractor
{
/**
* if path to deeper array element exists, return it
* otherwise return the complete original array
* #param array $deepArray
* #param $pathBitsString
* #param $separator
* #return array
*/
public static function deeperArrayElement(array $deepArray, $pathBitsString, $separator)
{
$currentArray = $deepArray;
$pathBits = explode($separator, $pathBitsString);
foreach ($pathBits as $bit) {
if (isset($currentArray[$bit])) {
$deepArrayElement = $currentArray[$bit];
$currentArray = $deepArrayElement;
} else {
return $deepArray;
}
}
return $deepArrayElement;
}
}
and unit tests
<?php
namespace App\Tools\Arrays;
class DeepArrayExtractorTest extends \TestCase
{
public function setUp()
{
parent::setUp();
}
public function tearDown()
{
parent::tearDown();
}
/**
* #test
* #group DeepArrayExtractorTest1
*/
public function getDeepArrayCorrectly()
{
$deepArray = [
'step1' =>
['step2' => 1]
];
$separator = '---';
$path = 'step1---step2';
$deepArrayElement = DeepArrayExtractor::deeperArrayElement($deepArray, $path, $separator);
$this->assertEquals(1, $deepArrayElement);
}
/**
* #test
* #group DeepArrayExtractorTest2
*/
public function deepArrayDoesNotExistSoOriginalArrayReturned()
{
$deepArray = [
'step1' =>
['step3' => 1]
];
$separator = '---';
$path = 'step1---step2';
$deepArrayElement = DeepArrayExtractor::deeperArrayElement($deepArray, $path, $separator);
$this->assertEquals($deepArray, $deepArrayElement);
}
}

array_map and pass 2 arguments to the mapped function - array_map(): Argument #3 should be an array

I have an abstract class that looks like this:
abstract class Transformer {
/**
* Transform a collection of items
*
* #param array $items
* #param bool $format
* #return array
*/
public function transformCollection(array $items, $format)
{
return array_map([$this, 'transform'], $items, $format);
}
/**
* Transform a item
*
* #param array $item
* #param bool $format
* #return mixed
*/
public abstract function transform(array $item, $format);
}
Then I have the following class that implements it:
class ServiceLogTransformer extends Transformer {
public function transform(array $service_log, $format = false)
{
return [
'id' => $service_log['id'],
'date' => $service_log['log_date'],
'time' => $service_log['log_time'],
'type' => ($format ? status_label($service_log['log_type']) : $service_log['log_type']),
'entry' => $service_log['log_entry']
];
}
}
When this code runs, I get the error:
array_map(): Argument #3 should be an array
How do you pass 2 or more arguments when you call array_map function within a class? I checked the PHP Documentation and it looks like this is allowed, but it isn't working on my Larave 4.2 project.
Any ideas?
Please always read docs:
http://php.net/manual/en/function.array-map.php
array array_map ( callable $callback , array $array1 [, array $... ] )
and yet you pass bool $format as argument
"How do you pass 2 or more arguments when you call array_map function
within a class?
I would create anonymous function with use() syntax
public function transformCollection(array $items, $format)
{
return array_map(function($item) use ($format) {
return $this->transform($item, $format);
}, $items);
}
You can't use array_map to pass hard-coded values to a callback function (what is commonly referred to as currying or partially-applied functions in functional languages). What array_map takes is a variable amount of arrays, all of which should have the same amount of elements. The element at the current index of each array is passed to the callback as separate arguments. So, for instance, if you do this:
$arr1 = [1, 2, 3, 4];
$arr2 = [2, 4, 6, 8];
$func = function ($a, $b) { return $a.'-'.$b; };
$arr3 = array_map($func, $arr1, $arr2);
You get this:
['1-2', '2-4', '3-6', '4-8']
Hopefully that explains the idea behind it - each array you pass in will have the element at the current position in the first array passed as the relevant parameter to the callback function.
So, as I said, it's not to be used for passing 'static' values to the callback. However, you can do this yourself by defining an anonymous function on the fly. In your transformCollection method:
return array_map(function ($item) use ($format) {
return $this->transform($item, $format);
}, $items);
Most answers show that an anonymous function with the use keyword is the typical way to pass additional arguments through to other callables.
abstract class Transformer {
public function transformCollection(array $items, $format)
{
return array_map(function($item) use ($format) {
return $this->transform($item, $format);
}, $items);
}
}
Most likely though, this particular situation would be better suited by a standard foreach loop over array_map, as it would potentially be more efficient and easier to read. This would also prevent your indexes from being renumbered, as well as work with Traversable and ArrayAccess items.
abstract class Transformer {
public function transformCollection(array $items, $format)
{
foreach($items as $key => $item) {
$items[$key] = $this->transform($item, $format);
}
return $items;
}
}
If you really, really have your heart set on using array_map, anonymous functions don't work with your environment (i.e. pre PHP 5.3) and you need to pass $format through as the 2nd argument, then you will need to convert $format to an array with the same length as $items.
abstract class Transformer {
public function transformCollection(array $items, $format)
{
// Fill an array of same length as $items with the $format argument.
$format = array_fill(0, count($items), $format);
return array_map([$this, 'transform'], $items, $format);
}
}
EDIT:
I realised recently that there was another option available since you're using an instance to transform your data. It involves storing $format to the instance, possibly using a setter, and overriding it if it is provided as an argument. This way it's accessible via the transform method using $this->format.
abstract class Transformer {
protected $format;
public function setFormat($format)
{
$this->format = $format;
}
public function transformCollection(array $items, $format = null)
{
if (isset($format)) {
$this->setFormat($format);
}
// ...
}
// ...
}
This may not be applicable for laravel 4.2 // pre php 5.3 (as Shaun mentions) but might come in handy for some people who come across this question.
abstract class Transformer {
/**
* Transform a collection of items
*
* #param array $items
* #param bool $format
* #return array
*/
public function transformCollection(array $items, $format)
{
$args = func_get_args();
return $this->mapWith([$this, 'transform'], $args);
}
/**
* #param callback<array|string> $callback
* #param array $args first a collection to disect, then optional additional arguments to the callback
* #return array
*/
private function mapWith($callback, $args) {
$data = array_shift($args);
$closure = \Closure::fromCallable($callback);
$scope = \is_array($callback) ? $callback[0] : null;
return array_map(function ($item) use ($scope, $closure, $args) {
array_unshift($args, $item);
if (null !== $scope) {
array_unshift($args, $scope);
$closure = [$closure, 'call'];
}
return \call_user_func_array($closure, $args);
}, $data);
}
/**
* Transform a item
*
* #param array $item
* #param bool $format
* #return mixed
*/
public abstract function transform(array $item, $format);
}
function status_label($index){return vsprintf('label: %s', [$index,]);}
#Then I have the following class that implements it:
class ServiceLogTransformer extends Transformer {
public function transform(array $service_log, $format = false)
{
return [
'id' => $service_log['id'],
'date' => $service_log['log_date'],
'time' => $service_log['log_time'],
'type' => ($format ? status_label($service_log['log_type']) : $service_log['log_type']),
'entry' => $service_log['log_entry']
];
}
}
$logs = [
['id' => 123454, 'log_date'=>'20180926', 'log_time'=>'151913', 'log_type'=>'q', 'log_entry' => 'lorem',],
['id' => 353454, 'log_date'=>'20180926', 'log_time'=>'152013', 'log_type'=>'r', 'log_entry' => 'dolor',],
];
$slt = new ServiceLogTransformer();
$new = $slt->transformCollection($logs, false);
$lab = $slt->transformCollection($logs, true);
var_dump($logs);
var_dump($new);
var_dump($lab);
So, that's a dynamic use, by using the call-method on the Closure-class that's underneath php's anonymous functions. If the callback is an array, it will bind the scope of the ->call to the first array-element, which should be the method's object.

PHP : Remove object from array

What is an elegant way to remove an object from an array of objects in PHP?
class Data{
private $arrObservers;
public add(Observer $o) {
array_push($this->arrObservers, $o);
}
public remove(Observer $o) {
// I NEED THIS CODE to remove $o from $this->arrObservers
}
}
You can do
function unsetValue(array $array, $value, $strict = TRUE)
{
if(($key = array_search($value, $array, $strict)) !== FALSE) {
unset($array[$key]);
}
return $array;
}
You can also use spl_object_hash to create a hash for the objects and use that as array key.
However, PHP also has a native Data Structure for Object collections with SplObjectStorage:
$a = new StdClass; $a->id = 1;
$b = new StdClass; $b->id = 2;
$c = new StdClass; $c->id = 3;
$storage = new SplObjectStorage;
$storage->attach($a);
$storage->attach($b);
$storage->attach($c);
echo $storage->count(); // 3
// trying to attach same object again
$storage->attach($c);
echo $storage->count(); // still 3
var_dump( $storage->contains($b) ); // TRUE
$storage->detach($b);
var_dump( $storage->contains($b) ); // FALSE
SplObjectStorage is Traversable, so you can foreach over it as well.
On a sidenote, PHP also has native interfaces for Subject and Observer.
I agree with the answers above, but for the sake of completeness (where you may not have unique IDs to use as a key) my preferred methods of removing values from an array are as follows:
/**
* Remove each instance of a value within an array
* #param array $array
* #param mixed $value
* #return array
*/
function array_remove(&$array, $value)
{
return array_filter($array, function($a) use($value) {
return $a !== $value;
});
}
/**
* Remove each instance of an object within an array (matched on a given property, $prop)
* #param array $array
* #param mixed $value
* #param string $prop
* #return array
*/
function array_remove_object(&$array, $value, $prop)
{
return array_filter($array, function($a) use($value, $prop) {
return $a->$prop !== $value;
});
}
Which are used in the following way:
$values = array(
1, 2, 5, 3, 5, 6, 7, 1, 2, 4, 5, 6, 6, 8, 8,
);
print_r(array_remove($values, 6));
class Obj {
public $id;
public function __construct($id) {
$this->id = $id;
}
}
$objects = array(
new Obj(1), new Obj(2), new Obj(4), new Obj(3), new Obj(6), new Obj(4), new Obj(3), new Obj(1), new Obj(5),
);
print_r(array_remove_object($objects, 1, 'id'));
I recommend using the ID (if you have one, anything that will be unique to that object should work within reason) of the object as the array key. This way you can address the object within the array without having to run through a loop or store the ID in another location. The code would look something like this:
$obj_array[$obj1->getId()] = $obj1;
$obj_array[$obj2->getId()] = $obj2;
$obj_array[$obj3->getId()] = $obj3;
unset($obj_array[$object_id]);
UPDATE:
class Data{
private $arrObservers;
public add(Observer $o) {
$this->arrObservers[$o->getId()] = $o;
}
public remove(Observer $o) {
unset($this->arrObservers[$o->getId()]);
}
}
unset($myArray[$index]); where $index is the index of the element you want to remove. If you wan't a more specific answer, show some code or describe what you're trying to do.
$obj_array['obj1'] = $obj1;
$obj_array['obj2'] = $obj2;
$obj_array['obj3'] = $obj3;
unset($obj_array['obj3']);
For remove an object from a multi dimensional array you can use this:
$exampleArray= [
[
"myKey"=>"This is my key",
"myValue"=>"10"
],
[
"myKey"=>"Oh!",
"myValue"=>"11"
]
];
With array_column you can specify your key column name:
if(($key = array_search("Oh!", array_column($exampleArray, 'myKey'))) !== false) {
unset($exampleArray[$key]);
}
And this will remove the indicated object.
Use this for your internal object storage instead: http://us2.php.net/manual/en/class.splobjectstorage.php
function obj_array_clean ($array, $objId)
{
$new = array() ;
foreach($array as $value)
{
$new[$value->{$objId}] = $value;
}
$array = array_values($new);
return $array;
}
$ext2 = obj_array_clean($ext, 'OnjId');
It will remove the duplicate object "OnjId" from array objects $array.
If you want to remove one or more objects from array of objects (using spl_object_hash to determine if objects are the same) you can use this method:
$this->arrObservers = Arr::diffObjects($this->arrObservers, [$o]);
from this library.
Reading the Observer pattern part of the GoF book? Here's a solution that will eliminate the need to do expensive searching to find the index of the object that you want to remove.
public function addObserver(string $aspect, string $viewIndex, Observer $view)
{
$this->observers[$aspect][$viewIndex] = $view;
}
public function removeObserver(string $aspect, string $viewIndex)
{
if (!isset($this->observers[$aspect])) {
throw new OutOfBoundsException("No such aspect ({$aspect}) of this Model exists: " . __CLASS__);
}
if (!isset($this->observers[$aspect][$viewIndex])) {
throw new OutOfBoundsException("No such View for ({$viewIndex}) was added to the aspect ({$aspect}) of this Model:" . __CLASS__);
}
unset($this->observers[$aspect][$viewIndex]);
}
You can loose the "aspect" dimension if you are not using that way of keeping track of which Views are updated by specific Models.
public function addObserver(string $viewIndex, Observer $view)
{
$this->observers[$viewIndex] = $view;
}
public function removeObserver(string $viewIndex)
{
if (!isset($this->observers[$viewIndex])) {
throw new OutOfBoundsException("No such View for ({$viewIndex}) was added to this Model:" . __CLASS__);
}
unset($this->observers[$viewIndex]);
}
Summary
Build in a way to find the element before assigning the object to the array. Otherwise, you will have to discover the index of the object element first.
If you have a large number of object elements (or, even more than a handful), then you may need to resort to finding the index of the object first. The PHP function array_search() is one way to start with a value, and get the index/key in return.
https://www.php.net/manual/en/function.array-search.php
Do be sure to use the strict argument when you call the function.
If the third parameter strict is set to true then the array_search()
function will search for identical elements in the haystack. This
means it will also perform a strict type comparison of the needle in
the haystack, and objects must be the same instance.
Try this, will solve your problem.
class Data{
private $arrObservers;
public add(Observer $o) {
array_push($this->arrObservers,$o);
}
public remove($Observer $o) {
unset($this->arrObservers[$o]);
}
}
I believe this is the best way
$index = array_search($o, $this->arrObservers, true);
unset($this->arrObservers[$index]);

PHP function to validate array

Is there a function which I could give an array, which would return true if the provided function returned true for all of them?
theFunction(array(1,2,3) , 'is_numeric') //true
theFunction(array(1,"b",3) , 'is_numeric') //false
No, but you can use array_reduce:
array_reduce(array(1,2,3),
function ($a, $v) { return $a && is_numeric($v); }, true);
You can of course build your own higher-order function:
function for_all(array $arr, $func) {
return array_reduce($arr,
function ($a, $v) use ($func) {
return $a && call_user_func($func, $v);
}, true);
}
var_dump(
for_all(array(1,2,3), 'is_numeric')
); //true
array_filter() does the job:
$data = array(1, 2, 3);
if ($data === array_filter($data, 'is_numeric'))
{
// all values of $data are numeric
}
/**
* all in collection?
*
* Passes each element of the collection to the given function. The method
* returns true if the function never returns false or null.
*
* If the function is not given, an implicit
* function ($v) { return ($v !== null && $v !== false) is added
* (that is all() will return true only if none of the collection members are false or null.)
*
* #param array $arr input array
* #param function $lambda takes an element, returns a bool (optional)
* #return boolean
*/
function all($arr, $lambda=null) {
// these differ from PHP's "falsy" values
if (!is_callable($lambda)) {
foreach ($arr as $value)
if ($value === false || $value === null)
return false;
} else {
foreach ($arr as $value)
if (!call_user_func($lambda, $value))
return false;
}
return true;
}
This is lifted from my implementation of Ruby's enum
You can call it like:
var_dump(all($array, 'is_numeric'));
var_dump(all($array, 'is_string'));
var_dump(all($array, function($x) { return $x != 'fun';})); // PHP >= 5.3.0
If you don't mind about efficiency and care more about simplicity you can use the min and array_map without having to create new functions.
(bool)min(array_map('is_numeric', array(1,2,3))); //true
(bool)min(array_map('is_numeric', array(1,"b",3))); //false
Also if you think about the process as finding one that doesn't fit the pattern you can rewrite it a bit cleaner.
!array_filter('is_not_numeric', array(1,2,3)); //true
!array_filter('is_not_numeric', array(1,"b",3)); //true
This is the function to validate value against validation rules or just use callables: names of PHP functions or closures.
/**
* Returns true if $value matches $pattern
*
* #param $value
* #param string $pattern
*
* #return bool
*
* #see https://github.com/ptrofimov/matchmaker - ultra-fresh PHP matching functions
* #author Petr Trofimov <petrofimov#yandex.ru>
*/
function matcher($value, $pattern)
{
$args = [];
if (($p = ltrim($pattern, ':')) != $pattern) foreach (explode(' ', $p) as $name) {
if (substr($name, -1) == ')') {
list($name, $args) = explode('(', $name);
$args = explode(',', rtrim($args, ')'));
}
if (is_callable(rules($name))) {
if (!call_user_func_array(rules($name), array_merge([$value], $args))) {
return false;
}
} elseif (rules($name) !== $value) {
return false;
}
} else {
return $pattern === '' || $value === $pattern;
}
return true;
}
You can use it with prepared set of validation rules, implemented in project matchmaker: https://github.com/ptrofimov/matchmaker

Categories