array_walk_recursive - modify both keys and values - php

How can you modify both keys and values with array_walk_recursive??
Here only the values are encoded
function _utf8_encode($arr){
array_walk_recursive($arr, 'utf8_enc');
return $arr;
}
function utf8_enc(&$value, &$key){
$value = utf8_encode($value);
$key = utf8_encode($key);
}

array_walk_recursive does ONLY apply the user function on the VALUES of an array, not on indexes (I think it has something to with the fact, that the indexes of an array have to be unique, so you cannot manipulate them). Best thing would be to write a recursive function on yourself. The following should work:
function utf8enc($array) {
if (!is_array($array)) return;
$helper = array();
foreach ($array as $key => $value) $helper[utf8_encode($key)] = is_array($value) ? utf8enc($value) : utf8_encode($value);
return $helper;
}
$enc_array = utf8enc($your_array);

This my recursive function that can change not only the values of the array as array_walk_recursive() but also the keys of the given array. It also keeps the order of the array.
/**
* Change values and keys in the given array recursively keeping the array order.
*
* #param array $_array The original array.
* #param callable $_callback The callback function takes 2 parameters (key, value)
* and returns an array [newKey, newValue] or null if nothing has been changed.
*
* #return void
*/
function modifyArrayRecursive(array &$_array, callable $_callback): void
{
$keys = \array_keys($_array);
foreach ($keys as $keyIndex => $key) {
$value = &$_array[$key];
if (\is_array($value)) {
modifyArrayRecursive($value, $_callback);
continue;
}
$newKey = $key;
$newValue = $value;
$newPair = $_callback ($key, $value);
if ($newPair !== null) {
[$newKey, $newValue] = $newPair;
}
$keys[$keyIndex] = $newKey;
$_array[$key] = $newValue;
}
$_array = \array_combine($keys, $_array);
}
/**
* Usage example
*/
modifyArrayRecursive($keyboardArr, function ($key, $value) {
if ($value === 'some value') {
return ['new_key_for_this_value', $value];
}
return null;
});

Another recursive function in addition to rabudde's answer:
function utf8_encode_array($array_to_encode=array()){
$encoded_array=array();
if(is_array($array_to_encode)){
foreach($array_to_encode as $key => $value){
$key=utf8_encode($key);
if(is_array($value)){
$encoded_array[$key]=utf8_encode_array($value);
}
else{
$encoded_array[$key]=utf8_encode($value);
}
}
}
return $encoded_array;
}

/**
* Recursively modifies keys or/and values of an array
* #param array $array
* #param callable $func
* #return array
*/
function modifyArrayRecursive(array $array, callable $func): array
{
$nArray = [];
foreach($array as $key => $value) {
if(is_array($value)) {
$value = modifyArrayRecursive($value, $func);
}
$nArray += $func($key, $value);
}
return $nArray;
}
/**
* Array wants to be modified
*/
$array = [
'key1' => [
'key2' => ['value1', 'value2']
],
'value3',
'value4'
];
/**
* Example of getting new modified array
*/
$nArray = modifyArrayRecursive($array, function($key, $value) {
if($key === 'key2') {
$key = 'key2_upd';
}
if($value === 'value4') {
$value = 'value4_upd';
}
return [$key => $value];
});

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

Adding an alphabetic sort order

