PHP lazy array mapping - php

Is there a way of doing array_map but as an iterator?
For example:
foreach (new MapIterator($array, $function) as $value)
{
if ($value == $required)
break;
}
The reason to do this is that $function is hard to calculate and $array has too many elements, only need to map until I find a specific value. array_map will calculate all values before I can search for the one I want.
I could implement the iterator myself, but I want to know if there is a native way of doing this. I couldn't find anything searching PHP documentation.

In short: No.
There is no lazy iterator mapping built into PHP. There is a non-lazy function iterator_apply(), but nothing like what you are after.
You could write one yourself, as you said. I suggest you extend IteratorIterator and simply override the current() method.
If there were such a thing it would either be documented here or here.

This is a lazy collection map function that gives you back an Iterator:
/**
* #param array|Iterator $collection
* #param callable $function
* #return Iterator
*/
function collection_map( $collection, callable $function ) {
foreach( $collection as $element ) {
yield $function( $element );
}
}

I'm thinking of a simple Map class implementation which uses an array of keys and an array of values. The overall implementation could be used like Java's Iterator class whereas you'd iterate through it like:
while ($map->hasNext()) {
$value = $map->next();
...
}

foreach ($array as $key => $value) {
if ($value === $required) {
break;
} else {
$array[$key] = call_back_function($value);
}
}
process and iterate until required value is found.

Don't bother with an iterator, is the answer:
foreach ($array as $origValue)
{
$value = $function($origValue);
if ($value == $required)
break;
}

I wrote this class to use a callback for that purpose. Usage:
$array = new ArrayIterator(array(1,2,3,4,5));
$doubles = new ModifyIterator($array, function($x) { return $x * 2; });
Definition (feel free to modify for your need):
class ModifyIterator implements Iterator {
/**
* #var Iterator
*/
protected $iterator;
/**
* #var callable Modifies the current item in iterator
*/
protected $callable;
/**
* #param $iterator Iterator|array
* #param $callable callable This can have two parameters
* #throws Exception
*/
public function __construct($iterator, $callable) {
if (is_array($iterator)) {
$this->iterator = new ArrayIterator($iterator);
}
elseif (!($iterator instanceof Iterator))
{
throw new Exception("iterator must be instance of Iterator");
}
else
{
$this->iterator = $iterator;
}
if (!is_callable($callable)) {
throw new Exception("callable must be a closure");
}
if ($callable instanceof Closure) {
// make sure there's one argument
$reflection = new ReflectionObject($callable);
if ($reflection->hasMethod('__invoke')) {
$method = $reflection->getMethod('__invoke');
if ($method->getNumberOfParameters() !== 1) {
throw new Exception("callable must have only one parameter");
}
}
}
$this->callable = $callable;
}
/**
* Alters the current item with $this->callable and returns a new item.
* Be careful with your types as we can't do static type checking here!
* #return mixed
*/
public function current()
{
$callable = $this->callable;
return $callable($this->iterator->current());
}
public function next()
{
$this->iterator->next();
}
public function key()
{
return $this->iterator->key();
}
public function valid()
{
return $this->iterator->valid();
}
public function rewind()
{
$this->iterator->rewind();
}
}

PHP's iterators are quite cumbersome to use, especially if deep nesting is required. LINQ, which implements SQL-like queries for arrays and objects, is better suited for this, because it allows easy method chaining and is lazy through and through. One of the libraries implementing it is YaLinqo*. With it, you can perform mapping and filtering like this:
// $array can be an array or \Traversible. If it's an iterator, it is traversed lazily.
$is_value_in_array = from($array)->contains(2);
// where is like array_filter, but lazy. It'll be called only until the value is found.
$is_value_in_filtered_array = from($array)->where($slow_filter_function)->contains(2);
// select is like array_map, but lazy.
$is_value_in_mapped_array = from($array)->select($slow_map_function)->contains(2);
// first function returns the first value which satisfies a condition.
$first_matching_value = from($array)->first($slow_filter_function);
// equivalent code
$first_matching_value = from($array)->where($slow_filter_function)->first();
There're many more functions, over 70 overall.
* developed by me

Have a look at Non-standard PHP library. It has a lazy map function:
use function \nspl\a\lazy\map;
$heavyComputation = function($value) { /* ... */ };
$iterator = map($heavyComputation, $list);

