I am writing a fairly complex PHP applications where a single user action can trigger changes in many other sub-systems, and I'm contemplating using an observer pattern. However, I am wondering if I have to re-create all the objects involved.
Is it possible to while serializing objects to store their relationships? For example
$equipmentHandler = new EquipmentHandler();
$character = new Character();
$character->subscribeOnEquipmentChanged($equipmentHandler);
$_SESSION['character'] = serialize($character);
$_SESSION['subscriber'] = serialize($equipmentHandler);
Will the relationship be preserved after unserializing? Or do I have do lump them all into one object?
$cache['character'] = $character;
$cache['subscriber'] = $equipmentHandler;
$_SESSION['cache'] = serialize($cache);
Any advice would be appreciated.
(PS. The character data requires many DB requests to create and I am thinking of storing it by doing a write to cache and DB, but only read from cache policy, so it will be serialized anyway)
A relation will be kept, but it will be different than you expect. When you serialize two instances of Character that both refer to the same EquipmentHandler, you're going to get two separate instances of this EquipmentHandler, instead of the single one you expected. As this example illustrates:
<?php
echo "BEFORE SERIALIZE:\n";
class A { }
class B { }
$a = new A;
$b = new B;
$a -> b = $b;
$a2 = new A;
$a2 -> b = $b;
var_dump($a->b);
var_dump($a2->b);
echo "AFTER SERIALIZE:\n";
$a3 = unserialize(serialize($a));
$a4 = unserialize(serialize($a2));
var_dump($a3->b);
var_dump($a4->b);
The output of this is:
BEFORE SERIALIZE:
object(B)#2 (0) {
}
object(B)#2 (0) {
}
AFTER SERIALIZE:
object(B)#5 (0) {
}
object(B)#7 (0) {
}
Look for the number after the pound. This refers to the object ID within PHP. Before serializing both $a->b and $a2->b refer to an object with object ID #2: the same instance. But after the serialization they refer to object IDs #5 and #7: different instances.
This may, or may not, be a problem for you.
To restore the connection to one single B object, you're going to have to get a little tricky. You could use the __sleep() handler in A to flatten the actual reference to an INSTANCE of B to just a mentioning of B: "I had a reference to B". Then implement the __wakeup() handler using that mentioning of a B instance in A to acquire a single instance of a new B object.
BTW. The PHP session extension already does serializing automatically, no need for you to pre-serialize it yourself :)
According to the manual of the serialize function:
The value to be serialized. serialize() handles all types, except the resource-type. You can even serialize() arrays that contain references to itself. Circular references inside the array/object you are serializing will also be stored. Any other reference will be lost.
When serializing objects, PHP will attempt to call the member function __sleep prior to serialization. This is to allow the object to do any last minute clean-up, etc. prior to being serialized. Likewise, when the object is restored using unserialize() the __wakeup member function is called.
So I guess it is not possible unless you do something smart in the _sleep and _wakeup
Your actually have the solution in your question! More complex cases might need to make use of __sleep and __wakeup ... but given the information you provided, all you have to do is -- as you suggest -- "lump them all into one object".
Explanation
In an answer to a similar question, I said:
Serialization will maintain "relative" references. (Technically, there is no such thing as a relative reference in PHP, but its a good way to conceptualize it.)
If you collect your referenced and referencing variables in an array, serializing the array will save the reference relationship. It won't maintain the original reference, but will automatically recreate it in the context of the new array returned by unserialize. ... It works the same way for internal references in objects.
Example
// example objects
class A {}
class B {}
$a = new A();
$b = new B();
$b->a = $a;
// collect referenced and referencing objects in array
$cache = array( 'a' => $a, 'b' => $b );
// flatten and recreate cache (represents data stored & retrieved from db)
$cached = unserialize( serialize( $cache ) );
// overwrite local variables from cache
extract( $cached, EXTR_OVERWRITE );
Then, if you do var_dump( $a ); var_dump( $b->a );, notice in the output below how the object IDs for $a and for $b->a are both '3', indicating they both refer to the same instance of A.
object(A)#3 (0) {
}
object(A)#3 (0) {
}
Related
class MyClass {
// data going in
private $in;
// data going out
public $out;
// constructor
public function __construct($obj0, $obj1) {
$this->in = array('obj0' => $obj0, 'obj1' => $obj1);
$this->out = array();
}
// method
public function process() {
foreach ($this->in as $key => $value) {
$this->out[$key] = $value;
}
}
}
PRIOR KNOWLEDGE
Objects are passed by reference by default, but there are edge cases
PHP uses copy on write
QUESTION
Am I doubling the memory usage now since I have the objects in MyClass->in and MyClass->out?
If i use a method of obj0 or obj1 am I forcing a copy on write, even if it does not change any of the values of the properties of these objects?
Is the object still passed by reference when added as a class property?
As the manual page you link to says, the statement "objects are passed by reference" is not a good description of what is happening. A better way to think of it is that the "value" of an object is a handle, pointer, or address to something that exists in a different space.
Whether you assign it to an object property, an array element, or a normal variable, this "value" remains the same, and changes to the object are visible wherever you look at them. Copying the value uses a few bytes (the size of the pointer) but doesn't duplicate the memory of the object itself.
As a final clarification, the "write" in "copy-on-write" refers to modification of an existing value, after copying it from one place to another. So writing $foo = $bar, where $bar is an array, will not duplicate the memory used by the array, but subsequently writing $foo[0]=1; or $bar[0]=1 will, because the two copies need to be distinguished. This doesn't actually come into play in your example; if it did, it would be just the "object pointer" that was copied, so very little extra memory would be needed.
Is possible in PHP to replace an object A of class1 with an object B of class2 so that all references to A are now redirected to B.
The goal is to avoid tracking all the references to A, which may exist in a complex system of relations, and replacing the object one by one.
A bit more context: I need object A to be a temporary stand-in during parsing of an svg file, representing a <use> tag, to be replaced by the actual instance referred to by the <use> tag, once the file is parsed. The problem is that <use> may refer to a tag that has not been parsed yet, and <use> tag may refer to object of different geometric type.
Here is a (generic) example of the behavior I want:
$A = new Class1();
$Array_1["name1"] = $A;
...
$Array_n["namen"] = $A;
$object_1->field_1 = $A;
...
$object_n->field_n = $A;
$B = new Class2(); // In my case, $B is assigned to a field of $A, and both extend the same abstract class.
TELL_PHP_ENGINE_TO_REPLACE($A, $B); //Needed operation
Note that this operation must happen at the level of the php engine, by changing the the handle and handler fields, handler table, (etc.) related to the zval structure.
This may be impossible currently, but I want to check before I invest time writing a function to do all replacements manually. Also, a manual function will have to be updated every time I modify the architecture.
For a PHP project I working on I have an object (a singleton actually but that's not hugely important, I am aware that I will need to make a special case for the static value(s)). This object contains protected arrays each with pointers to between ten and thirty other objects.
What is the simplest way to serialize (and also unserialize) the entire data structure while maintaining all the relevant references and indexes (which is what most of the arrays are)?
details
The program deals with cards which are each represented by objects and these objects are collected by packs (also objects) and registered to an object called box. Packs also register to box (passing by reference the object) which maintains an index of both. Box carries out various operations (like getting a random card from each pack and adding it to another array (as pointer) and creating a mirror array (less these cards) called index which it shuffles. The cards are then "dealt" between instances of the object player (which will also probably register with box).
It would be nice to think that I could simply serialise box and everything would be fine but I strongly doubt this. How can I be sure that all the references (arrays of pointers) are intact and correct (and not copies of the objects) after the mess of objects has become a string and gone back to being objects again?
UPDATES
I attempted to simply dump box with serialize and got
Object of class pack could not be converted to int in /path/to/box.class.php on line XYZ
To be honest that is what I expected. Thus my question about how I go about doing this.
Maybe I am communicating poorly? Perhaps some code will make this clearer.
Note just how much by reference storage we have going on. How do I implement the Serializable interface to account for this?
<?php
class box{
public $cards = array();
public $index = array();
protected $solution = array();
public $packs = array();
public function shuffle(){
if(count($this->index==0)){
$this->index = $this->cards;
}
shuffle($this->index);
}
public function set_up(){
$this->index = $this->cards;
foreach($this->packs as $pack){
$card=$pack->chooseAtRandom();
unset($this->index[$card->getID()]);
$this->solution[]&=$card;
}
$this->shuffle();
}
public function registerPackToBox(&$pack){
$this->packs[] &= $pack;
return (count($this->packs)-1);
}
public function registerCardToBox(&$card){
$this->cards[] &= $card;
return (count($this->cards)-1);
}
// ... other stuff ...
}
From looking at the docs, $_SESSION will automatically serialize and store objects for you.
Example: http://example.preinheimer.com/sessobj.php
I've created an object factory where I pass the object name as the first parameter and then an array of dependencies as the second parameter. I already have it working but but I feel there has to be a simple php function to allow for the dynamic instantiation of the object.
$shinyObject = ObjectFactory::get('Model\MyObject', array('\lib\DependencyOne', '\lib\DependencyTwo'))
The purpose of this factory is to retrieve an object thats serialized in session if it exists, if not then create a new instance of the object and then save in session. I want to know if there is a php function to instantiate a new object dynamically with dependencies.
As far as I understand, what you're looking after is a dependency injection framework.
PHP doesn't offer such thing out of the box.
There are, however, nice external libraries such as PHP-DI that provide this kind of functionality. Since you already created such library yourself, I don't think you're going to need it, but I think it was worth mentioning anyway - you can look at how some things are done (for instance, how to deal with singletons, etc.).
The nice thing about PHP-DI is that objects themselves specify what they need, and main object factory takes care of everything, by constructing a map of dependencies (perhaps that is what you meant by "easier" approach - you don't actually need to pass list of dependent objects in PHP-DI).
If I were to make such thing myself, I'd implement ObjectFactory::define($key, array $deps) that would be called once per object, and then use just ObjectFactory::get($key) whenever I need, without having to know anything about $$key's dependencies.
Just use new keyword:
$foo = 'Bar'
$bar = new $foo;
or you can generate a string and use eval().
Edit
In response to Justin Kiang's concerns, here is an example:
class A
{
}
class Foo
{
public $a;
public function __construct(A $a)
{
$this->a = $a;
}
}
function create($class, array $dependencies)
{
$str = "\$bar = new $class(";
foreach ($dependencies as $dependency)
{
$str .= "unserialize('" . serialize($dependency) . "'), ";
}
$str = trim($str);
$str = trim($str, ",") . ");";
eval($str);
return $bar;
}
$a = new A;
var_dump(create('Foo', [$a]));
It results as follows:
object(Foo)[2]
public 'a' =>
object(A)[3]
This is more a discussion topic than a 'help me' question.
I have just come across a strange problem. A variable (object) is being overridden when not passed anywhere.
Example;
var_dump($node->title);
$entity = $server->pull($item);
var_dump($node->title);
The two var_dumps display two different values.
$item is an unrelated string.
At this point, I dont think the contents of the 'pull()' method are relevant - I'm not curious about WHERE this is being overridden, I'm curious about HOW its being overridden.
How can PHP alter the variable unless it has been passed to the method?
There are no references in my function, node is passed direct;
function my_function($node) {
Even if I make a clone of my object
Even if I rename the var
$my_node = $node;
The cloned object is still overridden.
The renamed object is still overridden (ie, its not related to the name.)
I would love to know how this is possible. How can pull() (or associated methods) over ride a variable that they have not been given?
I need to reiterate; I don't care where the value is being changed, I care about how it is being changed when it isn't passed to the method.
Doing this:
$my_node = $node;
Doesn't actually clone the object. If you change $my_node, it will still be reflected in $node because both are just pointers to the object, see http://www.php.net/manual/en/language.oop5.references.php
So, possibly something is acting on $node or a copy of $node and therefore changing the object.
See this example from the link above:
class A {
public $foo = 1;
}
$a = new A;
$b = $a; // $a and $b are copies of the same identifier
// ($a) = ($b) = <id>
$b->foo = 2;
echo $a->foo."\n";
Even though we did $b = $a, if we change the property on $b, it changes the property on $a.
So, an important part of OO in PHP5 is that objects are always passed by reference, which I'd totally forgotten.
This makes it more than likely that the method being called has access to my object via a reference and makes this question seem silly. :)
http://php.net/manual/en/language.oop5.references.php