I have some code that I have tried modifying so that the array is sorted alphabetically, I thought the following:
public function getOptions(array $aggs, $filterEmptyOptions = true)
Should be:
public function getOptions(array sort($aggs), $filterEmptyOptions = true)
But it doesn't work, where should I add sort() to the array?
EDIT: I am trying to sort the brewery names alphabetically
This is my full code if it makes it easier?
class BreweryBoolMustFilter implements BoolFilterInterface
{
/**
* #param Query $query
* #param Query\BoolQuery $bool
* #param Request $request
* #param array $filters
*/
public function __construct(Query $query, Query\BoolQuery $bool, Request $request, array &$filters)
{
if ($request->query->has('brewery') && (int) $request->query->get('brewery'))
{
$filters['brewery'] = (int) $request->query->get('brewery');
$bool->addMust(new Query\Term(['breweryId' => $filters['brewery']]));
}
$aggs3 = new Terms('breweries');
$aggs3->setField('breweryId');
$aggs3->setSize(100);
$query->addAggregation($aggs3);
}
/**
* #param array $aggs
* #param bool $filterEmptyOptions
*
* #return array
*/
public function getOptions(array $aggs, $filterEmptyOptions = true)
{
$breweriesObjects = (new BreweryQuery)->find();
$breweries = [];
foreach ($breweriesObjects as $breweryObject)
{
$breweries[$breweryObject->getId()] = [
'name' => $breweryObject->getName(),
'doc_count' => 0,
];
}
foreach ($aggs['breweries']['buckets'] as $bucket)
{
if (isset($breweries[$bucket['key']]))
{
$breweries[$bucket['key']]['doc_count'] = $bucket['doc_count'];
}
}
if (true === $filterEmptyOptions)
{
foreach ($breweries as $key => $brewery)
{
if (0 == $brewery['doc_count'])
{
unset($breweries[$key]);
}
}
}
return $breweries;
}
PHP indexes only natural datatypes, you can't execute a function with an argument.
The best case you can do to close the scope is put inside the function or work with anonymous functions to close scope in all your code:
$f = function($data){
return sort($data);
};
yourfunction($f());
yourfunction2($f());
You need to call sort() in the function, not in the parameter list. You should be sorting $breweries after you fill it in, sorting it by the name values. You can use usort() for this.
public function getOptions(array $aggs, $filterEmptyOptions = true)
{
$breweriesObjects = (new BreweryQuery)->find();
$breweries = [];
foreach ($breweriesObjects as $breweryObject)
{
$breweries[$breweryObject->getId()] = [
'name' => $breweryObject->getName(),
'doc_count' => 0,
];
}
usort($breweries, function($a, $b) {
if ($a['name'] < $b['name']) {
return -1;
} elseif ($a['name'] > $b['name']) {
return 1;
} else {
return 0;
}
});
foreach ($aggs['breweries']['buckets'] as $bucket)
{
if (isset($breweries[$bucket['key']]))
{
$breweries[$bucket['key']]['doc_count'] = $bucket['doc_count'];
}
}
if (true === $filterEmptyOptions)
{
foreach ($breweries as $key => $brewery)
{
if (0 == $brewery['doc_count'])
{
unset($breweries[$key]);
}
}
}
return $breweries;
}

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);
}
}

Foreach array php [duplicate]