Related

PHP object comparison and private properties

I am wondering how PHP determines the equality of instances of a class with private properties:
class Example {
private $x;
public $y;
public __construct($x,$y) {
$this->x = $x; $this->y = $y;
}
}
and something like
$needle = new Example(1,2);
$haystack = [new Example(2,2), new Example(1,2)];
$index = array_search($needle, $haystack); // result is 1
The result is indeed 1, so the private member is compared. Is there a possibility to only match public properties?
I know I could overwrite the __toString method and cast all arrays and needles to string, but that leads to ugly code.
I am hoping to find a solution that is elegant enough to work with in_array, array_search, array_unique, etc.
A possible solution could be the PHP Reflection API. With that in mind you can read the public properties of a class and compare them to other public properties of another instance of the same class.
The following code is a simple comparison of public class properties. The base for the comparison is a simple value object.
declare(strict_types=1);
namespace Marcel\Test;
use ReflectionClass;
use ReflectionProperty;
class Example
{
private string $propertyA;
public string $propertyB;
public string $propertyC;
public function getPropertyA(): string
{
return $this->propertyA;
}
public function setPropertyA(string $propertyA): self
{
$this->propertyA = $propertyA;
return $this;
}
public function getPropertyB(): string
{
return $this->propertyB;
}
public function setPropertyB($propertyB): self
{
$this->propertyB = $propertyB;
return $this;
}
public function getPropertyC(): string
{
return $this->propertyC;
}
public function setPropertyC($propertyC): self
{
$this->propertyC = $propertyC;
return $this;
}
public function __compare(Example $b, $filter = ReflectionProperty::IS_PUBLIC): bool
{
$reflection = new ReflectionClass($b);
$properties = $reflection->getProperties($filter);
$same = true;
foreach ($properties as $property) {
if (!property_exists($this, $property->getName())) {
$same = false;
}
if ($this->{$property->getName()} !== $property->getValue($b)) {
$same = false;
}
}
return $same;
}
}
The __compare method of the Example class uses the PHP Reflection API. First we build a reflection instance of the class to which we want to compare to the current instance. Then we request all public properties of the class we want to compare to. If a public property does not exist in the instance or the value of the property is not the same as in the object we want to compare to, the method returns false, otherwise true.
Some examples.
$objectA = (new Example())
->setPropertyA('bla')
->setPropertyB('yadda')
->setPropertyC('bar');
$objectB = (new Example())
->setPropertyA('foo')
->setPropertyB('yadda')
->setPropertyC('bar');
$result = $objectA->__compare($objectB);
var_dump($result); // true
In this example the comparison results into true because the public properties PropertyB and PropertyC exist in both instances and have the same values. Keep in mind, that this comparison works only, if the second instance is the same class. One could spin this solution further and compare all possible objects based on their characteristics.
In Array Filter Example
It is a kind of rebuild of the in_array function based on the shown __compare method.
declare(strict_types=1);
namespace Marcel\Test;
class InArrayFilter
{
protected ArrayObject $data;
public function __construct(ArrayObject $data)
{
$this->data = $data;
}
public function contains(object $b)
{
foreach ($this->data as $object) {
if ($b->__compare($object)) {
return true;
}
}
return false;
}
}
This filter class acts like the in_array function. It takes a collection of objects and checks, if an object with the same public properties is in the collection.
Conclusion
If you want this solution to act like array_unique, array_search or ìn_array you have to code your own callback functions which execute the __compare method in the way you want to get the result.
It depends on the amount of data to be handled and the performance of the callback methods. The application could consume much more memory and therefore become slower.

How to optimize an ArrayIterator implementation in PHP?

