SitePoint Sponsor

User Tag List

Results 1 to 12 of 12
  1. #1
    SitePoint Columnist Skunk's Avatar
    Join Date
    Jan 2001
    Location
    Lawrence, Kansas
    Posts
    2,066
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Help needed with PHP references stored as object properties

    I'm lost in a sea of ampersands

    Here's what I'm trying to do. I am currently in the "design" stage of a rewrite of my PHP links script (previously known as ssLinks, now being renamed to incDirectory). You can see examples of the old script in action here:

    http://www.flahorse.com/links/links.php
    http://www.prairienet.org/prairielinks/links.php
    http://www.tcld.net/links.php
    http://www.azbilliards.com/links/links.php

    For the redesign I have two goals. Firstly, I want to make the whole thing completely object oriented. Secondly, I want to provide a nice tidy exposed API so that more advanced users can integrate it tidily with their existing sites without having to mess around with the default template system.

    At the moment there are three principle classes:

    IncDirectory - represents the whole directory
    IncDirectoryCategory - represents a category within the IncDirectoryLink - represents a link in the directory
    IncDirectoryLinkIterator - a collection of links which can be iterated through using a next() method

    As an example of how things will work, you will be able to call the getLinkIterator() method on a category to return an iterator which can be used to cycle through all of the links in the category.

    This is where I'm running in to problems. Each IncDirectoryLink object needs to provide a getBreadcrumbs() method which returns some kind of structure (possible a IncDirectoryBreadcrumbs object, I haven't decided yet) which represents the category hierarchy connecting that link to the root of the directory. The problem is, a link object should only "know" its own details and the ID of the category it belongs to - and that is not enough information to build a full trail of breadcrumbs. As a result, the link object needs to store a reference to it's parent category (or even to the parent IncDirectory object which will have enough information to work out the breadcrumbs to the link). And this is what has bgot me stumped. How do I store a reference to an object in another PHP object (the default behaviour of PHP is to store a copy of the object, which is far too inefficient for my needs)? I've read the chapter on references in the PHP manual several times and it's about as clear as mud - sometimes you have an & by a variable, sometimes by an assignment, sometimes by a function call and sometimes by a function/method paramter. I need to pass the link object a reference to its parent category in the IncDirectoryLink constructor and store it in an object property, but I need to avoid storing a copy of the object at all costs (especially as I plan to serialized Link objects at some point, without serializing their parent category at the same time).

    Any help would be gratefully appreciated

    Cheers,

    Skunk

  2. #2
    SitePoint Guru
    Join Date
    Oct 2001
    Posts
    656
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Well, you can assign references to objects just like you'd do with other variables.

    Say you have the following two objects:
    PHP Code:
    $c1 = new IncDirectoryCategory;
    $c2 = new IncDirectoryCategory;

    // You can now do something like
    $c1->parent_category =& $c2

  3. #3
    SitePoint Columnist Skunk's Avatar
    Join Date
    Jan 2001
    Location
    Lawrence, Kansas
    Posts
    2,066
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    That simple? Cool

    Next question... what about if I want to write a function that returns a reference that is already stored as a reference in the object. Extending your example code:
    Code:
    $c1 = new IncDirectoryCategory;
    
    $c2 = new IncDirectoryCategory;
    
    // You can now do something like
    $c1->parent_category =& $c2;
    
    // I want a method in IncDirectorycategory that returns the reference intact
    class IncDirectoryCategory {
      //...
      function getParentCategory() {
        return $this->parent_category;
      }
    }
    // But I need to return the reference.

  4. #4
    SitePoint Guru
    Join Date
    Oct 2001
    Posts
    656
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    PHP Code:
    $c1 = new IncDirectoryCategory;

    $c2 = new IncDirectoryCategory;

    // You can now do something like
    $c1->parent_category =& $c2;

    // I want a method in IncDirectorycategory that returns the reference intact
    class IncDirectoryCategory {
      
    //...
      
    function &getParentCategory() {
        return 
    $this->parent_category;
      }
    }
    $c2 =& $c1->getParentCategory(); 

  5. #5
    Net Senior Citizen tommatthews's Avatar
    Join Date
    Apr 2001
    Location
    Sydney Australia
    Posts
    869
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    When do you expect to release IncDirectory Skunk?
    It sounds cool.


    affordable website design

    :: sydney australia ::

  6. #6
    SitePoint Evangelist
    Join Date
    Oct 2001
    Posts
    592
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    In addition to specifying references in function arguments and as return arguments, you will also want to instantiate objects using references:

    PHP Code:
    $c1 =& new IncDirectoryCategory 
    Normally, the constructor returns a copy of the object you just created. For example:

    PHP Code:
    class Object
    {
        var 
    $parent;

        function 
    Object(&$parent)
        {
            
    $this->parent =& $parent;
        }

        function &
    getParent()
        {
            return 
    $this->parent;
        }
    }


    // $parent is some other object

    // First (wrong) way:
    $object1 = new Object($parent);

    // The other (right) way:
    $object2 =& new Object($parent); 
    In the first case, a reference to $parent is assigned to the member variable of $object1 in the constructor, after which a COPY of that object is returned. This will lead to unexpected results.

    In the second case, the constructor returns a reference to the object you just created, which is what you want.

    Basically, whenever you use references in combination with assignments to '$this' in the constructor (or in methods called from the constructor), you have to call 'new' with an ampersand in front of it. But to be on the safe side you can do it all the time, with a small penalty.

    I only found this out myself recently, because I had those dreaded 'unexpected results'. Objects that ought to have references to other objects didn't have them for some reason; which I couldn't understand at all. Then I modified all '$a = new A' statements to '$a =& new A', and gone were my problems.

    Vincent
    Last edited by voostind; Aug 3, 2002 at 03:50.

  7. #7
    SitePoint Evangelist
    Join Date
    Oct 2001
    Posts
    592
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Oh, and about serializing

    Just remembered: you can't serialize objects with references in them. By default, the referenced objects will be 'pulled in' to the serialized data, which will lead to very big files, and can even lead to errors if two objects reference each other (an infinite loop).

    To serialize objects with references in them you have two options:
    - Implement the magic class methods '__sleep' and/or '__wakeup'.
    - Create your own serialization system.

    When PHP serializes an object, it calls __sleep on that object if that method exists. You can implement it to return an array of variable-names that you want serrialized:

    PHP Code:
    class Object
    {
        var 
    $reference;
        var 
    $data;

        function 
    __sleep()
        {
            return array(
    'data'); // omit $reference
        
    }

    Vice versa, you can implement __wakeup to initialize members that weren't serialized, like references or database links. This sucks big time, however. How would you implement __wakeup for the class above?

    PHP Code:
    class Object
    {
        var 
    $reference;
        var 
    $data;

        function 
    __wakeup()
        {
           
    // We'll have to restore $this->reference here,
           // but how do we get to that object? 
           // Using a global? (Yikes!)
           
    $this->reference =& ?? // You tell me!
        
    }

    Because I didn't quite like this (although __sleep is fine, in essence), and because there were problems with it (see the annotated PHP reference) I decided to implement serialization myself, something like this:

    PHP Code:
    class Object
    {
        var 
    $reference;
        var 
    $data;

        function 
    Object(&$reference)
        {
            
    $this->reference =& $reference;
        }

        function 
    create($arguments)
        {
            
    // Initialize the object from $arguments, e.g.:
            
    $this->data $arguments;
        }

        function 
    getPersistentData()
        {
            return 
    $this->data;
        }

        function 
    setPersistentData($data)
        {
            
    $this->data $data;
        }

    I separated the 'static' part from the 'dynamic' part. To fully create an object, I can write this:

    PHP Code:
    $object =& new Object($parent);
    if (
    file_exists('object.dat'))
    {
        
    // Read the contents of object.dat into $data
        
    $object->setPersistentData($data);
    }
    else
    {
        
    // Initialize the object's arguments in $arguments
        
    $object->create($arguments);
        
    // Execute other initialization methods
        // ...
        
    $data $object->getPersistentData();
        
    // Write $data to 'object.dat'

    So on construction of an object I pass it all the references it needs, after which I can either initialize it directly, or through persistent data. Note that the class Object here knows nothing about reading and writing to files, which is completely intentional. (What if I want to store persistent data in a database, for example?)

    The class Object I use myself is a bit smarter than this one. It provides a basic system for serializing objects, so I can use it as a base class for other classes that need serialization. Most of the time I don't need to override a single method to get those classes to serialize properly, but when required I can.

    Because reading and writing files is a lot of work to type in each time (and current(file('object.dat')) is very inefficient), I also wrote a static class Cache that does that for me, allowing me to write this:

    PHP Code:
    $object =& new Object($parent);
    if (!
    Cache::loadObject($object))
    {
        
    // Set some test arguments
        
    $arguments = array(
            
    'url' => 'http://www.sitepointforums.com',
            
    'subject' => 'Serializing objects'
        
    );
        
    // Create the object and save it in the cache
        
    $object->create($arguments);
        
    Cache::saveObject($object);

    This looks pretty clean and is easy to understand as well. For completeness, here is class Cache (simplified; the one I use is a bit different):

    PHP Code:
    class Cache
    {
        function 
    saveData($filename$data)
        {
            
    $fp fopen(APP_CACHE $filename'w');
            if (!
    $fp)
            {
                exit(
    "Could not open file $filename for writing!");
            }
            
    fputs($fpserialize($data));
            
    fclose($fp);
        }
        
        function 
    loadData($filename)
        {
            if (!
    file_exists(APP_CACHE $filename))
            {
                return 
    false;
            }
            
    $fp fopen(APP_CACHE $filename'r');
            if (!
    $fp)
            {
                exit(
    "Could not open file $filename for reading!");
            }
            
    $data fgets($fpCACHE_BUFFER_SIZE);
            
    fclose($fp);
            return 
    unserialize($data);
        }

        function 
    saveObject(&$object)
        {
            
    // Construct a filename from the object's name. For me, that's:
            
    $file $object->getObjectId();
            
    Cache::saveData($file$object->getPersistentData());
        }

        function 
    loadObject(&$object)
        {
            
    // Construct a filename from the object's name. For me, that's:
            
    $file $object->getObjectId();
            
    $data Cache::loadData($file);
            if (
    $data !== false)
            {
                
    $object->setPersistentData($data);
                return 
    true;
            }
            return 
    false;
        }
    }
    ?> 
    And that's all!

    Note that this class requires two constants: APP_CACHE, which is the directory where the cache files are stored, and CACHE_BUFFER_SIZE, which is the maximum number of bytes to read when loading cached data. When you serialize an object, all data in it is stored on a single line; so if you have a big object, you'll end up with a very long line. You must take care that CACHE_BUFFER_SIZE is big enough to read the biggest object from file.

    The class Cache I use myself is a bit bigger, because it uses localization (according to the language set by the system it reads and writes the correct localized cache files), and because it doesn't call 'exit' when something goes wrong, but 'throws an Exception' instead. Exceptions are other things I implemented for a system I'm working on, but they're of no interest here.

    Anyway, hopefully you can find some use for this!

    Vincent

  8. #8
    SitePoint Zealot
    Join Date
    Dec 2001
    Location
    Ontario, Canada
    Posts
    141
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    After reading this thread I had to play with referenced and objects ...
    and I found a limitation ( bug maybe ) when I tried this:
    PHP Code:
        class clsParent {
            var 
    $value "clsParent";
            var 
    $child null;
            function 
    clsParent() {
                
    $this->child = &new clsChild( &$this );
            }
        }
        
        class 
    clsChild {
            var 
    $value "clsChild";
            var 
    $parent null;
            function 
    clsChild( &$clsParent ) {
                if( 
    $clsParent != null ) {
                    
    $this->parent = &$clsParent;
                }
            }
            
        }
        
    $top = new clsParent();
        print( 
    $top->value "<br />\n" );
        print( 
    $top->child->value "<br />\n" );
        print( 
    $top->child->parent->value "<br />\n" );
        
    $top->value "blah";
        print( 
    $top->child->parent->value "<br />\n" ); // should be "blah" 
    it seems that the parent reference does not refer to the parent object, but rather a copy of it.
    Web Hound
    $x='010000010110001101101001011001000101001001100101011010010110011101101110';
    for($i=0;$i<strlen($x);$i+=8)print(chr(bindec(substr($x,$i,8))));

  9. #9
    SitePoint Zealot
    Join Date
    Dec 2001
    Location
    Ontario, Canada
    Posts
    141
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    btw I am not advocating that accessing the parent object in such an indirect method ( $obj->child->parent->value rather then $obj->value ) is good practice, but with in the child object you would be able to use $this->parent->value
    Web Hound
    $x='010000010110001101101001011001000101001001100101011010010110011101101110';
    for($i=0;$i<strlen($x);$i+=8)print(chr(bindec(substr($x,$i,8))));

  10. #10
    SitePoint Evangelist
    Join Date
    Oct 2001
    Posts
    592
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    It's not a bug, you made a mistake.

    Try changing this:

    PHP Code:
    $top = new clsParent(); 
    to this:

    PHP Code:
    $top =& new clsParent(); 


    Vincent

  11. #11
    SitePoint Zealot
    Join Date
    Dec 2001
    Location
    Ontario, Canada
    Posts
    141
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    ah, kool, thanks.
    but I didnt ( still dont ) see how that should make a difference, obviously it does
    Web Hound
    $x='010000010110001101101001011001000101001001100101011010010110011101101110';
    for($i=0;$i<strlen($x);$i+=8)print(chr(bindec(substr($x,$i,8))));

  12. #12
    SitePoint Zealot
    Join Date
    Dec 2001
    Location
    Ontario, Canada
    Posts
    141
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I get it now, after re-reading vincent's posts
    Web Hound
    $x='010000010110001101101001011001000101001001100101011010010110011101101110';
    for($i=0;$i<strlen($x);$i+=8)print(chr(bindec(substr($x,$i,8))));


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •