Searching in multidimensional php objects - php

public function filter($index, $value) {
if($this->tasks) {
foreach ($this->tasks as $id => $t) {
if($t->$index != $value) {
unset($this->tasks[$id]);
}
}
} else {
echo "Tasks are empty";
}
}
So I am having a problem with the function above searching multidimensional objects. What I mean by that is like these:
$task->form->id
$task->form->type
$task->author->id
$task->fields->[FIELDNAME]->value
$task->form->template->id
$task->id
etc. These are the kinds of fields that need to be accessed. I thought that I could just, in $index, put "form->id" but that didn't work. For my application, I literally can spell it out. I just don't want to have to write a function for each to the last, because some of them (as you can see) only need to be on the first level and some need to be all the way down four objects.
Just tell me what more data you need and I will give it. I know every keystroke intimately so it sometimes means I forget to share something.
If you help, thank you so much.
WHAT I DID
Okay so I got it to work but I did something different.
Within the Form class
public function search($value, $type = NULL) {
if(is_object($this->fields)) {
foreach($this->fields as $page) {
foreach($page as $name=>$field) {
if($name != "pdata") {
if ($type != NULL && $field->type == $type && $field->value == $value || $type == NULL && isset($field->value) && $field->value == $value) {
return true;
}
}
}
return false;
}
} else {
//Probably corrupted
return false;
}
return false;
}
Outside of it I can just call this function and delete or add based on whether it returns true or false.

function array_searchRecursive( $needle, $haystack, $strict=false, $path=array() )
{
if( !is_array($haystack) ) {
return false;
}
foreach( $haystack as $key => $val ) {
if( is_array($val) && $subPath = array_searchRecursive($needle, $val, $strict, $path) ) {
$path = array_merge($path, array($key), $subPath);
return $path;
} elseif( (!$strict && $val == $needle) || ($strict && $val === $needle) ) {
$path[] = $key;
return $path;
}
}
return false;
}
OR
I know you don't want to write it all out, but I have to say I love the way this is done in the Symfony framework. In symfony you have an Entity class that defines functions to retrieve each piece of information (variable) from a given object.
One way you could mimic this is to create a base class for tasks that has functions to retrieve each variable you need...such as getTaskId.
Then you could extend this base class with the one you are working with now. This would give you access to the functions you created to parse your object. Retrieving $task->form->template->id could be as simple as calling the getTemplateId function.
I know this would probably take longer than what you were looking for, but I hope it helps.

You can use get_object_vars() to process all public variables of an object and proceed recursively to reach all variables named id. Here is a simple template:
class Task {
public $author, $form, $id;
}
class Author {
public $id;
}
class Form {
public $id, $type, $template;
}
class Template {
public $id;
}
$tasks = Array();
$task = new Task();
$tasks[] = $task;
$task->id = "id1";
$task->author = new Author();
$task->author->id = "id1";
$task->form = new Form();
$task->form->template = new Template();
$task->form->template->id = "id1";
function filter($parent, $object, $id_value) {
$vars = get_object_vars($object);
foreach ($vars as $var => $value) {
if ($var == "id") {
if ($value == $id_value) {
echo "$parent -> $var == $value <br />";
}
}
else if (is_object($value) && isset($value)) {
filter("$parent -> $var", $value, $id_value);
}
}
}
foreach ($tasks as $index => $task) {
filter("Task[$index]", $task, "id1");
}
The output is:
Task[0] -> author -> id == id1
Task[0] -> form -> template -> id == id1
Task[0] -> id == id1

Related

How can I get/set a property dynamically having a path without recursion?

I would like to get/set a property of a property of a property (...) having the path. For example, if I have
$obj->a->b->c
I would like to get it with
get_property(["a", "b", "c"], $obj)
I've written this function for getting it and it works for array and object values but I need it for objects.
public static function get_value_by_path($index, $array) {
if (!$index || empty($index))
return NULL;
if (is_array($index)) {
if (count($index) > 1) {
if (is_array($array) && array_key_exists($index[0], $array)) {
return static::get_value_by_path(array_slice($index, 1), $array[$index[0]]);
}
if (is_object($array)) {
return static::get_value_by_path(array_slice($index, 1), $array->{$index[0]});
}
return NULL;
}
$index = $index[0];
}
if (is_array($array) && array_key_exists($index, $array))
return $array[$index];
if (is_object($array) && property_exists($array, $index)) return $array->$index;
return NULL;
}
My question is: is it possible to do this without recursion?
I haven't found similiar questions.
This function below will do it:
function get_property($propertyPath, $object)
{
foreach ($propertyPath as $propertyName) {
// basic protection against bad path
if (!property_exists($object,$property)) return NULL;
// get the property
$property = $object->{$propertyName};
// if it is not an object it has to be the end point
if (!is_object($property)) return $property;
// if it is an object replace current object
$object = $property;
}
return $object;
}
depending on what you exactly want to can build in some error code. You can use the get function if you want to set something like this:
function set_property($propertyPath, &$object, $value)
{
// pop off the last property
$lastProperty = array_pop($propertyPath);
// get the object to which the last property should belong
$targetObject = get_property($propertyPath,$object);
// and set it to value if it is valid
if (is_object($targetObject) && property_exists($targetObject,$lastProperty)) {
$targetObject->{$lastProperty} = $value;
}
}
I do however like recursion, and with it these functions could possibly be better.

