SitePoint Sponsor

User Tag List

Results 1 to 11 of 11
  1. #1
    SitePoint Evangelist hessodreamy's Avatar
    Join Date
    Apr 2005
    Location
    uk
    Posts
    525
    Mentioned
    3 Post(s)
    Tagged
    0 Thread(s)

    Using magic __get

    I've got a class which has a lot of properties, an I wanted to define getters that would selectively access the database (ie if we've set the property, return it, otherwise get a bunch of data from the database and store it somewhere for other class getters to access).

    I stumbled upon using __get which works pretty well for this, however it only works when the property has not been defined. I like to define my properties. I think it's neater. Even if I don't want to write a ream of near-identical getters for them.

    Is it possible to use a similar functionality that works when properties have been defined but not set?

    eg This will work as desired
    Code:
    class AutoGetter
    {
    	public function __get($name)
    	{
    		//do some things that justify the use of magic functions
    		return $name;
    	}
    }
    
    $o = new AutoGetter();
    echo $o->nonExistantVariable;
    This will echo:
    nonExistantVariable

    However, if I define the property
    Code:
    class AutoGetter
    {
    	public $nonExistantVariable;
    	
    	public function __get($name)
    	{
    		//do some things that justify the use of magic functions
    		return $name;
    	}
    }
    
    $o = new AutoGetter();
    echo $o->nonExistantVariable;
    This will output null, because the property $nonExistantVariable has been defined, even if it hasn't been set.

    What is it I'm looking for here? Instead of magic functions would I be better off using my constructor to dynamically create a bunch of getters?

  2. #2
    SitePoint Addict eanimator's Avatar
    Join Date
    Sep 2005
    Posts
    396
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    class AutoGetter
    {
    public $nonExistantVariable;

    public function __get($key)
    {
    if($this->$key != null)
    return $this->$key;
    }
    }

  3. #3
    SitePoint Evangelist hessodreamy's Avatar
    Join Date
    Apr 2005
    Location
    uk
    Posts
    525
    Mentioned
    3 Post(s)
    Tagged
    0 Thread(s)
    Thanks, but the problem I'm having with this approach is that the magic get would not be called for the property nonExistantVariable. The magic get is only called for non-defined properties, and I was looking for a way around it, or maybe a suggestion of a different approach.

  4. #4
    Utopia, Inc. silver trophy
    ScallioXTX's Avatar
    Join Date
    Aug 2008
    Location
    The Netherlands
    Posts
    9,095
    Mentioned
    153 Post(s)
    Tagged
    2 Thread(s)
    You could work around this by creating protected or private variables in the class, like this:

    PHP Code:
    class AutoGetter
    {
      protected 
    $nonExistantVariable;
      
      public function 
    __get($name)
      {
        return 
    $name;
      }
    }

    $a = new AutoGetter;
    var_dump($a->nonExistantVariable); // string 'nonExistantVariable' (length=19) 
    This returns 'nonExistantVariable', because even though it does exist it's not publicly visible so you can't access it and __get will fire instead
    At the same time you can call it from the class itself, so you can use it as a magic __get and __set

    PHP Code:
    class AutoGetter
    {
      protected 
    $nonExistantVariable;
      
      public function 
    __get($name)
      {
        
    // this will *not* call __get, because the variable is
        // visible from the scope of this class
        // just not from outside the class
        
    return isset($this->$name) ? $this->$name null;
      }

      public function 
    __set($name$value)
      {
        
    // this will *not* call __set, because the variable is
        // visible from the scope of this class
        // just not from outside the class
        
    $this->$name $value;
      }

    if you want to make sure only defined variables can be set in __set, you can use property_exists, i.e.,

    PHP Code:
    public function __set($name$value)
    {
      if (!
    property_exists($this$name))
        throw new 
    Exception('Class "'.__CLASS__.'" does not have a property "'.$name.'"');
      
    $this->$name $value;

    and the same goes for __get of course
    Rémon - Hosting Advisor

    SitePoint forums will switch to Discourse soon! Make sure you're ready for it!

    Minimal Bookmarks Tree
    My Google Chrome extension: browsing bookmarks made easy

  5. #5
    SitePoint Evangelist hessodreamy's Avatar
    Join Date
    Apr 2005
    Location
    uk
    Posts
    525
    Mentioned
    3 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by ScallioXTX View Post
    You could work around this by creating protected or private variables in the class
    Yes, that does seem to solve a lot of the problems. However:
    Quote Originally Posted by ScallioXTX View Post
    At the same time you can call it from the class itself, so you can use it as a magic __get and __set
    What did you mean by this? From my tests, attempting to call $this->nonExistantVariable from within the class does NOT call the magic function. It also doesn't call the magic function when called from inheriting classes, eg
    Code:
    class AutoGetter
    {
    	protected $nonExistantVariable;
      
    	public function __get($name)
    	{
    		echo "<DIV>GETTING $name using magic function</DIV>";
    		return isset($this->$name)? $this->$name : null;
    	}
    }
    
    class ExtendAutoGetter extends AutoGetter
    {
    	public function getNonExistantVariable()
    	{
    		return $this->nonExistantVariable;
    	}
    
    }
    $ag = new ExtendAutoGetter();
    var_dump( $ag->getNonExistantVariable());
    This does not call the magic get when the property is accessed via a member function. As I'm planning on using this on a much-extended class this would mean I'd have to be very careful with a lot of my code. Am I missing something here?

  6. #6
    Utopia, Inc. silver trophy
    ScallioXTX's Avatar
    Join Date
    Aug 2008
    Location
    The Netherlands
    Posts
    9,095
    Mentioned
    153 Post(s)
    Tagged
    2 Thread(s)
    Hm, indeed. If you want the class itself to call the magic __set and __get from inside the class as well then it doesn't work.
    In that case you could go for underscores in the properties, and call them without the properties. Something like

    PHP Code:
    class AutoGetter
    {
      protected 
    $_someVar;

      public function 
    __get($name)
      {
        
    $var '_'.$name;
        if (!
    property_exists($this$var))
          throw new 
    Exception('Class "'.__CLASS__.'" does not have a property "'.$name.'"');
        return 
    $this->$var;
      }

      public function 
    __set($name$value)
      {
        
    $var '_'.$name;
        if (!
    property_exists($this$var))
          throw new 
    Exception('Class "'.__CLASS__.'" does not have a property "'.$name.'"');
        
    $this->$var $value;
      }
    }

    $a = new AutoGetter;
    $a->someVar 1
    this way $a->someVar works from outside the class as well as inside class, but internally uses $a->_someVar
    Rémon - Hosting Advisor

    SitePoint forums will switch to Discourse soon! Make sure you're ready for it!

    Minimal Bookmarks Tree
    My Google Chrome extension: browsing bookmarks made easy

  7. #7
    SitePoint Evangelist hessodreamy's Avatar
    Join Date
    Apr 2005
    Location
    uk
    Posts
    525
    Mentioned
    3 Post(s)
    Tagged
    0 Thread(s)
    Oi! This sounds like it's going to get complicated. Which is usually a sign that I shouldn't be doing it this way!

    Perhaps if I explain what I'm trying to achieve someone can point me in the right direction.

    I have a base Product class, which will be extended by, for example ShoppingCartProduct, and most likely many more.

    The Product class has a lot of properties, a few core ones being name, price, description, delivery_time.

    I wanted the application side not to necessarily need to deal with the explicit retrieval of product details from the database, so I wanted to smoothly let the class grab required data from the database as it needs it. What I wanted to do was, when a property is accessed, return the property if it has been set, otherwise, grab a whole bunch of fields from the database, return the one that is being accessed, but store the other fields for use in the future, so we can cut out single field database calls.

    eg
    Code:
    class Product
    {
    	/*define fields that can be populated from the database
    	this way a getter can query the database if something has not been set, and store the returned result for other getters
    	*/
    	private $haveRunQuery=false;
    	private  $dbFields = array('id', 'name', 'price', 'description', 'delivery_time', 'stock', 'vat_rate');
    	private $dbValues = array();
    
    	public function __get($name)
    	{
    		//if property has been set in the object, return it
    		if(isset($this->$name))
    		{
    			return $this->$name;
    		}
    		
    		else if(!array_key_exists($name,$this->dbValues))
    		{
    			$this->getAllFieldsFromDB();
    		}
    		
    		//if, after all that, the value is available, set it in the object
    		if(array_key_exists($name,$this->dbValues))
    		{
    			$this->$name=$this->dbValues[$name];
    			return $this->dbValues[$name];
    		}
    		//if we've got this far something's gone wrong and we've attempted to access a nonexistant property		
    		trigger_error("Attempting to access non-existant product property ".$name);
    		return false;
    	
    	}
    	
    	protected function getAllFieldsFromDB()
    	{
    		//get all applicable db fields into an array for all the getters to access db values that haven't been set
    		$q = query("SELECT 'id', 'name', 'price', 'description', 'delivery_time', 'stock', 'vat_rate' FROM products WHERE id=".$this->id);
    		if(!$q or mysql_num_rows($q)<1) return false;
    		$row = mysql_fetch_assoc($q);
    		//put the
    		foreach($row as $var=>$val)
    		{
    			$this->dbValues[$var]=$val;
    		}
    		$this->haveRunQuery=true;
    		return true;
    	}
    }
    Using magic __get seemed at first like a neat way to make this work. Is it perhaps not the best or right way? Another idea was to use my constructor to dynamically create getter functions (eg getId(), getName()), and in my code just call the function. But is it even possible to dynamically create class functions? I guess I could use magic function _call()?

  8. #8
    Utopia, Inc. silver trophy
    ScallioXTX's Avatar
    Join Date
    Aug 2008
    Location
    The Netherlands
    Posts
    9,095
    Mentioned
    153 Post(s)
    Tagged
    2 Thread(s)
    It looks like you're trying to reinvent ActiveRecord. Maybe you can have a look at any of the packages that are mentioned in that article and see if anything suits your needs?
    I personally love the AR implementation in Yii, but I don't think you can easily use that independently from the rest of the framework.

    If you go the way of building it yourself, I would not make the fields configurable, but request them from the database using a DESCRIBE or SHOW CREATE query. To speed things up you could cache this in memcache or apc or similar if you like. Configuring manually would be faster but is more error prone (and frankly quite annoying).

    And I would not go the way of dynamically adding methods to a class using __call, all this will make the whole class too "magic" IMO. Of course others will disagree on that.
    Rémon - Hosting Advisor

    SitePoint forums will switch to Discourse soon! Make sure you're ready for it!

    Minimal Bookmarks Tree
    My Google Chrome extension: browsing bookmarks made easy

  9. #9
    SitePoint Evangelist hessodreamy's Avatar
    Join Date
    Apr 2005
    Location
    uk
    Posts
    525
    Mentioned
    3 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by ScallioXTX View Post
    It looks like you're trying to reinvent ActiveRecord.
    I keep doing that. I keep playing with design patterns that make database access easier, and they keep turning into ActiveRecord!

  10. #10
    Utopia, Inc. silver trophy
    ScallioXTX's Avatar
    Join Date
    Aug 2008
    Location
    The Netherlands
    Posts
    9,095
    Mentioned
    153 Post(s)
    Tagged
    2 Thread(s)
    Quote Originally Posted by hessodreamy View Post
    I keep doing that. I keep playing with design patterns that make database access easier, and they keep turning into ActiveRecord!
    For good reason I'd say. ActiveRecord is a very sweet pattern
    Rémon - Hosting Advisor

    SitePoint forums will switch to Discourse soon! Make sure you're ready for it!

    Minimal Bookmarks Tree
    My Google Chrome extension: browsing bookmarks made easy

  11. #11
    Twitter: @AnthonySterling silver trophy AnthonySterling's Avatar
    Join Date
    Apr 2008
    Location
    North-East, UK.
    Posts
    6,111
    Mentioned
    3 Post(s)
    Tagged
    0 Thread(s)
    I much prefer the Data Mapper pattern, I can't say I'm a fan of Active Record at all.
    @AnthonySterling: I'm a PHP developer, a consultant for oopnorth.com and the organiser of @phpne, a PHP User Group covering the North-East of England.


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
  •