The __get(), __set(), __isset(), __unset(), __call(), and __callStatic() magic methods provide a means to dynamically access properties and methods of an object. Said methods invoke automatically when using conventional accessor, mutator, and function syntax. With the exception of __callStatic(), all of these methods may only exist in an object’s context or local scope. Conversely, the __callStatic() method is defined as static and therefore accessed with the scope resolution operator, or paamayim nekudotayim(i.e. double colon).
This functionality is typically used as a fallback in case a requested property or method is not explicitly defined. Additionally, methods or properties that are inaccessible because they are defined private or protected will also invoke magic methods from a global scope. This is useful if an object needs to provide conditional access to a hidden property. For example, in the case of accessing a password property on a user object, said object may require an authenticator to ensure access is only possible under certain circumstances.
Property overloading
__set ( string $name , mixed $value )
Invoked when mutating a specified key-pair
__get ( string $name )
Retrieves the matching key value
__isset ( string $name )
Determines if a key-pair exists.
Only invoked as the subject of either the isset() or empty() calls.
__unset ( string $name )
Destroys a key-pair in memory so the memory can be re-allocated.
Only invoked as the subject of an unset() call.
The Object class below implements a constructor which accepts an array argument and assigns it to a private property. Then the array values are exposed dynamically with the magic methods mentioned earlier,
__get,
__set,
__isset, and
__unset respectively.
class Object
{
// local consolidated property array
protected $property = array();
function __construct( array $property = null )
{
if( is_array( $property ) )
$this->property = $property;
}
function __get( $name )
{
return $this->property[ $name ];
}
function __set( $name, $value )
{
$this->property[ $name ] = $value;
}
function __isset( $name )
{
return array_key_exists( $name, $this->property );
}
function __unset( $name )
{
unset( $this->property[ $name ] );
}
}
Example usage
// create array with default properties
$property = array( 'myprop' => 'someValue',
'myfield' => 2 );
// instantiate object, passing default property array into constructor
$object = new Object( $property );
// magic methods
// test __get procedure
echo $object->myprop;
// test __set procedure
$object->myprop = 'newValue';
echo $object->myprop;
// test __isset procedure
if( isset( $object->myprop ) )
echo 'myprop exists!';
// test __unset procedure
unset( $object->myprop )
var_dump( $object->myprop );
Output
someValue
newValue
myprop exists!
null
Method overloading
Much like property overloading, to overload a method means to implement magic methods such as
__call or
__callStatic. Both accept a method name and an array of parameters as arguments. A typical implementation just identifies and invokes an appropriate fallback function.
__call ( string $name , array $arguments )
Invoked when attempting to call a local method that is inaccessible
__callStatic ( string $name , array $arguments )
Invoked when attempting to call a static method that is inaccessible
The following class is a simple example of how the
__call() method can expose otherwise private methods globally.
class Object
{
private function Output()
{
echo __METHOD__ . ' successful!';
}
function __call( $methodName, $arguments )
{
echo 'Invoking __call(' . $methodName . ')';
if( method_exists( $this, $methodName ))
return $this->$methodName();
return null;
}
}
Example usage
$object = new Object();
$object->Output();
Output
Invoking __call(Output)
Output successful!
This example demonstrates that an inaccessible method may be exposed through
__call() magic. It is important to note that exposing otherwise hidden methods may pose a security risk and as such is only used for testing purposes.
The previous implementation uses dynamic variable resolution to determine the method to invoke. Typically this performs better than the common alternative,
call_user_func() shown below.
function __call( $methodName, $arguments )
{
if( method_exists( $this, $methodName ))
return call_user_func( array( $this, $methodName ) );
return null;
}
Despite performance limitations, sometimes it may be necessary to invoke methods using
call_user_func_array() instead, since it behaves similarly to
call_user_func() and accepts a parameter array that is passed into the callback’s arguments. Therein avoiding the necessity to modify the callback function to accept a parameter array like that passed into
__call().
function __call( $methodName, $arguments )
{
if( method_exists( $this, $methodName ))
return call_user_func_array( array( $this, $methodName ), $arguments );
return null;
}
Because of performance issues, some developers choose to hard-code method resolution up to a certain number of arguments, using
call_user_func_array() only as a last resort.
function __call( $methodName, $arguments )
{
switch( count( $arguments ))
{
case 0:
return $this->$methodName();
case 1:
return $this->$methodName( $arguments[0] );
case 2:
return $this->$methodName( $arguments[0],
$arguments[1] );
case 3:
return $this->$methodName( $arguments[0],
$arguments[1],
$arguments[2] );
case 4:
return $this->$methodName( $arguments[0],
$arguments[1],
$arguments[2],
$arguments[3] );
default:
return call_user_func_array( array( $this, $methodName ), $arguments );
break;
}
}
The previous method is anything but elegant, despite being considerably faster for methods with less than 5 arguments. Upon reviewing this code, a seasoned developer may immediately want to implement it using dynamically generated code and
eval() for execution. However, using
eval() is generally frowned upon because it performs even worse than
call_user_func_array() and lacks support on many platforms.
The example shown below is equivalent to the previous example except that it uses the
__callStatic() method instead of
__call().
class Object
{
private static function Output()
{
echo 'Static output successful!';
}
static function __callStatic( $methodName, $arguments )
{
switch( count( $arguments ))
{
case 0:
return self::$methodName();
case 1:
return self::$methodName( $arguments[0] );
case 2:
return self::$methodName( $arguments[0],
$arguments[1] );
case 3:
return self::$methodName( $arguments[0],
$arguments[1],
$arguments[2] );
case 4:
return self::$methodName( $arguments[0],
$arguments[1],
$arguments[2],
$arguments[3] );
default:
return call_user_func_array( __CLASSNAME__'::'.$methodName , $arguments );
break;
}
}
}
Example usage
Object::Output();
Output
Invoking __callStatic(Output)
Static output successful!
Happy coding