When should I return?

I am struggling to create an access object to sections stored in the Database. This is a skellington of the process, this contains static data until I can get the principle working.
class User {
const IS_ADMIN = 1;
const IS_MODERATOR = 2;
const IS_MEMBER = 4;
}
This class will auto-load data from the database eventually but for the time being, this class has default values.
class Scope {
private $priv = [];
public function __construct() {
$this->priv = [1];
}
public function getPrivilidges() {
return $this->priv;
}
}
This is where it messes up, I can tell that the second and third conditions cannot be met if the first fails, how can I stop this?
class Priverlidges {
public function canView($type, Scope $scope) {
if($type & User::IS_ADMIN) {
foreach($scope->getPrivilidges() as $p) {
if($p == User::IS_ADMIN) continue;
return false;
}
return true;
}
if($type & User::IS_MODERATOR) {
foreach($scope->getPrivilidges() as $p) {
if($p == User::IS_MODERATOR) continue;
return false;
}
return true;
}
if($type & User::IS_MEMBER) {
foreach($scope->getPrivilidges() as $p) {
if($p == User::IS_MEMBER) continue;
return false;
}
return true;
}
}
}
Example usage which works fine when the default value of the priverlidge is 1:
echo (int)(new Priverlidges)->canView(User::IS_ADMIN, new Scope());
Example usage which works fine when the default value of the priverlidge is 2:
echo (int)(new Priverlidges)->canView(User::IS_MODERATOR | User::IS_ADMIN, new Scope()); // it returns false at the first condition
Can anyone help me with when to return true or false? Thanks in advance.
P.S - Users can be both Mods and Admins
EDIT: I have tried to use in_array() and still am unsure when to return the value true or false because it get's overwrite if the second method runs.
I figured it out. First, check the user is not already authenticated using a placeholder ($this->_state). Then check the type of user and check it is inside the scope.
class Priverlidges {
private $_state = false;
public function canView($type, Scope $scope) {
if(!$this->_state && $type & User::IS_ADMIN && in_array(User::IS_ADMIN, $scope->getPrivilidges())) {
$this->_state = true;
}
if(!$this->_state && $type & User::IS_MODERATOR && in_array(User::IS_MODERATOR, $scope->getPrivilidges())) {
$this->_state = true;
}
if(!$this->_state && $type & User::IS_MEMBER && in_array($scope->getPrivilidges(), User::IS_MEMBER)) {
$this->_state = true;
}
return $this->_state;
}
}

PHP OOP: why does self::$$property[$key] not work?

Is there a way to implement the following PHP class without the foreach loop (and otherwise un-changed)?
<?php
class foo {
private static $_thing = array(
'kee' => 'valyu'
);
public static function fetch($property, $key = '') {
if (property_exists('foo', $property)) {
if ($key == '') return self::$$property;
else {
# i cannot seem to do self::$$property[$key].
foreach (self::$$property as $_key => $_value) {
if ($_key == $key) return $_value;
}
}
}
return false;
}
}
var_dump(foo::fetch('bad')); # null
var_dump(foo::fetch('_thing')); # array(1) { ... }
var_dump(foo::fetch('_thing', 'bad')); # null
var_dump(foo::fetch('_thing', 'kee')); # string(5) "valyu"
?>
self::$$property[$key] gives me "access to undeclared static property: foo::$x" (x being the first character in the string value of $key).
It's simply a quirk of the php parser, there seem to be no way to tell the parse with syntax that you want the variable-variable resolved first and then use the [] on it's result instead of the $property[$key] first, in one line.
However if you break it into two it will work fine:
class foo {
private static $_thing = array(
'kee' => 'valyu'
);
public static function fetch($property, $key = '') {
if (property_exists('foo', $property)) {
if ($key == '') {
return self::$$property;
} else {
$prop = self::$$property; // move the result to temporary variable, first part of $$property[$key]
if (array_key_exists($key, $prop)) { //isset will return false if the key is in fact set to null
return $prop[$key]; // use it, second part of $$property[$key]
}
}
}
return null;
}
}
var_dump(foo::fetch('bad')); // null
var_dump(foo::fetch('_thing')); // array(1) { ... }
var_dump(foo::fetch('_thing', 'bad')); // null
var_dump(foo::fetch('_thing', 'kee')); // string(5) "valyu"
Update:
As Dan kindly pointed out, the {} syntax can be used to disambiguate the intent for the parser like this:
public static function fetch($property, $key = '') {
if (property_exists('foo', $property)) {
if ($key == '') {
return self::$$property;
} else if (array_key_exists($key, self::${$property})) {
return self::${$property}[$key];
}
}
return null;
}
I've also changed the last return from false to null so it matches your example's comments.

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];
?>

Convert PHP 5.3 anonymous function into 5.2 compatible function

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

Categories