Jump to content

php magic functions


dikkeduif

Recommended Posts

Hey

 

Hopefully I'm posting in the correct forum.

 

Are the magic functions in php a curse or a blessing? For example, what about the __call() function? I've seen examples where people use this function to replace the accessors and mutators in classes. How great this may seem at first, after a while it gets confusing. If you're using an ide with auto complete, these acc/mut functions do not show up, when you're using phpdoc, these functions will not be included in the documentation etc. It may be useful for overloading functions though...

 

What is your experience with these magic functions? Which one do you use most, and how do you use them?

Link to comment
Share on other sites

I rarely use them. As you said, it can make your code hard to understand. That said, I have used in the past when I didn't want to redefine the whole of an interface, so mostly out of lazyness. Of course if you delegate to multiple other objects redefining all the subjects interfaces becomes a little silly, but I've yet to encounter a situation where I wasn't able to solve the problem in a different way.

 

Despite of that, I did create a MagicOverloader abstract class for just that purpose. I haven't used it a while, and I generally avoid using it, but sometimes it is just too easy. I can just add subjects to it (in order of precedence) using MagicOverloader::setSubject($obj) in the child class, and any undefined calls will be looked up in the $subjects array. If I do use it, I usually end up refactoring and getting rid of it.

 

Here it is anyway (word of warning; I haven't tested it since I last edited it):

 

<?php
/**
* Backbone_MagicOverloader
*
* Convenience tool for delegation to multiple subject objects that can be made type-safe.
*
* @package Backbone core tools
* @author John Kleijn
* @copyright Kleijn Webdesign en development Netherlands
* @abstract
*
*/
abstract class Backbone_MagicOverloader {

/**
* Ordered list of subjects. 
* @var Array
*/
private $subjects = array();

/**
* Name of exception class to use when calling undefined method.
* @var string
* @see Backbone_MagicOverloader::setExceptionClass()
*/
private $eClass = 'Backbone_MagicOverloaderException';

/**
* Subject type to accept. 
* @var string
* @see Backbone_MagicOverloader::setAcceptType()
*/
private $acceptType;



/**
 * Sets the type of subjects accepted.
 *
 * @param string $str
 */
protected function setAcceptType($str){
	$this->acceptType = $str;
}


/**
 * Adds subject object.
 *
 * @param object $subject
 * @throws Backbone_MagicOverloaderException
 */
protected function setSubject($subject){
	if(is_object($subject)){
		if($this->$acceptType && !$subject instanceof $this->acceptType){
			throw new Backbone_MagicOverloaderException(
				"Invalid subject type '".gettype($subject)."'. acceptType: '{$this->acceptType}'."
			);
		}
		$this->subjects[] = $subject;
		return;
	}
	throw new Backbone_MagicOverloaderException("Unable to set subject, not an object ($subject).");
}

/**
 * Sets exception type to throw when a method is not found.
 *
 * @param string $eClassStr
 */
protected function setExceptionClass($eClassStr){
	$this->eClass = $eClassStr;
}

/**
 * Directly forwards a call, even if defined by child class.
 *
 * @param string $method
 * @param array $args
 * @return mixed
 */
protected function forwardCall($method, $args = array()){
	return $this->__call($method, $args);
}

/**
 * Intercepts method calls that fell through child.
 *
 * @param string $name
 * @param array $params
 * @return mixed
 */
    protected function __call($name, $params){

        foreach($this->subjects as $subject){
       		if(method_exists($subject,$name)){
            	return call_user_func_array(
                	array($subject,$name),$params);
        	}
        }
        throw new $this->eClass('Call to undeclared method "'.get_class($this).'::'.$name.'".');
    }
    
    /**
     * Sets property directlty. Bypass iteration of subjects.
     *
     * @param string $property
     * @param mixed $value
     */
protected function bypassSet($property, $value){
	$this->$property = $value;
}
    
/**
 * Intercepts 'set' call that fell trough child. 
 *
 * @param string $property
 * @param string $value
 * @throws Backbone_MagicOverloaderException
 */
    protected function __set($property, $value){
    	
    	foreach($this->subjects as $subject){
        if(property_exists($subject, $property)){
            $subject->$property = $value;
            //Setting failed?
            if($subject->$property !== $value){
	    		throw new Backbone_MagicOverloaderException(
	    			'Writing property "'.$property.'" failed, optionally use MagicOverloader::bypassSubjects().'
	    		);
            }
            return;
        }
    	}
    	//Set property on this object (default php behaviour).
        $this->$property = $value;
    	
    }
    
    /**
     * Intercept 'get' call that fell trough child.
     *
     * @param unknown_type $property
     * @return unknown
     */
    protected function __get($property){
    	foreach($this->subjects as $subject){
        if(property_exists($subject, $property)){
            return $subject->$property;
        }
    	}
  		trigger_error('Undefined property '.get_class($this).'::'.$property, E_USER_NOTICE);
    return null;
    }
    
    /**
     * Check if property not set in child is set in one of the subjects.
     *
     * @param string $property
     * @return bool
     */
    protected function __isset($property){
    	foreach($this->subjects as $subject){
        if(property_exists($subject, $property)){
            return true;
        }
    	}
    	return false;
    }
    
    /**
     * Unsets ALL occurrences of $property in subjects.
     *
     * @param string $property
     */
    protected function __unset($property){
    	foreach($this->subjects as $subject){
        if(property_exists($subject, $property)){
            unset($this->subject->$property);
        }
    	}
    }
}
?>

Link to comment
Share on other sites

Apparently __get() and __set() are useful in the implementation of singleton patterns whilst avoiding factory methods.

 

class Singleton {
private static $props = array();

public function __construct() { }
public function __get($name) {
	if(array_key_exists($name, self::$props)) {
		return self::$props[$name];
	} //if
} // __get

public function __set($name, $value) {
	self::$props[$name] = $value;
} //__set

}

$a = new Singleton;
$b = new Singleton;
$a->property = "hello world";
print $b->property; // hello world

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.