I have a long running PHP daemon with a collection class that extends ArrayIterator. This holds a set of custom Column objects, typically less than 1000. Running it through the xdebug profiler I found my find method consuming about 35% of the cycles.
How can I internally iterate over the items in an optimized way?
class ColumnCollection extends \ArrayIterator
{
public function find($name)
{
$return = null;
$name = trim(strtolower($name));
$this->rewind();
while ($this->valid()) {
/** #var Column $column */
$column = $this->current();
if (strtolower($column->name) === $name) {
$return = $column;
break;
}
$this->next();
}
$this->rewind();
return $return;
}
}
Your find() method apparently just returns the first Column object with the queried $name. In that case, it might make sense to index the Array by name, e.g. store the object by it's name as the key. Then your lookup becomes a O(1) call.
ArrayIterator implements ArrayAccess. This means you can add new items to your Collection like this:
$collection = new ColumnCollection;
$collection[$someCollectionObject->name] = $someCollectionObject;
and also retrieve them via the square bracket notation:
$someCollectionObject = $collection["foo"];
If you don't want to change your client code, you can simply override offsetSet in your ColumnCollection:
public function offsetSet($index, $newValue)
{
if ($index === null && $newValue instanceof Column) {
return parent::offsetSet($newValue->name, $newValue);
}
return parent::offsetSet($index, $newValue);
}
This way, doing $collection[] = $column would automatically add the $column by name. See http://codepad.org/egAchYpk for a demo.
If you use the append() method to add new elements, you just change it to:
public function append($newValue)
{
parent::offsetSet($newValue->name, $newValue);
}
However, ArrayAccess is slower than native array access, so you might want to change your ColumnCollection to something like this:
class ColumnCollection implements IteratorAggregate
{
private $columns = []; // or SplObjectStorage
public function add(Column $column) {
$this->columns[$column->name] = $column;
}
public function find($name) {
return isset($this->data[$name]) ? $this->data[$name] : null;
}
public function getIterator()
{
return new ArrayIterator($this->data);
}
}
I replaced the iterator method calls with a loop on a copy of the array. I presume this gives direct access to the internal storage since PHP implements copy-on-write. The native foreach is much faster than calling rewind(), valid(), current(), and next(). Also pre-calculating the strtolower on the Column object helped. This got performance down from 35% of cycles to 0.14%.
public function find($name)
{
$return = null;
$name = trim(strtolower($name));
/** #var Column $column */
foreach ($this->getArrayCopy() as $column) {
if ($column->nameLower === $name) {
$return = $column;
break;
}
}
return $return;
}
Also experimenting with #Gordon's suggestion of using an array keyed on name instead of using the internal storage. The above is working well for a simple drop-in replacement.

"Indirect modification of overloaded element of SplFixedArray has no effect"

