Convert PHP 5.3 anonymous function into 5.2 compatible function - php

I have this anonymous function $build_tree within another function that works fine in PHP 5.3
function nest_list($list) {
$index = array();
index_nodes($list, $index);
$build_tree = function(&$value, $key) use ($index, &$updated) {
if(array_key_exists($key, $index)) {
$value = $index[$key];
$updated = true;
todel($key); }
};
do {
$updated = false;
array_walk_recursive($list, $build_tree);
} while($updated);
return $list;
}
function index_nodes($nodes, &$index) {
foreach($nodes as $key => $value) {
if ($value) {
$index[$key] = $value;
index_nodes($value, $index);
}
}
}
How can I convert this into PHP 5.2 compatible code?

Generally, you could do this using an object's method (callbacks can be either a function, or an object's method; the latter allows you to maintain state). Something like this (not tested):
class BuildTree {
public $index, $updated = false;
public function __construct($index) {
$this->index = $index;
}
function foo(&$value, $key) {
if(array_key_exists($key, $this->index)) {
$value = $this-.index[$key];
$this->updated = true;
todel($key); }
}
}
do {
$build_tree_obj = new BuildTree($index);
array_walk_recursive($list, array($build_tree_obj, 'foo'));
} while($build_tree_obj->updated);
However, array_walk_recursive has a special feature that allows us to pass a third argument, which is a value that will be passed into every call of the function. Although the value is passed by value, we can cleverly use objects (reference types in PHP 5) to maintain state (from How to "flatten" a multi-dimensional array to simple one in PHP?):
$build_tree = create_function('&$value, $key, $obj', '
if(array_key_exists($key, $index)) {
$value = $index[$key];
$updated = true;
todel($key); }
');
do {
$obj = (object)array('updated' => false);
array_walk_recursive($list, $build_tree, $obj);
} while($obj->updated);

I don't think this is possible without changing the way the function is called, because there is no mechanism in PHP 5.3 for a lambda function to change a variable from the scope it is called in (in this case $updated).
You could return $updated like this:
$build_tree = create_function('&$value,$key,$updated','
$index = '.var_export($index).';
if(array_key_exists($key, $index)) {
$value = $index[$key];
$updated = true;
todel($key); }
return $updated;
');
but then you have to call it like this:
$updated = $build_tree('the value','the key',$updated);

Related

Array_walk_recursive return true or false

I have some script for long random xml and I need to find value when I know key.
I tried to use array_walk_recursive - but when I used it - I took value only when I used echo. When I used return I took only true or false...
I need to take back a variables for next processing.
Can you help me please?
class ClassName{
private $array;
private $key ;
public $value;
public $val;
function getKey($key) {
$this->key = $key;
return $key;
}
function getFind($value, $key)
{
static $i = 0;
if ($key === ($this->key)) {
$value = $value[$i];
$i++;
return $value;
}
}
}
$xml_simple = simplexml_load_file('./logs/xml_in1.xml');
$json = json_encode($xml_simple);
$array = json_decode($json, TRUE);
$obj = new ClassName();
$obj_key = 'pracovnik';
$obj->getKey($obj_key);
print_r(array_walk_recursive($array,[$obj,"getFind"]));
print_r( $obj->value);
The return value of array_walk_recursive is:
Returns true on success or false on failure.
What you might so as an idea is to use an array where you can add values to when this if clause is true:
if ($key === $this->key) {
Then you could create another method to get the result:
For example
class ClassName
{
private $key;
private $result = [];
function setKey($key) {
$this->key = $key;
}
function find($value, $key) {
if ($key === $this->key) {
$this->result[$key][] = $value;
}
}
function getResult(){
return $this->result;
}
}
$xml_simple = simplexml_load_file('./logs/xml_in1.xml');
$json = json_encode($xml_simple);
$array = json_decode($json, TRUE);
$obj = new ClassName();
$obj->setKey('pracovnik');
array_walk_recursive($array, [$obj, "find"]);
print_r($obj->getResult());
A few notes about the code that you tried:
You have a line after the return statement return $value; that will never be executed
You have declared but not using public $val; and private $array;
I think function getKey is better named setKey as you are only setting the key

Is it possible to make a unique value in PHP?

I'm making a array class and want a value to be able to be returned by a higher order function. The idea is that its a instance constant or method returned value such that I can skip the value in a map.
In other languages making an array or some compound value, like ['skip'] will make it pointer equal such that I can then use the operator for pointer equal and it will not be equal to other arrays with the exact same content, but my problem is that ['skip'] === ['skip'] is true so even with === the two values are the same.
Here is an example of usage of my code where I accedentally have the same value as I used to skip:
namespace Test;
use Common\Domain\Collection;
$arr = new Collection();
$arr[] = 1;
$arr[] = 2;
$arr[] = 3;
$arr[] = 4;
echo count($arr); // prints 4
$arr2 = $arr->map(function ($v) {
return $v % 2 == 0 ? Collection::SKIP : ["skip"];
});
echo count($arr2); // prints 0, but should be 2
Is there a way to get a unique value or work around this somehow?
Here is code that implements Collection:
namespace Common\Domain;;
class Collection implements \Iterator, \Countable, \ArrayAccess
{
const SKIP = ["skip"];
private $arr = [];
public function map(callable $fn, bool $keepKeys = false) :Collection
{
$arr = new static();
$nOrder = 0;
foreach($this->arr as $key => $value) {
$result = call_user_func($fn, $value, $key, $nOrder, $this);
if($result !== self::SKIP) {
if($keepKeys) {
$arr[$key] = $result;
} else {
$arr[] = $result;
}
}
}
return $arr;
}
// implementation of interfaces \Iterator, \Countable, \ArrayAccess
public function current()
{
return current($this->arr);
}
public function next()
{
next($this->arr);
}
public function key()
{
return key($this->arr);
}
public function valid()
{
return isset($this->arr[$this->key()]);
}
public function rewind()
{
reset($this->arr);
}
public function count()
{
return count($this->arr);
}
public function offsetExists($offset)
{
return array_key_exists($offset, $this->arr);
}
public function offsetGet($offset)
{
return $this->arr[$offset];
}
public function offsetSet($offset, $value)
{
$this->arr[$offset] = $value;
}
public function offsetUnset($offset)
{
unset($this->arr[$offset]);
}
}
I guess you are looking for Java-type enumerations, which doesn't exist in PHP. My best guess on your problem would be to use an object instead of a constant, that you would instantiate statically for a convenient use. Then, in the loop of your map function, you check the value with an instanceof instead of the basic equality operator, against the class you defined.
So, here :
class UniqueValue
{
public static function get()
{
return new self();
}
}
Then :
$arr2 = $arr->map(function ($v) {
return $v % 2 == 0 ? UniqueValue::get() : ["skip"];
});
And inside your collection :
public function map(callable $fn, bool $keepKeys = false) :Collection
{
$arr = new static();
$nOrder = 0;
foreach($this->arr as $key => $value) {
$result = call_user_func($fn, $value, $key, $nOrder, $this);
if($result ! instanceof UniqueValue) {
if($keepKeys) {
$arr[$key] = $result;
} else {
$arr[] = $result;
}
}
}
return $arr;
}
This is the quickest approach I can think of. If your array contains data from "outside" I don't think it's possible in any way that it matches against a class check from your own code.
I would solve this by implementing another method for this. The method delete would map a function over the collection and remove any elements where the function returns false.
e.g.
class Collection
{
// ...
public function delete($func)
{
$result = new static();
foreach($this->arr as $item)
{
if($func($item) !== false) $result[] = $item;
}
}
}
// example
$arr = new Collection();
$arr[] = 1;
$arr[] = 2;
$arr[] = 3;
$arr[] = 4;
echo count($arr); // prints 4
$arr2 = $arr->delete(function ($v) {
return $v % 2 ? true : false;
});
var_dump($arr2); // prints [2, 4]

Session wrapper class. Set multi dimensional array

I have a wrapper class for my $_SESSION array because I like to work with objects and it sort of prevents me from getting lazy and calling the super global from places I should not.
It used to be ok to have a simple:
public function get($name);
public function set($name, $value);
public function exists($name);
......
but now I am implementing a shopping service and it has a shopping cart and when the user adds an item to the cart it sets it like $_SESSION['cart'][$productId] which holds the quantity so as you can see my get() and set() break down.
I currently have this for my new get()
/*
* #args: ...
* #return: mixed
*/
public function get() {
$keys = func_get_args();
$value = $_SESSION[array_shift($keys)];
foreach( $keys as $key ) {
$value = $value[$key];
}
return $value;
}
// This is how I use it then
$quantity = $session->get('cart', $productId);
It works with perfectly assuming the keys being search for do exist, otherwise it gives me a warning.
The problem now is the set() method. I want to do it in a similar fashion so any amount of keys can be given in the signature of the method and then the value to store but it is proving to be very confusing for me anyway.
Does anyone know how to accomplish this?
Thanks.
This should work:
class Session{
// $...
public function get(){
$keys = func_get_args();
if(count($keys) < 1){
// handle exception
}
$value = $_SESSION[array_shift($keys)];
foreach($keys as $key){
if(!isset($value[$key])){
// handle exception
}
$value = $value[$key];
}
return $value;
}
// $valueToSet, $...
public function set(){
$data = func_get_args();
if(count($data) < 2){
// handle exception
}
$val = array_shift($data);
$value = &$_SESSION[array_shift($data)];
foreach($data as $key){
if(!isset($value[$key])){
$value[$key] = array();
}
$value = &$value[$key];
}
$value = $val;
}
// $...
public function exists(){
$keys = func_get_args();
if(count($keys) < 1){
// handle exception
}
$tmp = array_shift($keys);
if(!isset($_SESSION[$tmp])) return false;
$value = $_SESSION[$tmp];
foreach($keys as $key){
if(!isset($value[$key])){
return false;
}
$value = $value[$key];
}
return true;
}
}
I can give you a little example:
$var = 'asdf';
$var_copy = $var;
$var_copy2 = &$var;
$var_copy = 'asdf2';
echo $var; // prints 'asdf'
$var_copy2 = 'asdf2';
echo $var; // prints 'asdf2'
And a little link from php.net

PHP - target a multidimensional array element without eval()

The goal is to pass a specific array element through custom_format().
Example: If $hierarchy = '4:0:2', then $data[4][0][2] = custom_format($data[4][0][2]).
Does anyone know how to replicate the following code without relying on eval()?
Current code:
$hierarchy = '4:0:2';
$hierarchy = str_replace(':', '][', $hierarchy);
eval("\$data[$hierarchy] = custom_format(\$data[$hierarchy]);");
Thanks in advance.
An overly verbose yet elegant option is the following:
class MyArray implements ArrayAccess {
public function offsetExists($offset) {
if(!is_array($offset))
$offset = explode(':', $value);
$key = array_shift($offset);
if($key !== NULL) {
if($this->$key InstanceOf MyArray) {
return(isset($this->$key[$offset]));
}
}
}
public function offsetGet($offset) {
if(!is_array($offset))
$offset = explode(':', $value);
$key = array_shift($offset);
if($key !== NULL) {
if($this->$key InstanceOf MyArray) {
return($this->$key[$offset]);
}
}
}
public function offsetSet($offset, $value) {
if(!is_array($offset))
$offset = explode(':', $value);
$key = array_shift($offset);
if($key !== NULL) {
if(!($this->$key InstanceOf MyArray)) {
$this->$key = new MyArray;
}
$this->$key[$offset] = $value;
}
}
public function offsetUnset($offset) {
if(!is_array($offset))
$offset = explode(':', $value);
$key = array_shift($offset);
if($key !== NULL) {
if($this->$key InstanceOf MyArray) {
return(unset($this->$key[$offset]));
}
if(count($offset) == 0) {
return(unset($this->$key));
}
}
}
}
This does imply using MyArray everywhere you need this kind of array behaviour and perhaps creating a static method that recursively converts arrays and they array children into MyArray objects so that they will respond consistently to this behavior.
One concrete example is the need to change the offsetGet method, to check if $value is an array then to use the conversion function to convert it to a MyArray if you want to access its elements.
How about something like this:
<?php
$hierarchy = '4:0:2';
list($a,$b,$c) = explode(':',$hierarchy);
echo $data[$a][$b][$c];
?>

PHP Is there anyway I can simulate an array operator on the return value of a function [duplicate]

This question already exists:
Closed 11 years ago.
Possible Duplicate:
Any way to access array directly after method call?
In C# and other languages, I can do something like this
$value = $obj->getArray()[0];
But not in PHP. Any workarounds or am I doomed to do this all the time?
$array = $obj->getArray();
$value = $array[0];
No, you can't do it without using array_shift (Which only gets the first element). If you want to access the third or fourth, most likely you'd want to do a function like this:
function elemnt($array, $element)
{
return $array[$element];
}
$value = element($obj->getArray(), 4);
Also, see this question, as it is an exact duplicate: Any way to access array directly after method call?
I think you are doomed to do it that way :(
You can do this:
$res = array_pop(array_slice(somefunc(1), $i, 1));
If this is a one-off or occasional thing where the situation in your example holds true, and you're retrieving the first element of the return array, you can use:
$value = array_shift($obj->getArray());
If this is a pervasive need and you often need to retrieve elements other than the first (or last, for which you can use array_pop()), then I'd arrange to have a utility function available like so:
function elementOf($array, $index = 0) {
return $array[$index];
}
$value = elementOf($obj->getArray());
$otherValue = elementOf($obj->getArray(), 2);
Well, maybe this could help you, in php spl, pretty usefull, you can make you a Special Array for you:
<?php
class OArray extends ArrayObject{
public function __set($name, $val) {
$this[$name] = $val;
}
public function __get($name) {
return $this[$name];
}
}
class O{
function __construct(){
$this->array = new OArray();
$this->array[] = 1;
$this->array[] = 2;
$this->array[] = 3;
$this->array[] = 4;
}
function getArray(){
return $this->array;
}
}
$o = new O();
var_dump( $o->getArray()->{1} );
<?php
class MyArray implements Iterator ,ArrayAccess
{
private $var = array();
//-- ArrayAccess
public function offsetSet($offset, $value) {
if (is_null($offset)) {
$this->var[] = $value;
} else {
$this->var[$offset] = $value;
}
}
public function offsetExists($offset) {
return isset($this->var[$offset]);
}
public function offsetUnset($offset) {
unset($this->var[$offset]);
}
public function offsetGet($offset) {
return isset($this->var[$offset]) ? $this->var[$offset] : null;
}//-- Iterator
public function __construct($array){
if (is_array($array)) {
$this->var = $array;
}
}
public function rewind() {
reset($this->var);
}
public function current() {
return current($this->var);
}
public function key() {
return key($this->var);
}
public function next() {
return next($this->var);
}
public function valid() {
return ($this->current() !== false);
}
}
$values = array(
"one" => 1,
"two" => 2,
"three" => 3,
);
$it = new MyArray($values);
foreach ($it as $a => $b) {
print "$a: $b<br>";
}
echo $it["one"]."<br>";?>

Categories