I need to do fast lookups to find if an array exists in an array. If I knew the depth of the array It would be easy - and fast!
$heystack['lev1']['lev2']['lev3'] = 10; // $heystack stores 10,000s of arrays like this
if(isset($heystack[$var1][$var2][$var3])) do something...
How would you do this dynamically if you don't know the depth? looping and searching at each level will be too slow for my application.
Your question has already the answer:
if (isset($heystack[$var1][$var2][$var3]))
{
# do something...
}
If you don't know the how many $var1 ... $varN you have, you can only do it dynamically which involves either looping or eval and depends if you need to deal with string or numerical keys. This has been already asked and answered:
Loop and Eval: use strings to access (potentially large) multidimensional arrays (and that's only one of the many)
If you are concerned about speed, e.g. if the array is always the same but you need to query it often, create a index first that has compound keys so you can more easily query it. That could be either done by storing all keys while traversing the array recursively:
class CompoundKeys extends RecursiveIteratorIterator
{
private $keys;
private $separator;
public function __construct($separator, RecursiveIterator $iterator, $mode = RecursiveIteratorIterator::SELF_FIRST, $flags = 0)
{
$this->separator = $separator;
parent::__construct($iterator, $mode, $flags);
}
public function current()
{
$current = parent::current();
if (is_array($current))
{
$current = array_keys($current);
}
return $current;
}
public function key()
{
$depth = $this->getDepth();
$this->keys[$depth] = parent::key();
return implode('.', array_slice($this->keys, 0, $depth+1));
}
}
Usage:
$it = new CompoundKeys('.', new RecursiveArrayIterator($array));
$compound = iterator_to_array($it, 1);
isset($compound["$var1.$var2.$var3"]);
Alternatively this can be done by traversing recursively and referencing the original arrays values:
/**
* create an array of compound array keys aliasing the non-array values
* of the original array.
*
* #param string $separator
* #param array $array
* #return array
*/
function array_compound_key_alias(array &$array, $separator = '.')
{
$index = array();
foreach($array as $key => &$value)
{
if (is_string($key) && FALSE !== strpos($key, $separator))
{
throw new InvalidArgumentException(sprintf('Array contains key ("%s") with separator ("%s").', $key, $separator));
}
if (is_array($value))
{
$subindex = array_compound_key_alias($value, $separator);
foreach($subindex as $subkey => &$subvalue)
{
$index[$key.$separator.$subkey] = &$subvalue;
}
}
else
{
$index[$key] = &$value;
}
}
return $index;
}
Usage:
$index = array_compound_key_alias($array);
isset($index["$var1.$var2.$var3"]);
You'll need some sort of looping but you won't need to traverse the entire depth. You can simply use a function that does the equivalent of $heystack[$var1][$var2][$var3], but dynamically:
$heystack['lev1']['lev2']['lev3'] = 10;
echo getElement($heystack, array('lev1', 'lev2', 'lev3')); // you could build second parameter dynamically
function getElement($array, $indexes = array())
{
foreach ($indexes as $index) {
$array = $array[$index];
}
return $array;
}
// output: 10
You'll need to put in some defense mechanisms to make the function more robust (for elements/indexes that don't exist) but this is the basic approach.
Related
I have built a web scraper that recursively gets all URLs from a specific website and stores them in an array
Example below:
$array = [
'http://site.test',
'http://site.test/blog',
'http://site.test/blog/blog1',
'http://site.test/blog/blog2',
'http://site.test/services',
'http://site.test/services/service1',
'http://site.test/services/service2',
'http://site.test/services/service2/sub-service',
'http://site.test/product',
'http://site.test/product/product1',
'http://site.test/product/product1',
];
I am looking for some sort of way to organise this array into a multidimensional array so that I can see what pages are child pages and of which section something like the below structure
ie:
Home
----blog
--------article1
--------article2
----services
--------service1
--------service2
------------sub-service1
-----product
--------product1
--------product2
I have tried looping through and extracting certain segments of each string but cannot seem to get the desired result.
Ideally I would like to have the result in an array or even in displayed in a multi-level list for display purposes.
Any guidance would be much appreciated!
Let's try) we have an array of links
$array = [
'http://site.test',
'http://site.test/blog',
'http://site.test/blog/blog1',
'http://site.test/blog/blog2',
'http://site.test/services',
'http://site.test/services/service1',
'http://site.test/services/service2',
'http://site.test/services/service2/sub-service',
'http://site.test/product',
'http://site.test/product/product1',
'http://site.test/product/product2',
];
For creating a tree we should create the Node class
class Node
{
private array $childNodes;
private string $name;
public function __construct(string $name)
{
$this->name = $name;
$this->childNodes = [];
}
public function getName(): string
{
return $this->name;
}
public function addChildNode(Node $node): void
{
$this->childNodes[$node->getName()] = $node;
}
public function hasChildNode(string $name): bool
{
return array_key_exists($name, $this->childNodes);
}
public function getChildNode(string $name): Node
{
return $this->childNodes[$name];
}
public function getChildNodes(): array
{
return $this->childNodes;
}
}
And Tree class, that used Node class.
Method appendUrl parses URL and builds nodes chain.
class Tree
{
private Node $head;
public function __construct()
{
$this->head = new Node('Head');
}
public function getHead(): Node
{
return $this->head;
}
public function appendUrl(string $url): void
{
$parsedUrl = parse_url($url);
$uri = sprintf('%s//%s', $parsedUrl['scheme'], $parsedUrl['host']);
$keys = array_filter(explode('/', $parsedUrl['path'] ?? ''));
$keys = [$uri, ...$keys];
$node = $this->head;
foreach ($keys as $key) {
if (!$node->hasChildNode($key)) {
$prevNode = $node;
$node = new Node($key);
$prevNode->addChildNode($node);
} else {
$node = $node->getChildNode($key);
}
}
}
}
Now we create ConsoleTreeDrawer class that draw our tree to console
class ConsoleTreeDrawer
{
public function draw(Tree $tree): void
{
$node = $tree->getHead();
$this->drawNode($node);
}
private function drawNode(Node $node, int $level = 1): void
{
$prefix = implode('', array_fill(0, 2 * $level, '-'));
print("{$prefix}{$node->getName()}\n");
foreach ($node->getChildNodes() as $childNode) {
$this->drawNode($childNode, $level + 1);
}
}
}
And let`s use our classes
$tree = new Tree();
foreach ($array as $url) {
$tree->appendUrl($url);
}
$drawer = new ConsoleTreeDrawer();
$drawer->draw($tree);
And we drew the tree
--Head
----http//site.test
------blog
--------blog1
--------blog2
------services
--------service1
--------service2
----------sub-service
------product
--------product1
Algorithm:
Remove the prefix http:// for now as it is useless for our requirement. You can add it later on again.
Next is to sort all the elements using usort. Here, based on length obtained from exploding based on /.
Now, we can be assured that all parents are before child in the array.
Next is to assign version number/rank to each link. Naming is as follows:
'http://site.test' => 1
'http://site.test/blog' => 1.1
'http://site.test/services' => 1.2
'http://site.test/blog/blog1' => 1.1.1
Above is the strategy in which version numbers will be assigned.
Now, we just need to sort the array based on this version numbers using uasort
and you are done.
Snippet:
<?php
$array = [
'http://site.test',
'http://site.test/blog',
'http://site.test/blog/blog1',
'http://site.test/blog/blog2',
'http://site.test/services',
'http://site.test/services/service1',
'http://site.test/services/service2',
'http://site.test/services/service2/sub-service',
'http://site.test/product',
'http://site.test/product/product1',
];
// remove http://
foreach($array as &$val){
$val = substr($val,7);
}
// sort based on length on explode done on delimiter '/'
usort($array, function($a,$b){
return count(explode("/",$a)) <=> count(explode("/",$b));
});
$ranks = [];
$child_count = [];
// assign ranks/version numbers
foreach($array as $link){
$parent = getParent($link);
if(!isset($ranks[$parent])){
$ranks[$link] = 1;
}else{
$child_count[$parent]++;
$ranks[$link] = $ranks[$parent] . "." . $child_count[$parent];
}
$child_count[$link] = 0;
}
function getParent($link){
$link = explode("/",$link);
array_pop($link);
return implode("/",$link);
}
// sort based on version numbers
uasort($ranks,function($a,$b){
$version1 = explode(".", $a);
$version2 = explode(".", $b);
foreach($version1 as $index => $v_num){
if(!isset($version2[$index])) return 1;
$aa = intval($v_num);
$bb = intval($version2[$index]);
if($aa < $bb) return -1;
if($bb < $aa) return 1;
}
return count($version1) <=> count($version2);
});
// get the actual product links that were made as keys
$array = array_keys($ranks);
print_r($array);// now you can attach back http:// prefix if you like
Note: Current algorithm removes duplicates as well as there is no point in keeping them.
#Update:
Since you need a multidimensional hierarchical array, we can keep track of parent and child array link references and insert children into their respective parents.
<?php
$array = [
'http://site.test',
'http://site.test/blog',
'http://site.test/blog/blog1',
'http://site.test/blog/blog2',
'http://site.test/services',
'http://site.test/services/service1',
'http://site.test/services/service2',
'http://site.test/services/service2/sub-service',
'http://site.test/product',
'http://site.test/product/product1',
];
foreach($array as &$val){
$val = substr($val,7);
}
usort($array, function($a,$b){
return count(explode("/",$a)) <=> count(explode("/",$b));
});
$hier = [];
$set = [];
foreach($array as $link){
$parent = getParent($link);
if(!isset($set[$parent])){
$hier[$link] = [];
$set[$link] = &$hier[$link];
}else{
$parent_array = &$set[$parent];
$parent_array[$link] = [];
$set[$link] = &$parent_array[$link];
}
}
function getParent($link){
$link = explode("/",$link);
array_pop($link);
return implode("/",$link);
}
print_r($hier);
This question already has answers here:
How to use return inside a recursive function in PHP
(4 answers)
Closed 9 months ago.
So I have this (simple) method:
/**
* #param $needle
* #param $haystack
*
* #return array
*/
public function recursiveArraySearch($needle, $haystack)
{
$array = false;
foreach ($haystack as $key => $value) {
if ($key === $needle) {
$array = $value;
} elseif (is_array($value)) {
$this->recursiveArraySearch($needle, $value);
}
}
return $array;
}
Which is called like so:
$result = $this->recursiveArraySearch('some_index', $configArray);
It am having trouble return it once and for all back to $result`.
If the $needle matches the $key then I just want it to return the value but at the moment it's returning to itself.
Something I haven't actually done yet.
Thanks
UPDATE: When I return the method as the answers suggest and it reached the end of an array node (like a binary tree search) it passes a string in as the $haystack and thus return false.
Data Structure:
I may want to get the values of key circled red or I may want the values of the key circled orange?
The function needs to return them of false.
you can do this
public function recursiveArraySearch($needle, $haystack)
{
foreach ($haystack as $key => $value) {
if ($key === $needle) {
return $value;
} elseif (is_array($value)) {
$check = $this->recursiveArraySearch($needle, $value);
if($check)
return $check;
}
}
return false;
}
public function recursiveArraySearch($needle, $haystack)
{
foreach ($haystack as $key => $value) {
if ($key === $needle) {
return $value;
} elseif (is_array($value)) {
$result = $this->recursiveArraySearch($needle, $value);
if ($result !== false){
return $result;
}
}
}
return false;
}
When you recurse down you need to check the result and return only if an item was found. If nothing was found then you need to let the loop continue.
This assumes that your array does not contain any boolean values. If it does, you'll need to use an alternate method to avoid confusing a false value for not found.
I edited this answer to fit your needs.
function findKey($array, $keySearch)
{
foreach ($array as $key => $item) {
if ($key == $keySearch) {
return $item;
}
else {
if (is_array($item)) {
$keyFound = findKey($item, $keySearch);
if( $keyFound != false ) {
return $keyFound;
}
}
}
}
return false;
}
A number of problems here. First and foremost you are not assigning the data returned from the recursive call to any kind of data structure. Also, you should be doing a better job of checking edge conditions. Finally, if your Doc Block says that array is returned, you need to 100% make sure you are returning an array. That is the contract you are making with the caller when they read the documentation on this method, so you should adhere to that.
The example below assumes you are just going to return a numerically indexed array of values to the initial caller. This example includes a merge of recursive results to active array, better handling around input validation, and the consistent return of a numerically-indexed array (with empty array signifying no results).
/**
* #param mixed $needle Integer or string key value used for recursive search.
* #param array $haystack Array to be searched.
*
* #throws InvalidArgumentException
*
* #return array Return numerically-indexed array with empty array if no match.
*/
public function recursiveArraySearch($needle, array $haystack)
{
// validate that we have a proper needle passed
if(!is_int($needle) && !is_string($needle)) {
throw new InvalidArgumentException(
'Invalid search needle type passed as argument. ' .
"Integer or string value expected. Value passed:\n" .
var_export($needle, true)
);
}
$array = [];
foreach ($haystack as $key => $value) {
// recursively search if $value is non-empty array
if(is_array($value) && !empty($value)) {
array_merge($array, $this->recursiveArraySearch($needle, $value));
}
// otherwise, we can make exact string/integer comparison
else if ($key === $needle) {
$array[] = $value;
}
}
return $array;
}
Note here that I am assuming you are looking for all matches in the recursive structure. If you are looking for the first match, you can do something like the following, which is a breadth-first search.
/**
* #param mixed $needle Integer or string key value used for recursive search.
* #param array $haystack Array to be searched.
*
* #throws InvalidArgumentException
*
* #return mixed Return values could be mixed since we have no constraint on
* value types in haystack. Null will be returned on no match, thus
* this function cannot differentiate explicitly defined null values
* from no match.
*/
public function recursiveBreadthFirstSingleMatchArraySearch($needle, array $haystack)
{
// validate that we have a proper needle passed
if(!is_int($needle) && !is_string($needle)) {
throw new InvalidArgumentException(
'Invalid search needle type passed as argument. ' .
"Integer or string value expected. Value passed:\n" .
var_export($needle, true)
);
}
// see if there is key match at first level of array
if(array_key_exists($needle, $haystack)) {
return $haystack[$needle];
}
// iterate through haystack performing recursive search on array until match
foreach ($haystack as $key => $value) {
// recursively search if $value is non-empty array
if(is_array($value) && !empty($value)) {
$result = $this->
recursiveBreadthFirstSingleMatchArraySearch($needle, $value));
if (!is_null($result)) {
return $result;
}
}
}
return null;
}
I am trying to figure out the best way to use dot notation when passing in a key or set of keys into a function and getting that post value.
Example
shipping.first_name
What it looks like in actual $_POST array:
$_POST[shipping][first_name] = 'some value'
I would like to be able to pass in (as a parameter) the string, and have the function return the post value.
function get_post($str = NULL){
return $_POST[$key1][$key1]..etc.
}
Current attempt (working as intended, but need to put into $_POST):
From: SO Question
function assignArrayByPath(&$arr, $path) {
$keys = explode('.', $path);
while ($key = array_shift($keys)) {
$arr = &$arr[$key];
}
}
$output = array();
assignArrayByPath($output, $str);
This produces an array of:
Array ( [shipping] => Array ( [first_name] => ) )
I would like then to do something like this:
return isset($_POST.$output) ? true : false;
So how do I take that array created from the period separated string and check if it exists in POST?
I think this might be a duplicate, but I am not positive. I apologize in advance if it is. Any help is much appreciated.
See Laravel array_set implement http://laravel.com/api/source-function-array_set.html#319
/**
* Set an array item to a given value using "dot" notation.
*
* If no key is given to the method, the entire array will be replaced.
*
* #param array $array
* #param string $key
* #param mixed $value
* #return array
*/
function array_set(&$array, $key, $value)
{
if (is_null($key)) return $array = $value;
$keys = explode('.', $key);
while (count($keys) > 1)
{
$key = array_shift($keys);
// If the key doesn't exist at this depth, we will just create an empty array
// to hold the next value, allowing us to create the arrays to hold final
// values at the correct depth. Then we'll keep digging into the array.
if ( ! isset($array[$key]) || ! is_array($array[$key]))
{
$array[$key] = array();
}
$array =& $array[$key];
}
$array[array_shift($keys)] = $value;
return $array;
}
Check exists you can see array_get http://laravel.com/api/source-function-array_get.html#224
/**
* Get an item from an array using "dot" notation.
*
* #param array $array
* #param string $key
* #param mixed $default
* #return mixed
*/
function array_get($array, $key, $default = null)
{
if (is_null($key)) return $array;
if (isset($array[$key])) return $array[$key];
foreach (explode('.', $key) as $segment)
{
if ( ! is_array($array) || ! array_key_exists($segment, $array))
{
return value($default);
}
$array = $array[$segment];
}
return $array;
}
I have a function that searches a multidimensional array for a key, and returns the path
inside the array to my desired key as a string.
Is there any way I can use this string in php to reach this place in my original array, not to get to the value but to make changes to this specific bracnch of the array?
An example:
$array = array('first_level'=>array(
'second_level'=>array(
'desired_key'=>'value')));
in this example the function will return the string:
'first_level=>second_level=>desired_key'
Is there a way to use this output, or format it differently in order to use it in the following or a similar way?
$res = find_deep_key($array,'needle');
$array[$res]['newkey'] = 'injected value';
Thanks
If the keys path is safe (e.g. not given by the user), you can use eval and do something like:
$k = 'first_level=>second_level=>desired_key';
$k = explode('=>', $k);
$keys = '[\'' . implode('\'][\'', $k) . '\']';
eval('$key = &$array' . $keys . ';');
var_dump($key);
I think you want to do a recursive search in the array for your key? Correct me if i am wrong.
Try this
function recursive_array_search($needle,$haystack) {
foreach($haystack as $key=>$value) {
$current_key=$key;
if($needle===$value OR (is_array($value) && recursive_array_search($needle,$value) !== false)) {
return $current_key;
}
}
return false;
}
Taken from here http://in3.php.net/manual/en/function.array-search.php#91365
You need something like:
find_key_in_array($key, $array, function($foundValue){
// do stuff here with found value, e.g.
return $foundValue * 2;
});
and the implementation would be something like:
function find_key_in_array($key, $array, $callback){
// iterate over array fields recursively till you find desired field, then:
...
$array[$key] = $callback($array[$key]);
}
If you need to append some new sub-array into multidimensional complex array and you know where exactly it should be appended (you have path as a string), this might work (another approach without eval()):
function append_to_subarray_by_path($newkey, $newvalue, $path, $pathDelimiter, &$array) {
$destinationArray = &$array;
foreach (explode($pathDelimiter, $path) as $key) {
if (isset($destinationArray[$key])) {
$destinationArray = &$destinationArray[$key];
} else {
$destinationArray[$newkey] = $newvalue;
}
}
}
$res = find_deep_key($array,'needle');
append_to_subarray_by_path('newkey', 'injected value', $res, '=>', $array);
Of course, it will work only if all keys in path already exist. Otherwise it will append new sub-array into wrong place.
just write a function that takes the string and the array. The function will take the key for each array level and then returns the found object.
such as:
void object FindArray(Array[] array,String key)
{
if(key.Length == 0) return array;
var currentKey = key.Split('=>')[0];
return FindArray(array[currentKey], key.Remove(currentKey));
}
Is there a way to convert a multidimensional array to a stdClass object in PHP?
Casting as (object) doesn't seem to work recursively. json_decode(json_encode($array)) produces the result I'm looking for, but there has to be a better way...
As far as I can tell, there is no prebuilt solution for this, so you can just roll your own:
function array_to_object($array) {
$obj = new stdClass();
foreach ($array as $k => $v) {
if (strlen($k)) {
if (is_array($v)) {
$obj->{$k} = array_to_object($v); //RECURSION
} else {
$obj->{$k} = $v;
}
}
}
return $obj;
}
I know this answer is coming late but I'll post it for anyone who's looking for a solution.
Instead of all this looping etc, you can use PHP's native json_* function. I've got a couple of handy functions that I use a lot
/**
* Convert an array into a stdClass()
*
* #param array $array The array we want to convert
*
* #return object
*/
function arrayToObject($array)
{
// First we convert the array to a json string
$json = json_encode($array);
// The we convert the json string to a stdClass()
$object = json_decode($json);
return $object;
}
/**
* Convert a object to an array
*
* #param object $object The object we want to convert
*
* #return array
*/
function objectToArray($object)
{
// First we convert the object into a json string
$json = json_encode($object);
// Then we convert the json string to an array
$array = json_decode($json, true);
return $array;
}
Hope this can be helpful
You and many others have pointed to the JSON built-in functions, json_decode() and json_encode(). The method which you have mentioned works, but not completely: it won't convert indexed arrays to objects, and they will remain as indexed arrays. However, there is a trick to overcome this problem. You can use JSON_FORCE_OBJECT constant:
// Converts an array to an object recursively
$object = json_decode(json_encode($array, JSON_FORCE_OBJECT));
Tip: Also, as mentioned here, you can convert an object to array recursively using JSON functions:
// Converts an object to an array recursively
$array = json_decode(json_encode($object), true));
Important Note: If you do care about performance, do not use this method. While it is short and clean, but it is the slowest among alternatives. See my other answer in this thread relating this.
function toObject($array) {
$obj = new stdClass();
foreach ($array as $key => $val) {
$obj->$key = is_array($val) ? toObject($val) : $val;
}
return $obj;
}
You can use the array_map recursively:
public static function _arrayToObject($array) {
return is_array($array) ? (object) array_map([__CLASS__, __METHOD__], $array) : $array;
}
Works perfect for me since it doesn't cast for example Carbon objects to a basic stdClass (which the json encode/decode does)
/**
* Recursively converts associative arrays to stdClass while keeping integer keys subarrays as arrays
* (lists of scalar values or collection of objects).
*/
function a2o( array $array ) {
$resultObj = new \stdClass;
$resultArr = array();
$hasIntKeys = false;
$hasStrKeys = false;
foreach ( $array as $k => $v ) {
if ( !$hasIntKeys ) {
$hasIntKeys = is_int( $k );
}
if ( !$hasStrKeys ) {
$hasStrKeys = is_string( $k );
}
if ( $hasIntKeys && $hasStrKeys ) {
$e = new \Exception( 'Current level has both integer and string keys, thus it is impossible to keep array or convert to object' );
$e->vars = array( 'level' => $array );
throw $e;
}
if ( $hasStrKeys ) {
$resultObj->{$k} = is_array( $v ) ? a2o( $v ) : $v;
} else {
$resultArr[$k] = is_array( $v ) ? a2o( $v ) : $v;
}
}
return ($hasStrKeys) ? $resultObj : $resultArr;
}
Some of the other solutions posted here fail to tell apart sequential arrays (what would be [] in JS) from maps ({} in JS.) For many use cases it's important to tell apart PHP arrays that have all sequential numeric keys, which should be left as such, from PHP arrays that have no numeric keys, which should be converted to objects. (My solutions below are undefined for arrays that don't fall in the above two categories.)
The json_decode(json_encode($x)) method does handle the two types correctly, but is not the fastest solution. It's still decent though, totaling 25µs per run on my sample data (averaged over 1M runs, minus the loop overhead.)
I benchmarked a couple of variations of the recursive converter and ended up with the following. It rebuilds all arrays and objects (performing a deep copy) but seems to be faster than alternative solutions that modify the arrays in place. It clocks at 11µs per execution on my sample data:
function array_to_object($x) {
if (!is_array($x)) {
return $x;
} elseif (is_numeric(key($x))) {
return array_map(__FUNCTION__, $x);
} else {
return (object) array_map(__FUNCTION__, $x);
}
}
Here is an in-place version. It may be faster on some large input data where only small parts need to be converted, but on my sample data it took 15µs per execution:
function array_to_object_inplace(&$x) {
if (!is_array($x)) {
return;
}
array_walk($x, __FUNCTION__);
reset($x);
if (!is_numeric(key($x))) {
$x = (object) $x;
}
}
I did not try out solutions using array_walk_recursive()
public static function _arrayToObject($array) {
$json = json_encode($array);
$object = json_decode($json);
return $object
}
Because the performance is mentioned, and in fact it should be important in many places, I tried to benchmark functions answered here.
You can see the code and sample data here in this gist. The results are tested with the data exists there (a random JSON file, around 200 KB in size), and each function repeated one thousand times, for the results to be more accurate.
Here are the results for different PHP configurations:
PHP 7.4.16 (no JIT)
$ php -dopcache.enable_cli=1 benchmark.php
pureRecursive(): Completed in 0.000560s
pureRecursivePreservingIntKeys(): Completed in 0.000580s
jsonEncode(): Completed in 0.002045s
jsonEncodeOptimized(): Completed in 0.002060s
jsonEncodeForceObject(): Completed in 0.002174s
arrayMap(): Completed in 0.000561s
arrayMapPreservingIntKeys(): Completed in 0.000592s
arrayWalkInplaceWrapper(): Completed in 0.001016s
PHP 8.0.2 (no JIT)
$ php -dopcache.enable_cli=1 benchmark.php
pureRecursive(): Completed in 0.000535s
pureRecursivePreservingIntKeys(): Completed in 0.000578s
jsonEncode(): Completed in 0.001991s
jsonEncodeOptimized(): Completed in 0.001990s
jsonEncodeForceObject(): Completed in 0.002164s
arrayMap(): Completed in 0.000579s
arrayMapPreservingIntKeys(): Completed in 0.000615s
arrayWalkInplaceWrapper(): Completed in 0.001040s
PHP 8.0.2 (tracing JIT)
$ php -dopcache.enable_cli=1 -dopcache.jit_buffer_size=250M -dopcache.jit=tracing benchmark.php
pureRecursive(): Completed in 0.000422s
pureRecursivePreservingIntKeys(): Completed in 0.000410s
jsonEncode(): Completed in 0.002004s
jsonEncodeOptimized(): Completed in 0.001997s
jsonEncodeForceObject(): Completed in 0.002094s
arrayMap(): Completed in 0.000577s
arrayMapPreservingIntKeys(): Completed in 0.000593s
arrayWalkInplaceWrapper(): Completed in 0.001012s
As you see, the fastest method with this benchmark is pure recursive PHP functions (posted by #JacobRelkin and #DmitriySintsov), especially when it comes to the JIT compiler. When it comes to json_* functions, they are the slowest ones. They are about 3x-4x (in the case of JIT, 5x) slower than the pure method, which may seem unbelievable.
One thing to note: If you remove iterations (i.e. run each function only one time), or even strictly lower its count, the results would differ. In such cases, arrayMap*() variants win over pureRecursive*() ones (still json_* functions method should be the slowest). But, you should simply ignore these cases. In the terms of performance, scalability is much more important.
As a result, in the case of converting arrays to object (and vice versa?), you should always use pure PHP functions, resulting in the best performance, perhaps independent from your configurations.
The simpliest way to convert an associative array to object is:
First encode it in json, then decode it.
like $objectArray = json_decode(json_encode($associtiveArray));
Here's a function to do an in-place deep array-to-object conversion that uses PHP internal (shallow) array-to-object type casting mechanism.
It creates new objects only when necessary, minimizing data duplication.
function toObject($array) {
foreach ($array as $key=>$value)
if (is_array($value))
$array[$key] = toObject($value);
return (object)$array;
}
Warning - do not use this code if there is a risk of having circular references.
Here is a smooth way to do it that can handle an associative array with great depth and doesn't overwrite object properties that are not in the array.
<?php
function setPropsViaArray( $a, $o )
{
foreach ( $a as $k => $v )
{
if ( is_array( $v ) )
{
$o->{$k} = setPropsViaArray( $v, ! empty ( $o->{$k} ) ? $o->{$k} : new stdClass() );
}
else
{
$o->{$k} = $v;
}
}
return $o;
};
setPropsViaArray( $newArrayData, $existingObject );
Late, but just wanted to mention that you can use the JSON encoding/decoding to convert fully from/to array:
//convert object $object into array
$array = json_decode(json_encode($object), true);
//convert array $array into object
$object = json_decode(json_encode($array));
json_encode and json_decode functions are available starting from php 5.2
EDIT: This function is conversion from object to array.
From https://forrst.com/posts/PHP_Recursive_Object_to_Array_good_for_handling-0ka
protected function object_to_array($obj)
{
$arrObj = is_object($obj) ? get_object_vars($obj) : $obj;
foreach ($arrObj as $key => $val) {
$val = (is_array($val) || is_object($val)) ? $this->object_to_array($val) : $val;
$arr[$key] = $val;
}
return $arr;
}
I was looking for a way that acts like json_decode(json_encode($array))
The problem with most other recursive functions here is that they also convert sequential arrays into objects. However, the JSON variant does not do this by default. It only converts associative arrays into objects.
The following implementation works for me like the JSON variant:
function is_array_assoc ($arr) {
if (!is_array($arr)) return false;
foreach (array_keys($arr) as $k => $v) if ($k !== $v) return true;
return false;
}
// json_decode(json_encode($array))
function array_to_object ($arr) {
if (!is_array($arr) && !is_object($arr)) return $arr;
$arr = array_map(__FUNCTION__, (array)$arr);
return is_array_assoc($arr) ? (object)$arr : $arr;
}
// json_decode(json_encode($array, true))
// json_decode(json_encode($array, JSON_OBJECT_AS_ARRAY))
function object_to_array ($obj) {
if (!is_object($obj) && !is_array($obj)) return $obj;
return array_map(__FUNCTION__, (array)$obj);
}
If you want to have the functions as a class:
class ArrayUtils {
public static function isArrAssoc ($arr) {
if (!is_array($arr)) return false;
foreach (array_keys($arr) as $k => $v) if ($k !== $v) return true;
return false;
}
// json_decode(json_encode($array))
public static function arrToObj ($arr) {
if (!is_array($arr) && !is_object($arr)) return $arr;
$arr = array_map([__CLASS__, __METHOD__], (array)$arr);
return self::isArrAssoc($arr) ? (object)$arr : $arr;
}
// json_decode(json_encode($array, true))
// json_decode(json_encode($array, JSON_OBJECT_AS_ARRAY))
public static function objToArr ($obj) {
if (!is_object($obj) && !is_array($obj)) return $obj;
return array_map([__CLASS__, __METHOD__], (array)$obj);
}
}
If anyone finds any mistakes please let me know.
/**
* Convert a multidimensional array to an object recursively.
* For any arrays inside another array, the result will be an array of objects.
*
* #author Marcos Freitas
* #param array|any $props
* #return array|any
*/
function array_to_object($props, $preserve_array_indexes = false) {
$obj = new \stdClass();
if (!is_array($props)) {
return $props;
}
foreach($props as $key => $value) {
if (is_numeric($key) && !$preserve_array_indexes) {
if(!is_array($obj)) {
$obj = [];
}
$obj[] = $this->array_to_object($value);
continue;
}
$obj->{$key} = is_array($value) ? $this->array_to_object($value) : $value;
}
return $obj;
}
The shortest I could come up with:
array_walk_recursive($obj, function (&$val) { if (is_object($val)) $val = get_object_vars($val); });