Search code examples
phpgetsetcallmagic-methods

What is the order of __set __get, accessing a public field and __call?


Assume we have this code:

class SomeClass{

    private $somePrivateField;


    public function __get($name){
        $function = "get".ucfirst($name);
        return $function;
    }

     public function __set($name,$value){
        $function = "set".ucfirst($name);
        return $function($value);
    }

    public function __call($name, $arguments) {
        //if function being called is getSomething
        //getThat private/protected field if exists and return
        //if not raise exception

        // similar for setSomething...
    }

}

This is a passage from some tutorial:

The __get() method accepts an argument that represents the name of the property being set. In the case of $obj->property, the argument will be property.

Our __get() method then converts this to getProperty, which matches the pattern we defined in the __call() method. What this means is that $obj->property will first try to set a public property with the same name, then go to __get(), then try to call the public method setProperty(), then go to __call(), and finally set the protected $_property.

So when I say somewhere in my code

$obj->property

I can understand it tried to access the public field first.. Why does it go to __get() first? Why not __set() ? Why does it go to __set() then?

Can someone please explain? Thanks...


Solution

    • __get() will only ever return a string comprising the name of a function that probably doesn't exist.
    • __set() actually calls the function whose name it constructs, but I'm having trouble determining why because...
    • __call() seemingly has to determine if the function it's calling is actually a "setter" or "getter" function, which is the entire point of __get() and __set() in the first place.
    • $obj->property is a non-sensical fragment of code that does not actually do anything on it's own.
    // assuming $obj->property is declared as private, or does not exist in the class.
    
    $var = $obj->property; // invokes __get('property')
    
    $obj->property = $var; // invokes __set('property', $var)
    
    $obj->someFunction($var1, $var2, ...);
    // invokes __call('someFunction', array($var1, $var2, ...)), but only if
    // the specified function is private, or otherwise does not exist.
    

    To re-write the example code so that it makes some semblance of sense:

    class SomeClass{
    
        private $somePrivateField;
    
        public function __get($name){
            if( isset($this->$name) ) {
                return $this->$name;
            } else {
                Throw new Exception("Object property $name does not exist.");
            }
        }
    
         public function __set($name,$value){
            if( isset($this->$name) ) {
                $this->$name = $value;
            } else {
                Throw new Exception("Object property $name does not exist.");
            }
        }
    }
    $obj = new SomeClass();
    $obj->somePrivateField = $var; // uses __set()
    $var = $obj->somePrivateField; // uses __get()
    

    Using __call() all is rarely necessary, certainly not for the given example.

    Or if you would like to be able to set/get private/public properties without having to explicitly declare them first:

    class SomeClass{
    
        private $properties = array();
    
        public function __get($name){
            if( isset($this->properties['name']) ) {
                return $this->properties['name'];
            } else {
                Throw new Exception("Object property $name does not exist.");
            }
        }
    
         public function __set($name,$value){
            $this->properties[$name] = $value;
        }
    
        // it's also a good idea to define __isset() and __unset() in this case as well
        public function __isset($name) {
            return isset($this->properties['name']);
        }
    
        public function __unset($name) {
            return unset($this->properties['name']);
        }
    }