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.
Related
As I understand, there is no way (even in PHP 7) to force function to take typehint of its parameter as an array of objects.
I think there is a workaroud to do that by defining another object as Traversable, which would be the container for all MyObject that would be otherwise in the array and setting the typehint to that Traversable.
But it would be so cool, if I could do this:
public function foo(MyObject[] $param) {}
So my question is, is there any reason why PHP doesn't implement this?
you can also
$arrYourObjectType = new YourObjectType[];
Then if the object array is your return type for the function, in your phpdoc, to type hint the return value, in the phpdoc above your function:
/**
* #param $whatever
* #return array ...$arrYourObjectType
**/
public function someFunction($whatever){
$arrYourObjectType[] = new YourObjectType[];
$x=0;
foreach($arrValues as $value)
{
$objYourObjectType = new YourObjectType();
$objYourObjectType->setSomething($value[0])
->setSomethingElse($value[1]);
(and so on)
//we had to set the first element to a new YourObjectType so the return
//value would match the hinted return type so we need to track the
//index
$arrYourObjectType[$x] = $objYourObjectType;
$x++;
}
return $arrYourObjectType;
}
Then in IDE's such as php storm, when using a class containing that function the return value of the function will be treated like an array of your object (properly hinted) and the IDE will expose the object methods on each element of the object array properly.
You can do things easy/dirty without all this, but phpStorm won't hint the methods on the elements of your object array properly.
If feeding an array of YourObjectType to a function...
/**
*#param YourObjectType ...$arrYourObjectType
**/
public function someFunction(YourObjectType...$arrYourObjectType){
foreach($arrYourObjectType as $objYourObject)
{
$someval = $objYourObject->getSomething();//will be properly hinted in your ide
}
}
It's all about the ellipses when feeding and retrieving object arrays :-)
Edit: I had a few things wrong with this because I did it from memory... corrected... Sorry bout this...
I don't understand well your question but if you want to insert data inside a object you can do:
<?php
class Insert
{
public $myobject = array();
public function foo($insert_in_object, $other_param_in_object) {
$this->myobject[] = $insert_in_object;
$this->myobject[] = $other_param_in_object;
return $this->myobject;
}
}
$start = new Insert();
$myobject = $start->foo('dog', 'cat');
var_dump($myobject)
?>
I want to take a value object like
class Vo {
private $blah;
private $de;
public function setBlah($blah) {
$this->blah = $blah;
}
public function getBlah() {
return $this->blah;
}
public function setDe($de) {
$this->de = $de;
}
public function getDe() {
return $this->de;
}
}
And with out building it call a method that will either turn it into an array or even better build the JSON to look like [{"blah" : "something set", "de" : "something set"}]. I'm just returning to PHP from Java/Jboss where I'm able to call a method on the object and it turns it into json.
You can't use json_encode() because it doesn't work on private variables. You'll need to define another function on the object:
function getJSON() {
return '[{"blah" : "'.$this->blah.'", "de" : "'.$this->de.'"}]';
}
codepad
Edit: Since PHP 5.4 PHP offers this easily:
class Vo implements JsonSerializable {
...
public function jsonSerialize() {
return get_object_vars($this);
}
...
}
...
var_dump(json_encode($object));
Gives you:
string(29) "{"blah":"hammer","de":"time"}"
See The JsonSerializable interface. Even if you're below PHP 5.4 implement that function and then just call it manually:
json_encode($object->jsonSerialize());
So you have a common pattern of how to deal with it.
Demo PHP 5.4; Demo PHP 5.2
You can try with json_encode($object), and json_decode($jsonObject).
Now, the only problem is, I'm not sure if the json_decode will return the proper object, or just a StdClass with the given properties.
I just tried doing a json_decode() on a simple json object, and it turns it into an object of the stdClass object, so you'll not be getting your Vo object back from json.
And as dfsq pointed out, json does not work on private properties.
If you do serialize($object), unserialize($string), then you can get the type back, but this is PHP specific, and not as portable as json.
Also take a look at this (php 5.4+):
http://www.php.net/manual/en/class.jsonserializable.php
If you are writing your own code from scratch, answer suggested by #SomeKittens with #hakre edits is way to go, but sometimes you don't want / can't modify class code. You can easily json_encode any object with all properties, but you need to cast it to an array and clean up keys for private/protected property names:
/**
* Encode object to JSON with protected/private variables with nested objects support.
* #param mixed $object object to be jsonized
* #param int $jsonEncodeOptions see JSON_* constants
* #param bool $returnArray for recursive calls
* #return string|array JSON string, associative array for recursive calls
*/
function json_encode_object($object, $jsonEncodeOptions = 0, $returnArray = FALSE){
$ret = array();
foreach((array)$object as $key => $value){
if(is_resource($value)){
//$rVal = NULL;
continue;
}elseif(is_object($value)){
$rVal = json_encode_object($value, $jsonEncodeOptions, TRUE);
}else{
$rVal = $value;
}
$rKey = str_replace(
array(
chr(0)."*".chr(0), //protected
chr(0).get_class($object).chr(0) //private
)
,'',
$key
);
$ret[$rKey] = $rVal;
}
return $returnArray?$ret:json_encode($ret, $jsonEncodeOptions);
}
I am using PHP 5.2.x and want to encode objects of my custom PHP classes with only private members. One of the private members is an array of objects of another custom class.
I tried the solution outlined in https://stackoverflow.com/a/7005915/17716, but that obviously does not work recursively. The only solution that I can see is extending the json_encode method somehow ,so that the class' version of the json_encode method is called instead of the default method.
For reference, the code is as follows:
Class A {
private $x;
private $y;
private $z;
private $xy;
private $yz;
private $zx;
public function f1() {
...
}
public function f2() {
...
}
.
.
.
public function getJSONEncode() {
return json_encode(get_object_vars($this));
}
}
class B {
private $p; //This finally stores objects of class A
private $q;
private $r;
public function __construct() {
$this->p = array();
}
public function fn1() {
...
}
public function fn2() {
...
}
.
.
.
public function getJSONEncode() {
return json_encode(get_object_vars($this));
}
}
class C {
private $arr;
public function __construct() {
$this->arr = array();
}
public function fillData($data) {
$obj = new B();
//create objects of class B and fill in values for all variables
array_push($this->arr, $obj)
}
public function returnData() {
echo $this->arr[0]->getJSONEncode(); //Edited to add
}
}
What would be the best way to achieve this nested json encoding?
Edited to Add:
The output I get when the returnData method is executed is:
{"p":[{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}],"q":"Lorem Ipsum","r":"Dolor Sit Amet"}
Whilst I believe you would be better off writing a proper export/encode function for each of your classes (which would construct a public object from both private and public values just for encoding - you could be quite clever and use php's reflection ability) - instead - you could make use of the following code:
/// example class B
class TestB {
private $value = 123;
}
/// example class A
class TestA {
private $prop;
public function __construct(){
$this->prop = new TestB();
}
}
function json_encode_private($obj){
/// export the variable to find the privates
$exp = var_export($obj, true);
/// get rid of the __set_state that only works 5.1+
$exp = preg_replace('/[a-z0-9_]+\:\:__set_state\(/i','((object)', $exp);
/// rebuild the object
eval('$enc = json_encode('.$exp.');');
/// return the encoded value
return $enc;
}
echo json_encode_private(new TestA());
/// {"prop":{"value":123}}
So the above should work, but I wouldn't recommend using eval anywhere in php - just because I always hear alarm bells quietly off in the distance :)
update
Just had a thought of what might make this a little safer, rather than using eval you could use create_function which would limit some of its creational powers, or at least the scope of those powers...
function json_encode_private($obj){
$exp = var_export($obj, true);
$exp = preg_replace('/[a-z0-9_]+\:\:__set_state\(/i','((object)', $exp);
$enc = create_function('','return json_encode('.$exp.');');
return $enc();
}
update 2
Had a chance to play around with another way of converting an object with private properties, to an object with public properties - using only a simple function (and no eval). The following would need to be tested on whichever version of PHP you are using as it's behaviour - again - might not be reliable.... due to the weird \0Class Name\0 prefixing in the converted private properties (see comments in code).
For more info on this strange prefixing behaviour:
http://uk3.php.net/language.types.array.php#language.types.array.casting
Anyway, so using a test class:
class RandomClass {
private $var = 123;
private $obj;
public function __construct(){
$this->obj = (object) null;
$this->obj->time = time();
}
}
We can use the following function to convert it to a public object:
function private_to_public( $a ){
/// grab our class, convert our object to array, build our return obj
$c = get_class( $a ); $b = (array) $a; $d = (object) null;
/// step each property in the array and move over to the object
/// usually this would be as simple as casting to an object, however
/// my version of php (5.3) seems to do strange things to private
/// properties when casting to an array... hence the code below:
foreach( $b as $k => $v ){
/// for some reason private methods are prefixed with a \0 character
/// and then the classname, followed by \0 before the actual key value.
/// This must be some kind of internal protection causing private
/// properties to be ignored. \0 is used by some languges to terminate
/// strings (not php though, as far as i'm aware).
if ( ord($k{0}) === 0 ) {
/// trim off the prefixed weirdnesss..?!
$e = substr($k, 1 + strlen($c) + 1);
/// unset the $k var first because it will remember the \0 even
/// if other values are assigned to it later on....?!
unset($k); $k = $e;
}
/// so if we have a key, either public or private - set our value on
/// the destination object.
if ( $k !== '' && $k !== NULL && $k !== FALSE ) {
$d->{$k} = $v;
}
}
return $d;
}
So if we put it all together:
$a = new RandomClass();
echo json_encode( private_to_public( $a ) );
/// {"var":123,"obj":{"time":1349777323}}
Again your best / most reliable bet is to either bespokely code your conversion methods for each class, or create some kind of generalised solution using Class Reflection, but that latter is far more involved, more involved than a StackOverflow answer... at least with the amount of time I have free ;)
further info
The above code will work when trying to access objects from anywhere, the reason for implementing this was because my first attempt was obviously to use the following:
echo json_encode( get_object_vars($a) );
/// you will get {} which isn't what you expect
It seems that if you want to use get_object_vars you have to use it from a context that has access to all the properties, i.e. from inside the class you are exposing:
public function getAllProperties(){
return get_object_vars( $this );
}
So, imagine we'd added the above to the RandomClass definition:
echo json_encode( $a->getAllProperties() );
/// you will get {"var":123,"obj":{"time":1349777323}}
This works because the members of a class have access to all the class's properties public or private.... so as I say, working this way is far far superior; superior, but not always possible.
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);
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 :)