I'm integrating an API to my website which works with data stored in objects while my code is written using arrays.
I'd like a quick-and-dirty function to convert an object to an array.
Just typecast it
$array = (array) $yourObject;
From Arrays:
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.
Example: Simple Object
$object = new StdClass;
$object->foo = 1;
$object->bar = 2;
var_dump( (array) $object );
Output:
array(2) {
'foo' => int(1)
'bar' => int(2)
}
Example: Complex Object
class Foo
{
private $foo;
protected $bar;
public $baz;
public function __construct()
{
$this->foo = 1;
$this->bar = 2;
$this->baz = new StdClass;
}
}
var_dump( (array) new Foo );
Output (with \0s edited in for clarity):
array(3) {
'\0Foo\0foo' => int(1)
'\0*\0bar' => int(2)
'baz' => class stdClass#2 (0) {}
}
Output with var_export instead of var_dump:
array (
'' . "\0" . 'Foo' . "\0" . 'foo' => 1,
'' . "\0" . '*' . "\0" . 'bar' => 2,
'baz' =>
stdClass::__set_state(array(
)),
)
Typecasting this way will not do deep casting of the object graph and you need to apply the null bytes (as explained in the manual quote) to access any non-public attributes. So this works best when casting StdClass objects or objects with only public properties. For quick and dirty (what you asked for) it's fine.
Also see this in-depth blog post:
Fast PHP Object to Array conversion
You can quickly convert deeply nested objects to associative arrays by relying on the behavior of the JSON encode/decode functions:
$array = json_decode(json_encode($nested_object), true);
From the first Google hit for "PHP object to assoc array" we have this:
function object_to_array($data)
{
if (is_array($data) || is_object($data))
{
$result = [];
foreach ($data as $key => $value)
{
$result[$key] = (is_array($value) || is_object($value)) ? object_to_array($value) : $value;
}
return $result;
}
return $data;
}
The source is at codesnippets.joyent.com.
To compare it to the solution of json_decode & json_encode, this one seems faster. Here is a random benchmark (using the simple time measuring):
$obj = (object) [
'name' =>'Mike',
'surname' =>'Jovanson',
'age' =>'45',
'time' =>1234567890,
'country' =>'Germany',
];
##### 100 000 cycles ######
* json_decode(json_encode($var)) : 4.15 sec
* object_to_array($var) : 0.93 sec
If your object properties are public you can do:
$array = (array) $object;
If they are private or protected, they will have weird key names on the array. So, in this case you will need the following function:
function dismount($object) {
$reflectionClass = new ReflectionClass(get_class($object));
$array = array();
foreach ($reflectionClass->getProperties() as $property) {
$property->setAccessible(true);
$array[$property->getName()] = $property->getValue($object);
$property->setAccessible(false);
}
return $array;
}
What about get_object_vars($obj)? It seems useful if you only want to access the public properties of an object.
See get_object_vars.
class Test{
const A = 1;
public $b = 'two';
private $c = test::A;
public function __toArray(){
return call_user_func('get_object_vars', $this);
}
}
$my_test = new Test();
var_dump((array)$my_test);
var_dump($my_test->__toArray());
Output
array(2) {
["b"]=>
string(3) "two"
["Testc"]=>
int(1)
}
array(1) {
["b"]=>
string(3) "two"
}
Type cast your object to an array.
$arr = (array) $Obj;
It will solve your problem.
Here is some code:
function object_to_array($data) {
if ((! is_array($data)) and (! is_object($data)))
return 'xxx'; // $data;
$result = array();
$data = (array) $data;
foreach ($data as $key => $value) {
if (is_object($value))
$value = (array) $value;
if (is_array($value))
$result[$key] = object_to_array($value);
else
$result[$key] = $value;
}
return $result;
}
All other answers posted here are only working with public attributes. Here is one solution that works with JavaBeans-like objects using reflection and getters:
function entity2array($entity, $recursionDepth = 2) {
$result = array();
$class = new ReflectionClass(get_class($entity));
foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
$methodName = $method->name;
if (strpos($methodName, "get") === 0 && strlen($methodName) > 3) {
$propertyName = lcfirst(substr($methodName, 3));
$value = $method->invoke($entity);
if (is_object($value)) {
if ($recursionDepth > 0) {
$result[$propertyName] = $this->entity2array($value, $recursionDepth - 1);
}
else {
$result[$propertyName] = "***"; // Stop recursion
}
}
else {
$result[$propertyName] = $value;
}
}
}
return $result;
}
To convert an object into array just cast it explicitly:
$name_of_array = (array) $name_of_object;
You can also create a function in PHP to convert an object array:
function object_to_array($object) {
return (array) $object;
}
Use:
function readObject($object) {
$name = get_class ($object);
$name = str_replace('\\', "\\\\", $name); // Outcomment this line, if you don't use
// class namespaces approach in your project
$raw = (array)$object;
$attributes = array();
foreach ($raw as $attr => $val) {
$attributes[preg_replace('('.$name.'|\*|)', '', $attr)] = $val;
}
return $attributes;
}
It returns an array without special characters and class names.
You can easily use this function to get the result:
function objetToArray($adminBar){
$reflector = new ReflectionObject($adminBar);
$nodes = $reflector->getProperties();
$out = [];
foreach ($nodes as $node) {
$nod = $reflector->getProperty($node->getName());
$nod->setAccessible(true);
$out[$node->getName()] = $nod->getValue($adminBar);
}
return $out;
}
Use PHP 5 or later.
Short solution of #SpYk3HH
function objectToArray($o)
{
$a = array();
foreach ($o as $k => $v)
$a[$k] = (is_array($v) || is_object($v)) ? objectToArray($v): $v;
return $a;
}
Here is my recursive PHP function to convert PHP objects to an associative array:
// ---------------------------------------------------------
// ----- object_to_array_recursive --- function (PHP) ------
// ---------------------------------------------------------
// --- arg1: -- $object = PHP Object - required --
// --- arg2: -- $assoc = TRUE or FALSE - optional --
// --- arg3: -- $empty = '' (Empty String) - optional --
// ---------------------------------------------------------
// ----- Return: Array from Object --- (associative) -------
// ---------------------------------------------------------
function object_to_array_recursive($object, $assoc=TRUE, $empty='')
{
$res_arr = array();
if (!empty($object)) {
$arrObj = is_object($object) ? get_object_vars($object) : $object;
$i=0;
foreach ($arrObj as $key => $val) {
$akey = ($assoc !== FALSE) ? $key : $i;
if (is_array($val) || is_object($val)) {
$res_arr[$akey] = (empty($val)) ? $empty : object_to_array_recursive($val);
}
else {
$res_arr[$akey] = (empty($val)) ? $empty : (string)$val;
}
$i++;
}
}
return $res_arr;
}
// ---------------------------------------------------------
// ---------------------------------------------------------
Usage example:
// ---- Return associative array from object, ... use:
$new_arr1 = object_to_array_recursive($my_object);
// -- or --
// $new_arr1 = object_to_array_recursive($my_object, TRUE);
// -- or --
// $new_arr1 = object_to_array_recursive($my_object, 1);
// ---- Return numeric array from object, ... use:
$new_arr2 = object_to_array_recursive($my_object, FALSE);
Custom function to convert stdClass to an array:
function objectToArray($d) {
if (is_object($d)) {
// Gets the properties of the given object
// with get_object_vars function
$d = get_object_vars($d);
}
if (is_array($d)) {
/*
* Return array converted to object
* Using __FUNCTION__ (Magic constant)
* for recursive call
*/
return array_map(__FUNCTION__, $d);
} else {
// Return array
return $d;
}
}
Another custom function to convert Array to stdClass:
function arrayToObject($d) {
if (is_array($d)) {
/*
* Return array converted to object
* Using __FUNCTION__ (Magic constant)
* for recursive call
*/
return (object) array_map(__FUNCTION__, $d);
} else {
// Return object
return $d;
}
}
Usage Example:
// Create new stdClass Object
$init = new stdClass;
// Add some test data
$init->foo = "Test data";
$init->bar = new stdClass;
$init->bar->baaz = "Testing";
$init->bar->fooz = new stdClass;
$init->bar->fooz->baz = "Testing again";
$init->foox = "Just test";
// Convert array to object and then object back to array
$array = objectToArray($init);
$object = arrayToObject($array);
// Print objects and array
print_r($init);
echo "\n";
print_r($array);
echo "\n";
print_r($object);
First of all, if you need an array from an object you probably should constitute the data as an array first. Think about it.
Don't use a foreach statement or JSON transformations. If you're planning this, again you're working with a data structure, not with an object.
If you really need it use an object-oriented approach to have a clean and maintainable code. For example:
Object as array
class PersonArray implements \ArrayAccess, \IteratorAggregate
{
public function __construct(Person $person) {
$this->person = $person;
}
// ...
}
If you need all properties, use a transfer object:
class PersonTransferObject
{
private $person;
public function __construct(Person $person) {
$this->person = $person;
}
public function toArray() {
return [
// 'name' => $this->person->getName();
];
}
}
Also you can use The Symfony Serializer Component
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
$serializer = new Serializer([new ObjectNormalizer()], [new JsonEncoder()]);
$array = json_decode($serializer->serialize($object, 'json'), true);
You might want to do this when you obtain data as objects from databases:
// Suppose 'result' is the end product from some query $query
$result = $mysqli->query($query);
$result = db_result_to_array($result);
function db_result_to_array($result)
{
$res_array = array();
for ($count=0; $row = $result->fetch_assoc(); $count++)
$res_array[$count] = $row;
return $res_array;
}
This answer is only the union of the different answers of this post, but it's the solution to convert a PHP object with public or private properties with simple values or arrays to an associative array...
function object_to_array($obj)
{
if (is_object($obj))
$obj = (array)$this->dismount($obj);
if (is_array($obj)) {
$new = array();
foreach ($obj as $key => $val) {
$new[$key] = $this->object_to_array($val);
}
}
else
$new = $obj;
return $new;
}
function dismount($object)
{
$reflectionClass = new \ReflectionClass(get_class($object));
$array = array();
foreach ($reflectionClass->getProperties() as $property) {
$property->setAccessible(true);
$array[$property->getName()] = $property->getValue($object);
$property->setAccessible(false);
}
return $array;
}
Some impovements to the "well-knwon" code
/*** mixed Obj2Array(mixed Obj)***************************************/
static public function Obj2Array($_Obj) {
if (is_object($_Obj))
$_Obj = get_object_vars($_Obj);
return(is_array($_Obj) ? array_map(__METHOD__, $_Obj) : $_Obj);
} // BW_Conv::Obj2Array
Notice that if the function is member of a class (like above) you must change __FUNCTION__ to __METHOD__
For your case it was right/beautiful if you would use the "decorator" or "date model transformation" patterns. For example:
Your model
class Car {
/** #var int */
private $color;
/** #var string */
private $model;
/** #var string */
private $type;
/**
* #return int
*/
public function getColor(): int
{
return $this->color;
}
/**
* #param int $color
* #return Car
*/
public function setColor(int $color): Car
{
$this->color = $color;
return $this;
}
/**
* #return string
*/
public function getModel(): string
{
return $this->model;
}
/**
* #param string $model
* #return Car
*/
public function setModel(string $model): Car
{
$this->model = $model;
return $this;
}
/**
* #return string
*/
public function getType(): string
{
return $this->type;
}
/**
* #param string $type
* #return Car
*/
public function setType(string $type): Car
{
$this->type = $type;
return $this;
}
}
Decorator
class CarArrayDecorator
{
/** #var Car */
private $car;
/**
* CarArrayDecorator constructor.
* #param Car $car
*/
public function __construct(Car $car)
{
$this->car = $car;
}
/**
* #return array
*/
public function getArray(): array
{
return [
'color' => $this->car->getColor(),
'type' => $this->car->getType(),
'model' => $this->car->getModel(),
];
}
}
Usage
$car = new Car();
$car->setType('type#');
$car->setModel('model#1');
$car->setColor(255);
$carDecorator = new CarArrayDecorator($car);
$carResponseData = $carDecorator->getArray();
So it will be more beautiful and more correct code.
Converting and removing annoying stars:
$array = (array) $object;
foreach($array as $key => $val)
{
$new_array[str_replace('*_', '', $key)] = $val;
}
Probably, it will be cheaper than using reflections.
I use this (needed recursive solution with proper keys):
/**
* This method returns the array corresponding to an object, including non public members.
*
* If the deep flag is true, is will operate recursively, otherwise (if false) just at the first level.
*
* #param object $obj
* #param bool $deep = true
* #return array
* #throws \Exception
*/
public static function objectToArray(object $obj, bool $deep = true)
{
$reflectionClass = new \ReflectionClass(get_class($obj));
$array = [];
foreach ($reflectionClass->getProperties() as $property) {
$property->setAccessible(true);
$val = $property->getValue($obj);
if (true === $deep && is_object($val)) {
$val = self::objectToArray($val);
}
$array[$property->getName()] = $val;
$property->setAccessible(false);
}
return $array;
}
Example of usage, the following code:
class AA{
public $bb = null;
protected $one = 11;
}
class BB{
protected $two = 22;
}
$a = new AA();
$b = new BB();
$a->bb = $b;
var_dump($a)
Will print this:
array(2) {
["bb"] => array(1) {
["two"] => int(22)
}
["one"] => int(11)
}
There's my proposal, if you have objects in objects with even private members:
public function dismount($object) {
$reflectionClass = new \ReflectionClass(get_class($object));
$array = array();
foreach ($reflectionClass->getProperties() as $property) {
$property->setAccessible(true);
if (is_object($property->getValue($object))) {
$array[$property->getName()] = $this->dismount($property->getValue($object));
} else {
$array[$property->getName()] = $property->getValue($object);
}
$property->setAccessible(false);
}
return $array;
}
Since a lot of people find this question because of having trouble with dynamically access attributes of an object, I will just point out that you can do this in PHP: $valueRow->{"valueName"}
In context (removed HTML output for readability):
$valueRows = json_decode("{...}"); // Rows of unordered values decoded from a JSON object
foreach ($valueRows as $valueRow) {
foreach ($references as $reference) {
if (isset($valueRow->{$reference->valueName})) {
$tableHtml .= $valueRow->{$reference->valueName};
}
else {
$tableHtml .= " ";
}
}
}
I think it is a nice idea to use traits to store object-to-array converting logic. A simple example:
trait ArrayAwareTrait
{
/**
* Return list of Entity's parameters
* #return array
*/
public function toArray()
{
$props = array_flip($this->getPropertiesList());
return array_map(
function ($item) {
if ($item instanceof \DateTime) {
return $item->format(DATE_ATOM);
}
return $item;
},
array_filter(get_object_vars($this), function ($key) use ($props) {
return array_key_exists($key, $props);
}, ARRAY_FILTER_USE_KEY)
);
}
/**
* #return array
*/
protected function getPropertiesList()
{
if (method_exists($this, '__sleep')) {
return $this->__sleep();
}
if (defined('static::PROPERTIES')) {
return static::PROPERTIES;
}
return [];
}
}
class OrderResponse
{
use ArrayAwareTrait;
const PROP_ORDER_ID = 'orderId';
const PROP_TITLE = 'title';
const PROP_QUANTITY = 'quantity';
const PROP_BUYER_USERNAME = 'buyerUsername';
const PROP_COST_VALUE = 'costValue';
const PROP_ADDRESS = 'address';
private $orderId;
private $title;
private $quantity;
private $buyerUsername;
private $costValue;
private $address;
/**
* #param $orderId
* #param $title
* #param $quantity
* #param $buyerUsername
* #param $costValue
* #param $address
*/
public function __construct(
$orderId,
$title,
$quantity,
$buyerUsername,
$costValue,
$address
) {
$this->orderId = $orderId;
$this->title = $title;
$this->quantity = $quantity;
$this->buyerUsername = $buyerUsername;
$this->costValue = $costValue;
$this->address = $address;
}
/**
* #inheritDoc
*/
public function __sleep()
{
return [
static::PROP_ORDER_ID,
static::PROP_TITLE,
static::PROP_QUANTITY,
static::PROP_BUYER_USERNAME,
static::PROP_COST_VALUE,
static::PROP_ADDRESS,
];
}
/**
* #return mixed
*/
public function getOrderId()
{
return $this->orderId;
}
/**
* #return mixed
*/
public function getTitle()
{
return $this->title;
}
/**
* #return mixed
*/
public function getQuantity()
{
return $this->quantity;
}
/**
* #return mixed
*/
public function getBuyerUsername()
{
return $this->buyerUsername;
}
/**
* #return mixed
*/
public function getCostValue()
{
return $this->costValue;
}
/**
* #return string
*/
public function getAddress()
{
return $this->address;
}
}
$orderResponse = new OrderResponse(...);
var_dump($orderResponse->toArray());
$Menu = new Admin_Model_DbTable_Menu();
$row = $Menu->fetchRow($Menu->select()->where('id = ?', $id));
$Addmenu = new Admin_Form_Addmenu();
$Addmenu->populate($row->toArray());
Here I've made an objectToArray() method, which also works with recursive objects, like when $objectA contains $objectB which points again to $objectA.
Additionally I've restricted the output to public properties using ReflectionClass. Get rid of it, if you don't need it.
/**
* Converts given object to array, recursively.
* Just outputs public properties.
*
* #param object|array $object
* #return array|string
*/
protected function objectToArray($object) {
if (in_array($object, $this->usedObjects, TRUE)) {
return '**recursive**';
}
if (is_array($object) || is_object($object)) {
if (is_object($object)) {
$this->usedObjects[] = $object;
}
$result = array();
$reflectorClass = new \ReflectionClass(get_class($this));
foreach ($object as $key => $value) {
if ($reflectorClass->hasProperty($key) && $reflectorClass->getProperty($key)->isPublic()) {
$result[$key] = $this->objectToArray($value);
}
}
return $result;
}
return $object;
}
To identify already used objects, I am using a protected property in this (abstract) class, named $this->usedObjects. If a recursive nested object is found, it will be replaced by the string **recursive**. Otherwise it would fail in because of infinite loop.
By using typecasting you can resolve your problem.
Just add the following lines to your return object:
$arrObj = array(yourReturnedObject);
You can also add a new key and value pair to it by using:
$arrObj['key'] = value;

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

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

Categories