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

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.

Related

PHP: Recursive generator that works on arrays, ArrayObjects and generators

I'm making a trait for ArrayObject in PHP 5.6 that returns a generator that walks through the contents of the ArrayObject, recursing through any arrays, ArrayObjects and Generators that it finds. I'm having some trouble getting the logic down. Here's what I have so far:
trait Stream
{
private function _streamOne($iterator)
{
foreach ($iterator as $key => $value) {
if (is_array($value) || (is_object($value) && $value instanceof \Traversable)) {
$this->_streamOne($value);
} else {
yield $key => $value;
}
}
}
public function stream()
{
return $this->_streamOne($this);
}
}
Unit test:
final class StreamTestClass extends \ArrayObject
{
use Stream;
}
$o = new StreamTestClass;
$o->offsetSet('alpha','blah');
$o->offsetSet('gamma',array('zeta'=>'yada','theta'=>'woot'));
$result = $o->stream();
$this->assertTrue($result->valid());
$this->assertEquals('alpha',$result->key());
$this->assertEquals('blah',$result->current());
$result->next();
$this->assertEquals('zeta',$result->key()); // fails asserting that null matches expected 'zeta'
$this->assertEquals('yada',$result->current());
$result->next();
$this->assertEquals('theta',$result->key());
$this->assertEquals('woot',$result->current());
$result->next();
$this->assertFalse($result->valid());
What do I need to do to make this work? The solution must work in PHP 5.6.
You're not actually yielding anything for the recursion.
trait Stream
{
private function _streamOne($iterator)
{
foreach ($iterator as $key => $value) {
if (is_array($value) || (is_object($value) && $value instanceof \Traversable)) {
// Change
foreach($this->_streamOne($value) as $ikey => $ivalue) {
yield $ikey => $ivalue;
}
// End
} else {
yield $key => $value;
}
}
}
public function stream()
{
return $this->_streamOne($this);
}
}

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.

PHP empty($string) return true but string is not empty

<?php
$m->type = 'EVENT';
if (empty($m->type)) {
var_dump($m->type);
}
?>
This piece of code prints
string(5) "EVENT"
How is this possible?
edit
The $m object is a plain one, with magic __set and __get that store values into a protected array.
<?php
$m->type = 'EVENT';
if ($m->type == NULL) {
var_dump($m->type);
}
?>
The above mentioned code works as expected (it skips the if body).
If you're using magic getter within your class, the docs page documents a rather tricky behaviour:
<?php
class Registry
{
protected $_items = array();
public function __set($key, $value)
{
$this->_items[$key] = $value;
}
public function __get($key)
{
if (isset($this->_items[$key])) {
return $this->_items[$key];
} else {
return null;
}
}
}
$registry = new Registry();
$registry->empty = '';
$registry->notEmpty = 'not empty';
var_dump(empty($registry->notExisting)); // true, so far so good
var_dump(empty($registry->empty)); // true, so far so good
var_dump(empty($registry->notEmpty)); // true, .. say what?
$tmp = $registry->notEmpty;
var_dump(empty($tmp)); // false as expected
?>

PHP Bi-Directional map

I'm porting to PHP a piece of Java code that uses a lot of Bi-directional maps (Guava's BiMap). Java-like maps are provided by PHP arrays or SplObjectStorage, but is there a library PHP Bi-Directional map available?
This class should provide for most needs of a bi-directional map :
class BiMap
{
private $KtoV, $VtoK;
public function __constructor()
{
$this->KtoV = []; // for version < 5.4.0, syntax must be: $this->KtoV = array();
$this->VtoK = [];
}
public function getKey($v)
{
if($this->hasValue($v))
{
return $this->VtoK[$v];
}
else
{
return null;
}
}
public function getAllKeys()
{
if($this->KtoV)
{
return array_keys($this->KtoV);
}
else
{
return $this->KtoV;
}
}
public function getValue($k)
{
if($this->hasKey($k))
{
return $this->KtoV[$k];
}
else
{
return null;
}
}
public function getAllValues()
{
if($this->VtoK)
{
return array_keys($this->VtoK);
}
else
{
return $this->VtoK;
}
}
public function hasKey($k)
{
return isset($this->KtoV[$k]);
}
public function hasValue($v)
{
return isset($this->VtoK[$v]);
}
public function put($k, $v)
{
if($this->hasKey($k))
{
$this->removeKey($k);
}
if($this->hasValue($v))
{
$this->removeValue($v);
}
$this->KtoV[$k] = $v;
$this->VtoK[$v] = $k;
}
public function putAll($array)
{
foreach($array as $k => $v)
{
$this->put($k, $v);
}
}
public function removeKey($k)
{
if($this->hasKey($k))
{
unset($this->VtoK[$this->KtoV[$k]]);
$v = $this->KtoV[$k];
unset($this->KtoV[$k]);
return $v;
}
else
{
return null;
}
}
public function removeValue($v)
{
if($this->hasValue($v))
{
unset($this->KtoV[$this->VtoK[$v]]);
$k = $this->VtoK[$v];
unset($this->VtoK[$v]);
return $k;
}
else
{
return null;
}
}
}
However, if you require null checking for key/values and/or object/array checking then handling similar to the following lines of code should be given in the body of a function and called appropriately within the hasKey($k), hasValue($v) and put($k, $v) methods :
if($item === null)
{
throw new Exception('null as BiMap key / value is invalid.');
}
if(is_object($item) || is_array($item))
{
throw new Exception('Object / Array as BiMap key / value is invalid.');
}
I did that once putting the values into 2 arrays. If keySet() and valueSet() are disjunct you can even use one value. Example:
$mapKtoV = array();
$mapVtoK = array();
function putInMap ($key,$value)
{
$mapKtoV[$key] = $value;
$mapVtoK[$value] = $key;
}
Of course you can also put them into a class.
Do you also think that this solution appears dodgy and smells? Yes, true, welcome to the world of PHP, which is usually dominated by bad code design. If you are really looking for a good solution, you should actually port you source from PHP to Java ;)
Hope it helps.

Searching in multidimensional php objects

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

Categories