Why the following
$a = new SplFixedArray(5);
$a[0] = array(1, 2, 3);
$a[0][0] = 12345; // here
var_dump($a);
produces
Notice: Indirect modification of overloaded element of SplFixedArray has no effect in <file> on line <indicated>
Is it a bug? How do you deal with multidimensional SplFixedArrays then? Any workarounds?
First, the problem is related to all classes which implement ArrayAccess it is not a special problem of SplFixedArray only.
When you accessing elements from SplFixedArray using the [] operator it behaves not exactly like an array. Internally it's offsetGet() method is called, and will return in your case an array - but not a reference to that array. This means all modifications you make on $a[0] will get lost unless you save it back:
Workaround:
$a = new SplFixedArray(5);
$a[0] = array(1, 2, 3);
// get element
$element = $a[0];
// modify it
$element[0] = 12345;
// store the element again
$a[0] = $element;
var_dump($a);
Here is an example using a scalar which fails too - just to show you that it is not related to array elements only.
This is actually fixable if you slap a & in front of offsetGet (assuming you have access to the internals of your ArrayAccess implementation):
class Dict implements IDict {
private $_data = [];
/**
* #param mixed $offset
* #return bool
*/
public function offsetExists($offset) {
return array_key_exists(self::hash($offset), $this->_data);
}
/**
* #param mixed $offset
* #return mixed
*/
public function &offsetGet($offset) {
return $this->_data[self::hash($offset)];
}
/**
* #param mixed $var
* #return string
*/
private static function hash($var) {
return is_object($var) ? spl_object_hash($var) : json_encode($var,JSON_UNESCAPED_SLASHES);
}
/**
* #param mixed $offset
* #param mixed $value
*/
public function offsetSet($offset, $value) {
$this->_data[self::hash($offset)] = $value;
}
/**
* #param mixed $offset
*/
public function offsetUnset($offset) {
unset($this->_data[self::hash($offset)]);
}
}
Adding my experience with the same error, in case it helps anyone:
I recently imported my code into a framework with a low error-tolerance (Laravel). As a result, my code now throws an exception when I try to retrieve a value from an associative array using a non-existent key. In order to deal with this I tried to implement my own dictionary using the ArrayAccess interface. This works fine, but the following syntax fails:
$myDict = new Dictionary();
$myDict[] = 123;
$myDict[] = 456;
And in the case of a multimap:
$properties = new Dictionary();
$properties['colours'] = new Dictionary();
$properties['colours'][] = 'red';
$properties['colours'][] = 'blue';
I managed to fix the problem with the following implementation:
<?php
use ArrayAccess;
/**
* Class Dictionary
*
* DOES NOT THROW EXCEPTIONS, RETURNS NULL IF KEY IS EMPTY
*
* #package fnxProdCrawler
*/
class Dictionary implements ArrayAccess
{
// FOR MORE INFO SEE: http://alanstorm.com/php_array_access
protected $dict;
function __construct()
{
$this->dict = [];
}
// INTERFACE IMPLEMENTATION - ArrayAccess
public function offsetExists($key)
{
return array_key_exists($key, $this->dict);
}
public function offsetGet($key)
{
if ($this->offsetExists($key))
return $this->dict[$key];
else
return null;
}
public function offsetSet($key, $value)
{
// NOTE: THIS IS THE FIX FOR THE ISSUE "Indirect modification of overloaded element of SplFixedArray has no effect"
// NOTE: WHEN APPENDING AN ARRAY (E.G. myArr[] = 5) THE KEY IS NULL, SO WE TEST FOR THIS CONDITION BELOW, AND VOILA
if (is_null($key))
{
$this->dict[] = $value;
}
else
{
$this->dict[$key] = $value;
}
}
public function offsetUnset($key)
{
unset($this->dict[$key]);
}
}
Hope it helps.
I did a workaround to this problem extending SplFixedArray and overriding offsetGet() to return a reference*
But as this hek2mgl mentioned, it could lead to side effects.
I shared the code to do it, because I couldn't find it in other place. Note is not serious implementation, because I'm not even checking the offset exists (I will be glad if someone propose enhancements), but it works:
class mySplFixedArray extends SplFixedArray{
public function &offsetGet($offset) {
return $this->array[$offset];
}
}
I was changing native PHP hash like arrays for these less memory consuming fixed length arrays, and some of the other things I have to change too (either as a consequence of the lazy extending of the class SplFixedArray, or just for not using native arrays) were:
Creating a manual method for copying my class objects property by property. clone didn't worked anymore.
Use a[i]!==NULL to check if the element exists, because isset() didn't worked anymore.
Add an offsetSet() method to the extended class too:
public function offsetSet($offset,$value) { $this->array[$offset]=$value; }
(*) I think this overriding is only possible after some PHP version between 5.2.6 and 5.3.4. I couldn't find too much info or code about this problem, but I want to share the solution for other people anyway.
I guess SplFixedArray is incomplete/buggy.
If i wrote an own class and it works like a charm:
$a = new \myArrayClass();
$a[0] = array(1, 2, 3);
$a[0][0] = 12345;
var_dump($a->toArray());
Output (no notices/warnings here, in strict mode too):
array (size=1)
0 =>
array (size=3)
0 => int 12345
1 => int 2
2 => int 3
Using the [] operator is not a problem (for assoc/mixed arrays too). A right implementation of offsetSet should do the job:
public function offsetSet($offset, $value) {
if ($offset === null) {
$offset = 0;
if (\count($this->array)) {
$keys = \preg_grep( '#^(0|([1-9][0-9]*))$#', \array_keys($this->array));
if (\count($keys)) {
$offset = \max($keys) + 1;
}
}
}
...
But there is only one exception. Its not possible to use the [] operator for offset which does not exist. In our example:
$a[1][] ='value'; // Notice: Indirect modification of overloaded...
It would throw the warning above because ArrayAccess calls offsetGet and not offsetSet for [1] and the later [] fails. Maybe there is a solution out there, but i did not find it yet. But the following is working without probs:
$a[] ='value';
$a[0][] ='value';
I would write an own implementation instead of using SplFixedArray. Maybe its possible to overload some methods in SplFixedArray to fix it, but i am not sure because i never used and checked SplFixedArray.

Eclipse doesn't resolve object type in php array

I came from Java world and try the same "easy" things in Php.
I have a immutable dummy data object:
class BugTimeData {
private $bugid = "";
private $startDate = "";
private $resolvedDate = "";
private $status = "";
private $weekends = "";
function __construct($bugid, $startDate, $resolvedDate, $status) {
$this->bugid = $bugid;
$this->startDate = $startDate;
$this->resolvedDate = $resolvedDate;
$this->status = $status;
}
function getBugId() {
return $this->bugid;
}
function getStartDate() {
return $this->startDate;
}
function getResolvedDate() {
return $this->resolvedDate;
}
function getStatus() {
return $this->status;
}
function getWeekendsBetween() {
return $this->weekends;
}
}
A add a object from this class into an array:
$data= new BugTimeData($a, $b, $c, $d);
array_push($content, $data);
I want to iterate over this array, read out objects and access their methods:
foreach($time_prio_bug_content as $key => $value) {
var_dump($value->getStatus());
}
This works! But my IDE (Eclipse) does not really know that $value is from type BugTimeData. So I cannot access public methods in a easy way.
Question:
How to cast $value to a object from type BugTimeData?
In Java this is really easy (because I do not need a cast because I can define the type of the objects when creating an array) and straight forward. So I wonder why this is not possible in Php?
It's just a problem of your IDE, not of PHP. If you add the appropriate annotation type hints, any decent IDE will pick up on it. For instance, define that the array contains objects of a certain type:
/** #var BugTimeData[] $array */
$array = array();
If the data comes from a function or method, add an appropriate #return documentation tag to the method's signature. Worst case, mark the variable inside the loop:
foreach($time_prio_bug_content as $key => $value) {
/** #var BugTimeData $value */
var_dump($value->getStatus());
}
As an alternative to Annotation I believe you can type hint as such:
var $value = new BugTimeData; // this tells the IDE which type the variable will hold from here on
foreach($time_prio_bug_content as $key => $value) {
var_dump($value->getStatus());
}
or implement a class method for the cast with type hinting the argument and return object (see link)
You can't. Because PHP is practically typeless, and when reading code there's no way to know what type a certain variable is. It is only possible during runtime.

What is the best method to merge two PHP objects?

We have two PHP5 objects and would like to merge the content of one into the second. There are no notion of subclasses between them so the solutions described in the following topic cannot apply.
How do you copy a PHP object into a different object type
//We have this:
$objectA->a;
$objectA->b;
$objectB->c;
$objectB->d;
//We want the easiest way to get:
$objectC->a;
$objectC->b;
$objectC->c;
$objectC->d;
Remarks:
These are objects, not classes.
The objects contain quite a lot of fields so a foreach would be quite slow.
So far we consider transforming objects A and B into arrays then merging them using array_merge() before re-transforming into an object but we can't say we are proud if this.
If your objects only contain fields (no methods), this works:
$obj_merged = (object) array_merge((array) $obj1, (array) $obj2);
This actually also works when objects have methods. (tested with PHP 5.3 and 5.6)
foreach($objectA as $k => $v) $objectB->$k = $v;
You could create another object that dispatches calls to magic methods to the underlying objects. Here's how you'd handle __get, but to get it working fully you'd have to override all the relevant magic methods. You'll probably find syntax errors since I just entered it off the top of my head.
class Compositor {
private $obj_a;
private $obj_b;
public function __construct($obj_a, $obj_b) {
$this->obj_a = $obj_a;
$this->obj_b = $obj_b;
}
public function __get($attrib_name) {
if ($this->obj_a->$attrib_name) {
return $this->obj_a->$attrib_name;
} else {
return $this->obj_b->$attrib_name;
}
}
}
Good luck.
I understand that using the generic objects [stdClass()] and casting them as arrays answers the question, but I thought the Compositor was a great answer. Yet I felt it could use some feature enhancements and might be useful for someone else.
Features:
Specify reference or clone
Specify first or last entry to take precedence
Multiple (more than two) object merging with syntax similarity to array_merge
Method linking: $obj->f1()->f2()->f3()...
Dynamic composites: $obj->merge(...) /* work here */ $obj->merge(...)
Code:
class Compositor {
protected $composite = array();
protected $use_reference;
protected $first_precedence;
/**
* __construct, Constructor
*
* Used to set options.
*
* #param bool $use_reference whether to use a reference (TRUE) or to copy the object (FALSE) [default]
* #param bool $first_precedence whether the first entry takes precedence (TRUE) or last entry takes precedence (FALSE) [default]
*/
public function __construct($use_reference = FALSE, $first_precedence = FALSE) {
// Use a reference
$this->use_reference = $use_reference === TRUE ? TRUE : FALSE;
$this->first_precedence = $first_precedence === TRUE ? TRUE : FALSE;
}
/**
* Merge, used to merge multiple objects stored in an array
*
* This is used to *start* the merge or to merge an array of objects.
* It is not needed to start the merge, but visually is nice.
*
* #param object[]|object $objects array of objects to merge or a single object
* #return object the instance to enable linking
*/
public function & merge() {
$objects = func_get_args();
// Each object
foreach($objects as &$object) $this->with($object);
// Garbage collection
unset($object);
// Return $this instance
return $this;
}
/**
* With, used to merge a singluar object
*
* Used to add an object to the composition
*
* #param object $object an object to merge
* #return object the instance to enable linking
*/
public function & with(&$object) {
// An object
if(is_object($object)) {
// Reference
if($this->use_reference) {
if($this->first_precedence) array_push($this->composite, $object);
else array_unshift($this->composite, $object);
}
// Clone
else {
if($this->first_precedence) array_push($this->composite, clone $object);
else array_unshift($this->composite, clone $object);
}
}
// Return $this instance
return $this;
}
/**
* __get, retrieves the psudo merged object
*
* #param string $name name of the variable in the object
* #return mixed returns a reference to the requested variable
*
*/
public function & __get($name) {
$return = NULL;
foreach($this->composite as &$object) {
if(isset($object->$name)) {
$return =& $object->$name;
break;
}
}
// Garbage collection
unset($object);
return $return;
}
}
Usage:
$obj = new Compositor(use_reference, first_precedence);
$obj->merge([object $object [, object $object [, object $...]]]);
$obj->with([object $object]);
Example:
$obj1 = new stdClass();
$obj1->a = 'obj1:a';
$obj1->b = 'obj1:b';
$obj1->c = 'obj1:c';
$obj2 = new stdClass();
$obj2->a = 'obj2:a';
$obj2->b = 'obj2:b';
$obj2->d = 'obj2:d';
$obj3 = new Compositor();
$obj3->merge($obj1, $obj2);
$obj1->c = '#obj1:c';
var_dump($obj3->a, $obj3->b, $obj3->c, $obj3->d);
// obj2:a, obj2:b, obj1:c, obj2:d
$obj1->c;
$obj3 = new Compositor(TRUE);
$obj3->merge($obj1)->with($obj2);
$obj1->c = '#obj1:c';
var_dump($obj3->a, $obj3->b, $obj3->c, $obj3->d);
// obj1:a, obj1:b, obj1:c, obj2:d
$obj1->c = 'obj1:c';
$obj3 = new Compositor(FALSE, TRUE);
$obj3->with($obj1)->with($obj2);
$obj1->c = '#obj1:c';
var_dump($obj3->a, $obj3->b, $obj3->c, $obj3->d);
// obj1:a, obj1:b, #obj1:c, obj2:d
$obj1->c = 'obj1:c';
A very simple solution considering you have object A and B:
foreach($objB AS $var=>$value){
$objA->$var = $value;
}
That's all. You now have objA with all values from objB.
The \ArrayObject class has the possibility to exchange the current array to disconnect the original reference. To do so, it comes with two handy methods: exchangeArray() and getArrayCopy(). The rest is plain simple array_merge() of the provided object with the ArrayObjects public properties:
class MergeBase extends ArrayObject
{
public final function merge( Array $toMerge )
{
$this->exchangeArray( array_merge( $this->getArrayCopy(), $toMerge ) );
}
}
The usage is as easy as this:
$base = new MergeBase();
$base[] = 1;
$base[] = 2;
$toMerge = [ 3,4,5, ];
$base->merge( $toMerge );
a solution To preserve,both methods and properties from merged onjects is to create a combinator class that can
take any number of objects on __construct
access any method using __call
accsess any property using __get
class combinator{
function __construct(){
$this->melt = array_reverse(func_get_args());
// array_reverse is to replicate natural overide
}
public function __call($method,$args){
forEach($this->melt as $o){
if(method_exists($o, $method)){
return call_user_func_array([$o,$method], $args);
//return $o->$method($args);
}
}
}
public function __get($prop){
foreach($this->melt as $o){
if(isset($o->$prop))return $o->$prop;
}
return 'undefined';
}
}
simple use
class c1{
public $pc1='pc1';
function mc1($a,$b){echo __METHOD__." ".($a+$b);}
}
class c2{
public $pc2='pc2';
function mc2(){echo __CLASS__." ".__METHOD__;}
}
$comb=new combinator(new c1, new c2);
$comb->mc1(1,2);
$comb->non_existing_method(); // silent
echo $comb->pc2;
I would go with linking the second object into a property of the first object. If the second object is the result of a function or method, use references. Ex:
//Not the result of a method
$obj1->extra = new Class2();
//The result of a method, for instance a factory class
$obj1->extra =& Factory::getInstance('Class2');
To merge any number of raw objects
function merge_obj(){
foreach(func_get_args() as $a){
$objects[]=(array)$a;
}
return (object)call_user_func_array('array_merge', $objects);
}
This snippet of code will recursively convert that data to a single type (array or object) without the nested foreach loops. Hope it helps someone!
Once an Object is in array format you can use array_merge and convert back to Object if you need to.
abstract class Util {
public static function object_to_array($d) {
if (is_object($d))
$d = get_object_vars($d);
return is_array($d) ? array_map(__METHOD__, $d) : $d;
}
public static function array_to_object($d) {
return is_array($d) ? (object) array_map(__METHOD__, $d) : $d;
}
}
Procedural way
function object_to_array($d) {
if (is_object($d))
$d = get_object_vars($d);
return is_array($d) ? array_map(__FUNCTION__, $d) : $d;
}
function array_to_object($d) {
return is_array($d) ? (object) array_map(__FUNCTION__, $d) : $d;
}
All credit goes to: Jason Oakley
Here is a function that will flatten an object or array. Use this only if you are sure your keys are unique. If you have keys with the same name they will be overwritten. You will need to place this in a class and replace "Functions" with the name of your class. Enjoy...
function flatten($array, $preserve_keys=1, &$out = array(), $isobject=0) {
# Flatten a multidimensional array to one dimension, optionally preserving keys.
#
# $array - the array to flatten
# $preserve_keys - 0 (default) to not preserve keys, 1 to preserve string keys only, 2 to preserve all keys
# $out - internal use argument for recursion
# $isobject - is internally set in order to remember if we're using an object or array
if(is_array($array) || $isobject==1)
foreach($array as $key => $child)
if(is_array($child))
$out = Functions::flatten($child, $preserve_keys, $out, 1); // replace "Functions" with the name of your class
elseif($preserve_keys + is_string($key) > 1)
$out[$key] = $child;
else
$out[] = $child;
if(is_object($array) || $isobject==2)
if(!is_object($out)){$out = new stdClass();}
foreach($array as $key => $child)
if(is_object($child))
$out = Functions::flatten($child, $preserve_keys, $out, 2); // replace "Functions" with the name of your class
elseif($preserve_keys + is_string($key) > 1)
$out->$key = $child;
else
$out = $child;
return $out;
}
Let's keep it simple!
function copy_properties($from, $to, $fields = null) {
// copies properties/elements (overwrites duplicates)
// can take arrays or objects
// if fields is set (an array), will only copy keys listed in that array
// returns $to with the added/replaced properties/keys
$from_array = is_array($from) ? $from : get_object_vars($from);
foreach($from_array as $key => $val) {
if(!is_array($fields) or in_array($key, $fields)) {
if(is_object($to)) {
$to->$key = $val;
} else {
$to[$key] = $val;
}
}
}
return($to);
}
If that doesn't answer your question, it will surely help towards the answer.
Credit for the code above goes to myself :)

Categories