Why is the array empty until accessed in a loop? - php

I'm just digging into a Laravel application and have come across the following function. This is called as part of the routing process (which is what I'm trying to figure out) and I've come across a strange situation where some kind of magic is happening. When the function is called, the $array contains an array of strings. But when I dump out the array at certain points which can be seen below, it's been totally transformed?
/**
* Return the first element in an array passing a given truth test.
*
* #param array $array
* #param callable|null $callback
* #param mixed $default
* #return mixed
*/
public static function first($array, callable $callback = null, $default = null)
{
if (is_null($callback)) {
if (empty($array)) {
return value($default);
}
foreach ($array as $item) {
return $item;
}
}
Array Is empty here
dd($array) // []
foreach ($array as $key => $value) {
Array now contains an object
dd($array);
if (call_user_func($callback, $value, $key)) {
return $value;
}
}
return value($default);
}
Any advice as to what I should be reading up on would be great!
Thanks

Related

How to re-code PHP assign-by-reference usage

My deprecation checker is throwing this error:
Using deprecated language feature assign by reference(&=) Since PHP 5.3 use normal assignment instead.
So, I am trying to figure out how to re-code the methods in this class to not use by reference or at least use it properly (if it is allowed at all - which I'm not clear on either).
below is the portion of the class using by reference. The entire class is here, the test and the deprecation checker log is here.
I would like some help recoding the class to remove the use of by reference
class ParameterBag
{
/**
* Sets value.
* can use 'key' = ['subkey' => value, 'subkey2' => value2]
* or
* 'key.subkey' = value
* 'key.subkey2' = value2
*
* #param $key
* #param $value
*/
public function set($key, $value)
{
$parameters = &$this->resolvePath($key, true);
$key = $this->resolveKey($key);
$parameters[$key] = $value;
}
/**
* Resolves a path in parameters property and returns it as a reference.
*
* This method allows structured namespacing of parameters.
*
* #param string $key Key name
* #param boolean $writeContext Write context, default false
*
* #return array
*/
private function &resolvePath($key, $writeContext = false)
{
$array = &$this->parameters;
$key = (strpos($key, $this->ns) === 0) ? substr($key, 1) : $key;
// Check if there is anything to do, else return
if (!$key) {
return $array;
}
$parts = explode($this->ns, $key);
if (count($parts) < 2) {
if (!$writeContext) {
return $array;
}
$array[$parts[0]] = [];
return $array;
}
unset($parts[count($parts) - 1]);
foreach ($parts as $part) {
if (!array_key_exists($part, $array)) {
if (!$writeContext) {
return $array;
}
$array[$part] = [];
}
$array = &$array[$part];
}
return $array;
}
}
This appears to be a bug in the deprecation tool. According to Deprecated features in PHP 5.3.x:
Assigning the return value of new by reference is now deprecated.
Call-time pass-by-reference is now deprecated.
But assignment by reference in general is not deprecated, and Returning References says:
To use the returned reference, you must use reference assigment

how to convert object to array Symfony2

I have an object named Property, how can I convert that to array
/**
* #Route("/property/{id}/pictures/download_all", name="property_zip_files_and_download", methods={"GET"})
*/
public function zipFilesAndDownloadAction(Property $property)
{
$pictures = $property->pictures;
$compressPath = $this->get('some_service.property.picture_compress')->compress($pictures);
//some code for download...
....
}
How can I convert the pictures to array and pass it to my service? Can anyone please help me out here
What is pictures?
In simple cases you can use (array) $pictures.
Also, you can use Serializer Normalizers
if variable is Iterator (ArrayCollection or PersistentCollection for example) and service method has array as typehinting, you can convert it to simple array with iterator_to_array function.
Try:
$compressPath = $this->get('some_service.property.picture_compress')->compress(iterator_to_array($pictures));
This is how I turned my model MyObject object into a data array.
class MyObject{
public function getContentArr($objInstance, $filter=true){
$arr = array();
if($objInstance){
$response = new Response(GeneralFunctions::getKernel()->getContainer()->get('serializer')->serialize($objInstance, 'json'));
$arr = json_decode($response->getContent(), true);
//remove all items that are null, or empty string
//if($filter){
//$arr = $this->filterContentArr($arr); // optional
//}
}
return $arr;
}
/**
* Returns filtered array removing empty/null
*
* #param array $data
* #return array
*/
public function filterContentArr($data){
foreach($data as $key => $val){
if(is_array($val)){
$data[$key] = $this->filterContentArr($val);
if(empty($data[$key])){
unset($data[$key]); //remove empty array
}
}else{
if($val == "" || $val == null){
unset($data[$key]);
}
}
}
return $data;
}
}
$myObject = new MyObject();
print_r($myObject->getContentArr($myObject));

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

What's the fastest way to add a value to an array in PHP

I'm working on a project that has an Arr helper class and I am curious about something: Is there a benefit in doing this:
/**
* Sets an array value
*
* #param array $array
* #param string $path
* #param mixed $value
*
* #return void
*/
public static function set(array &$array, $path, $value)
{
$segments = explode('.', $path);
while (count($segments) > 1) {
$segment = array_shift($segments);
if ( ! isset( $array[$segment] ) || ! is_array($array[$segment])) {
$array[$segment] = [];
}
$array =& $array[$segment];
}
$array[array_shift($segments)] = $value;
}
Arr::set($data['stories'], 'fields.age', '3');
Over this:
$data['stories']['fields']['age'] = '3';
Or is there a better, faster way?
The function is just easier to type - you do not have to make so many brackets and quotation mark. It makes things easier.
For performance: If you set the array values with normal syntax, it is faster, because you do not have to explode the path or check for existence before. But this takes not so many time.

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