If you are not focused enough, then don't use it.
Otherwise it is very powerful and you can build very complex code that handle a lot of things like zend framework did.
PHP所提供的重载
(overloading)是指动态地创建
类属性和方法。我们是通过魔术方法(magic methods)来实现的。
当调用当前环境下未定义或不可见的类属性或方法时,重载方法会被调用。本节后面将使用不可访问属性(inaccessible
properties)
和不可访问方法(inaccessible
methods)
来称呼这些未定义或不可见的类属性或方法。
所有的重载方法都必须被声明为 public。
Note:
这些魔术方法的参数都不能通过引用传递。
Note:
PHP中的
重载与其它绝大多数面向对象语言不同。传统的重载是用于提供多个同名的类方法,但各方法的参数类型和个数不同。
版本 | 说明 |
---|---|
5.3.0 | 新增 __callStatic()魔术方法。可见性未设置为 public 或未声明为 static 的时候会产生一个警告。 |
5.1.0 | 新增 __isset() 和 __unset() 两个魔术方法。 |
$name
) : bool$name
) : void在给不可访问属性赋值时,__set() 会被调用。
读取不可访问属性的值时,__get() 会被调用。
当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
当对不可访问属性调用 unset() 时,__unset() 会被调用。
参数 $name 是指要操作的变量名称。__set() 方法的 $value 参数指定了 $name 变量的值。
属性重载只能在对象中进行。在静态方法中,这些魔术方法将不会被调用。所以这些方法都不能被 声明为 static。从 PHP 5.3.0 起, 将这些魔术方法定义为 static 会产生一个警告。
Note:
因为 PHP 处理赋值运算的方式,__set() 的返回值将被忽略。类似的, 在下面这样的链式赋值中,__get() 不会被调用:
$a = $obj->b = 8;
Note:
在除 isset() 外的其它语言结构中无法使用重载的属性,这意味着当对一个重载的属性使用 empty() 时,重载魔术方法将不会被调用。
为避开此限制,必须将重载属性赋值到本地变量再使用 empty()。
Example #1 使用 __get(),__set(),__isset() 和 __unset() 进行属性重载
<?php
class PropertyTest {
/** 被重载的数据保存在此 */
private $data = array();
/** 重载不能被用在已经定义的属性 */
public $declared = 1;
/** 只有从类外部访问这个属性时,重载才会发生 */
private $hidden = 2;
public function __set($name, $value)
{
echo "Setting '$name' to '$value'\n";
$this->data[$name] = $value;
}
public function __get($name)
{
echo "Getting '$name'\n";
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
$trace = debug_backtrace();
trigger_error(
'Undefined property via __get(): ' . $name .
' in ' . $trace[0]['file'] .
' on line ' . $trace[0]['line'],
E_USER_NOTICE);
return null;
}
/** PHP 5.1.0之后版本 */
public function __isset($name)
{
echo "Is '$name' set?\n";
return isset($this->data[$name]);
}
/** PHP 5.1.0之后版本 */
public function __unset($name)
{
echo "Unsetting '$name'\n";
unset($this->data[$name]);
}
/** 非魔术方法 */
public function getHidden()
{
return $this->hidden;
}
}
echo "<pre>\n";
$obj = new PropertyTest;
$obj->a = 1;
echo $obj->a . "\n\n";
var_dump(isset($obj->a));
unset($obj->a);
var_dump(isset($obj->a));
echo "\n";
echo $obj->declared . "\n\n";
echo "Let's experiment with the private property named 'hidden':\n";
echo "Privates are visible inside the class, so __get() not used...\n";
echo $obj->getHidden() . "\n";
echo "Privates not visible outside of class, so __get() is used...\n";
echo $obj->hidden . "\n";
?>
以上例程会输出:
Setting 'a' to '1' Getting 'a' 1 Is 'a' set? bool(true) Unsetting 'a' Is 'a' set? bool(false) 1 Let's experiment with the private property named 'hidden': Privates are visible inside the class, so __get() not used... 2 Privates not visible outside of class, so __get() is used... Getting 'hidden' Notice: Undefined property via __get(): hidden in <file> on line 70 in <file> on line 29
在对象中调用一个不可访问方法时,__call() 会被调用。
在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。
$name 参数是要调用的方法名称。$arguments 参数是一个枚举数组,包含着要传递给方法 $name 的参数。
Example #2 使用 __call() 和 __callStatic() 对方法重载
<?php
class MethodTest
{
public function __call($name, $arguments)
{
// 注意: $name 的值区分大小写
echo "Calling object method '$name' "
. implode(', ', $arguments). "\n";
}
/** PHP 5.3.0之后版本 */
public static function __callStatic($name, $arguments)
{
// 注意: $name 的值区分大小写
echo "Calling static method '$name' "
. implode(', ', $arguments). "\n";
}
}
$obj = new MethodTest;
$obj->runTest('in object context');
MethodTest::runTest('in static context'); // PHP 5.3.0之后版本
?>
以上例程会输出:
Calling object method 'runTest' in object context Calling static method 'runTest' in static context
If you are not focused enough, then don't use it.
Otherwise it is very powerful and you can build very complex code that handle a lot of things like zend framework did.
It is important to understand that encapsulation can be very easily violated in PHP. for example :
class Object{
}
$Object = new Object();
$Objet->barbarianProperties = 'boom';
var_dump($Objet);// object(Objet)#1 (1) { ["barbarianProperties"]=> string(7) "boom" }
Hence it is possible to add a propertie out form the class definition.
It is then a necessity in order to protect encapsulation to introduce __set() in the class :
class Objet{
public function __set($name,$value){
throw new Exception ('no');
}
}
First off all, if you read this, please upvote the first comment on this list that states that "overloading" is a bad term for this behaviour. Because it REALLY is a bad name. You're giving new definition to an already accepted IT-branch terminology.
Second, I concur with all criticism you will read about this functionality. Just as naming it "overloading", the functionality is also very bad practice. Please don't use this in a production environment. To be honest, avoid to use it at all. Especially if you are a beginner at PHP. It can make your code react very unexpectedly. In which case you MIGHT be learning invalid coding!
And last, because of __get, __set and __call the following code executes. Which is abnormal behaviour. And can cause a lot of problems/bugs.
<?php
class BadPractice {
// Two real properties
public $DontAllowVariableNameWithTypos = true;
protected $Number = 0;
// One private method
private function veryPrivateMethod() { }
// And three very magic methods that will make everything look inconsistent
// with all you have ever learned about PHP.
public function __get($n) {}
public function __set($n, $v) {}
public function __call($n, $v) {}
}
// Let's see our BadPractice in a production environment!
$UnexpectedBehaviour = new BadPractice;
// No syntax highlighting on most IDE's
$UnexpectedBehaviour->SynTaxHighlighting = false;
// No autocompletion on most IDE's
$UnexpectedBehaviour->AutoCompletion = false;
// Which will lead to problems waiting to happen
$UnexpectedBehaviour->DontAllowVariableNameWithTyphos = false; // see if below
// Get, Set and Call anything you want!
$UnexpectedBehaviour->EveryPosibleMethodCallAllowed(true, 'Why Not?');
// And sure, why not use the most illegal property names you can think off
$UnexpectedBehaviour->{'100%Illegal+Names'} = 'allowed';
// This Very confusing syntax seems to allow access to $Number but because of
// the lowered visibility it goes to __set()
$UnexpectedBehaviour->Number = 10;
// We can SEEM to increment it too! (that's really dynamic! :-) NULL++ LMAO
$UnexpectedBehaviour->Number++;
// this ofcourse outputs NULL (through __get) and not the PERHAPS expected 11
var_dump($UnexpectedBehaviour->Number);
// and sure, private method calls LOOK valid now!
// (this goes to __call, so no fatal error)
$UnexpectedBehaviour->veryPrivateMethod();
// Because the previous was __set to false, next expression is true
// if we didn't had __set, the previous assignment would have failed
// then you would have corrected the typho and this code will not have
// been executed. (This can really be a BIG PAIN)
if ($UnexpectedBehaviour->DontAllowVariableNameWithTypos) {
// if this code block would have deleted a file, or do a deletion on
// a database, you could really be VERY SAD for a long time!
$UnexpectedBehaviour->executeStuffYouDontWantHere(true);
}
?>
I test those code:
<?php
class A {
public function test () {
static::who();
A::who();
self::who();
$this->who();
}
public static function __callStatic($a, $b) {
var_dump('A static');
}
public function __call($a, $b) {
var_dump('A call');
}
}
$a = new A;
$a->test();
?>
And the answer is
string(6) "A call"
string(6) "A call"
string(6) "A call"
string(6) "A call"
I think it means that __call will be called before __callStatic in an instance.
Using magic methods, especially __get(), __set(), and __call() will effectively disable autocomplete in most IDEs (eg.: IntelliSense) for the affected classes.
To overcome this inconvenience, use phpDoc to let the IDE know about these magic methods and properties: @method, @property, @property-read, @property-write.
/**
* @property-read name
* @property-read price
*/
class MyClass
{
private $properties = array('name' => 'IceFruit', 'price' => 2.49)
public function __get($name)
{
return $this->properties($name);
}
}
Note that you can enable "overloading" on a class instance at runtime for an existing property by unset()ing that property.
eg:
<?php
class Test {
public $property1;
public function __get($name)
{
return "Get called for " . get_class($this) . "->\$$name \n";
}
}
?>
The public property $property1 can be unset() so that it can be dynamically handled via __get().
<?php
$Test = new Test();
unset($Test->property1); // enable overloading
echo $Test->property1; // Get called for Test->$property1
?>
Useful if you want to proxy or lazy load properties yet want to have documentation and visibility in the code and debugging compared to __get(), __isset(), __set() on non-existent inaccessible properties.
Actually you dont need __set ect imo.
You could use it to set (pre-defined) protected (and in "some" cases private) properties . But who wants that?
(test it by uncommenting private or protected)
(pastebin because long ...) => http://pastebin.com/By4gHrt5
<?php
//How can implement __call function you understand better
class Employee {
protected $_name;
protected $_email;
protected $_compony;
public function __call($name, $arguments) {
$action = substr($name, 0, 3);
switch ($action) {
case 'get':
$property = '_' . strtolower(substr($name, 3));
if(property_exists($this,$property)){
return $this->{$property};
}else{
$trace = debug_backtrace();
trigger_error('Undefined property ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_NOTICE);
return null;
}
break;
case 'set':
$property = '_' . strtolower(substr($name, 3));
if(property_exists($this,$property)){
$this->{$property} = $arguments[0];
}else{
$trace = debug_backtrace();
trigger_error('Undefined property ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_NOTICE);
return null;
}
break;
default :
return FALSE;
}
}
}
$s = new Employee();
$s->setName('Nanhe Kumar');
$s->setEmail('nanhe.kumar@gmail.com');
echo $s->getName(); //Nanhe Kumar
echo $s->getEmail(); // nanhe.kumar@gmail.com
$s->setAge(10); //Notice: Undefined property setAge in
?>
A word of warning! It may seem obvious, but remember, when deciding whether to use __get, __set, and __call as a way to access the data in your class (as opposed to hard-coding getters and setters), keep in mind that this will prevent any sort of autocomplete, highlighting, or documentation that your ide mite do.
Furthermore, it beyond personal preference when working with other people. Even without an ide, it can be much easier to go through and look at hardcoded member and method definitions in code, than having to sift through code and piece together the method/member names that are assembled in __get and __set.
If you still decide to use __get and __set for everything in your class, be sure to include detailed comments and documenting, so that the people you are working with (or the people who inherit the code from you at a later date) don't have to waste time interpreting your code just to be able to use it.
Since this was getting me for a little bit, I figure I better pipe in here...
For nested calls to private/protected variables(probably functions too) what it does is call a __get() on the first object, and if you return the nested object, it then calls a __get() on the nested object because, well it is protected as well.
EG:
<?php
class A
{
protected $B
public function __construct()
{
$this->B = new B();
}
public function __get($variable)
{
echo "Class A::Variable " . $variable . "\n\r";
$retval = $this->{$variable};
return $retval;
}
}
class B
{
protected $val
public function __construct()
{
$this->val = 1;
}
public function __get($variable)
{
echo "Class B::Variable " . $variable . "\n\r";
$retval = $this->{$variable};
return $retval;
}
}
$A = new A();
echo "Final Value: " . $A->B->val;
?>
That will return something like...
Class A::Variable B
Class B::Variable val
Final Value: 1
It seperates the calls into $A->B and $B->val
Hope this helps someone
Be careful of __call in case you have a protected/private method. Doing this:
<?php
class TestMagicCallMethod {
public function foo()
{
echo __METHOD__.PHP_EOL;
}
public function __call($method, $args)
{
echo __METHOD__.PHP_EOL;
if(method_exists($this, $method))
{
$this->$method();
}
}
protected function bar()
{
echo __METHOD__.PHP_EOL;
}
private function baz()
{
echo __METHOD__.PHP_EOL;
}
}
$test = new TestMagicCallMethod();
$test->foo();
/**
* Outputs:
* TestMagicCallMethod::foo
*/
$test->bar();
/**
* Outputs:
* TestMagicCallMethod::__call
* TestMagicCallMethod::bar
*/
$test->baz();
/**
* Outputs:
* TestMagicCallMethod::__call
* TestMagicCallMethod::baz
*/
?>
..is probably not what you should be doing. Always make sure that the methods you call in __call are allowed as you probably dont want all the private/protected methods to be accessed by a typo or something.
Example of usage __call() to have implicit getters and setters
<?php
class Entity {
public function __call($methodName, $args) {
if (preg_match('~^(set|get)([A-Z])(.*)$~', $methodName, $matches)) {
$property = strtolower($matches[2]) . $matches[3];
if (!property_exists($this, $property)) {
throw new MemberAccessException('Property ' . $property . ' not exists');
}
switch($matches[1]) {
case 'set':
$this->checkArguments($args, 1, 1, $methodName);
return $this->set($property, $args[0]);
case 'get':
$this->checkArguments($args, 0, 0, $methodName);
return $this->get($property);
case 'default':
throw new MemberAccessException('Method ' . $methodName . ' not exists');
}
}
}
public function get($property) {
return $this->$property;
}
public function set($property, $value) {
$this->$property = $value;
return $this;
}
protected function checkArguments(array $args, $min, $max, $methodName) {
$argc = count($args);
if ($argc < $min || $argc > $max) {
throw new MemberAccessException('Method ' . $methodName . ' needs minimaly ' . $min . ' and maximaly ' . $max . ' arguments. ' . $argc . ' arguments given.');
}
}
}
class MemberAccessException extends Exception{}
class Foo extends Entity {
protected $a;
}
$foo = new Foo();
$foo->setA('some'); // outputs some
echo $foo->getA();
class Bar extends Entity {
protected $a;
/**
* Custom setter.
*/
public function setA($a) {
if (!preg_match('~^[0-9a-z]+$~i', $a)) {
throw new MemberAccessException('A can be only alphanumerical');
}
$this->a = $a;
return $this;
}
}
$bar = new Bar();
$bar->setA('abc123'); // ok
$bar->setA('[]/*@...'); // throws exception
?>
Note that __isset is not called on chained checks.
If isset( $x->a->b ) is executed where $x is a class with __isset() declared, __isset() is not called.
<?php
class demo
{
var $id ;
function __construct( $id = 'who knows' )
{
$this->id = $id ;
}
function __get( $prop )
{
echo "\n", __FILE__, ':', __LINE__, ' ', __METHOD__, '(', $prop, ') instance ', $this->id ;
return new demo( 'autocreated' ) ; // return a class anyway for the demo
}
function __isset( $prop )
{
echo "\n", __FILE__, ':', __LINE__, ' ', __METHOD__, '(', $prop, ') instance ', $this->id ;
return FALSE ;
}
}
$x = new demo( 'demo' ) ;
echo "\n", 'Calls __isset() on demo as expected when executing isset( $x->a )' ;
$ret = isset( $x->a ) ;
echo "\n", 'Calls __get() on demo without call to __isset() when executing isset( $x->a->b )' ;
$ret = isset( $x->a->b ) ;
?>
Outputs
Calls __isset() on demo as expected when executing isset( $x->a )
C:\htdocs\test.php:31 demo::__isset(a) instance demo
Calls __get() on demo without call to __isset() when executing isset( $x->a->b )
C:\htdocs\test.php:26 demo::__get(a) instance demo
C:\htdocs\test.php:31 demo::__isset(b) instance autocreated
If you want to make it work more naturally for arrays $obj->variable[] etc you'll need to return __get by reference.
<?php
class Variables
{
public function __construct()
{
if(session_id() === "")
{
session_start();
}
}
public function __set($name,$value)
{
$_SESSION["Variables"][$name] = $value;
}
public function &__get($name)
{
return $_SESSION["Variables"][$name];
}
public function __isset($name)
{
return isset($_SESSION["Variables"][$name]);
}
}
?>
I've written a brief, generic function for __get() and __set() that works well implementing accessor and mutator functions.
This allows the programmer to use implicit accessor and mutator methods when working with attribute data.
<?php
class MyClass
{
private $degrees
public function __get($name)
{
$fn_name = 'get_' . $name;
if (method_exists($this, $fn_name))
{
return $this->$fn_name();
}
else
{
return null;
}
}
public function __set($name, $value)
{
$fn_name = 'set_' . $name;
if (method_exists($this, $fn_name))
{
$this->$fn_name($value);
}
}
private function get_degrees()
{
return $this->degrees;
}
private function set_degrees($value)
{
$this->degrees = $value % 360;
if ($degrees < 0) $this->degrees += 360;
}
}
?>
Combining two things noted previously:
1 - Unsetting an object member removes it from the object completely, subsequent uses of that member will be handled by magic methods.
2 - PHP will not recursively call one magic method from within itself (at least for the same $name).
This means that if an object member has been unset(), it IS possible to re-declare that object member (as public) by creating it within your object's __set() method, like this:
<?php
class Foo
{
function __set($name, $value)
{
// Add a new (public) member to this object.
// This works because __set() will not recursively call itself.
$this->$name= $value;
}
}
$foo = new Foo();
// $foo has zero members at this point
var_dump($foo);
// __set() will be called here
$foo->bar = 'something'; // Calls __set()
// $foo now contains one member
var_dump($foo);
// Won't call __set() because 'bar' is now declared
$foo->bar = 'other thing';
?>
Also be mindful that if you want to break a reference involving an object member without triggering magic functionality, DO NOT unset() the object member directly. Instead use =& to bind the object member to any convenient null variable.
Here's a handy little routine to suggest properties you're trying to set that don't exist. For example:
Attempted to __get() non-existant property/variable 'operator_id' in class 'User'.
checking for operator and suggesting the following:
* id_operator
* operator_name
* operator_code
enjoy.
<?php
/**
* Suggests alternative properties should a __get() or __set() fail
*
* @param string $property
* @return string
* @author Daevid Vincent [daevid@daevid.com]
* @date 05/12/09
* @see __get(), __set(), __call()
*/
public function suggest_alternative($property)
{
$parts = explode('_',$property);
foreach($parts as $i => $p) if ($p == '_' || $p == 'id') unset($parts[$i]);
echo 'checking for <b>'.implode(', ',$parts)."</b> and suggesting the following:<br/>\n";
echo "<ul>";
foreach($this as $key => $value)
foreach($parts as $p)
if (stripos($key, $p) !== false) print '<li>'.$key."</li>\n";
echo "</ul>";
}
just put it in your __get() or __set() like so:
public function __get($property)
{
echo "<p><font color='#ff0000'>Attempted to __get() non-existant property/variable '".$property."' in class '".$this->get_class_name()."'.</font><p>\n";
$this->suggest_alternative($property);
exit;
}
?>
Be extra careful when using __call(): if you typo a function call somewhere it won't trigger an undefined function error, but get passed to __call() instead, possibly causing all sorts of bizarre side effects.
In versions before 5.3 without __callStatic, static calls to nonexistent functions also fall through to __call!
This caused me hours of confusion, hopefully this comment will save someone else from the same.
The following works on my installation (5.2.6 / Windows):
<?php
class G
{
private $_p = array();
public function __isset($k)
{
return isset($this->_p[$k]);
}
public function __get($k)
{
$v = NULL;
if (array_key_exists($k, $this->_p))
{
$v = $this->_p[$k];
}
else
{
$v = $this->{$k} = $this;
}
return $v;
}
public function __set($k, $v)
{
$this->_p[$k] = $v;
return $this;
}
}
$s = new G();
$s->A->B->C = 'FOO';
$s->X->Y->Z = array ('BAR');
if (isset($s->A->B->C))
{
print($s->A->B->C);
}
else
{
print('A->B->C is NOT set');
}
if (isset($s->X->Y->Z))
{
print_r($s->X->Y->Z);
}
else
{
print('X->Y->Z is NOT set');
}
// prints: FOOArray ( [0] => BAR )
?>
... have fun and ...
The __get overload method will be called on a declared public member of an object if that member has been unset.
<?php
class c {
public $p ;
public function __get($name) { return "__get of $name" ; }
}
$c = new c ;
echo $c->p, "\n" ; // declared public member value is empty
$c->p = 5 ;
echo $c->p, "\n" ; // declared public member value is 5
unset($c->p) ;
echo $c->p, "\n" ; // after unset, value is "__get of p"
?>
There isn't some way to overload a method when it's called as a reflection method:
<?php
class TestClass {
function __call($method, $args) {
echo "Method {$method} called with args: " . print_r($args, TRUE);
}
}
$class = new ReflectionClass("TestClass");
$method = $class->getMethod("myMehtod");
//Fatal error: Uncaught exception 'ReflectionException' with message 'Method myMethod' does not exist'
?>
Juan.
While PHP does not support true overloading natively, I have to disagree with those that state this can't be achieved trough __call.
Yes, it's not pretty but it is definately possible to overload a member based on the type of its argument. An example:
<?php
class A {
public function __call ($member, $arguments) {
if(is_object($arguments[0]))
$member = $member . 'Object';
if(is_array($arguments[0]))
$member = $member . 'Array';
$this -> $member($arguments);
}
private function testArray () {
echo "Array.";
}
private function testObject () {
echo "Object.";
}
}
class B {
}
$class = new A;
$class -> test(array()); // echo's 'Array.'
$class -> test(new B); // echo's 'Object.'
?>
Of course, the use of this is questionable (I have never needed it myself, but then again, I only have a very minimalistic C++ & JAVA background). However, using this general principle and optionally building forth on other suggestions a 'form' of overloading is definately possible, provided you have some strict naming conventions in your functions.
It would of course become a LOT easier once PHP'd let you declare the same member several times but with different arguments, since if you combine that with the reflection class 'real' overloading comes into the grasp of a good OO programmer. Lets keep our fingers crossed!
This is a misuse of the term overloading. This article should call this technique "interpreter hooks".
Small vocabulary note: This is *not* "overloading", this is "overriding".
Overloading: Declaring a function multiple times with a different set of parameters like this:
<?php
function foo($a) {
return $a;
}
function foo($a, $b) {
return $a + $b;
}
echo foo(5); // Prints "5"
echo foo(5, 2); // Prints "7"
?>
Overriding: Replacing the parent class's method(s) with a new method by redeclaring it like this:
<?php
class foo {
function new($args) {
// Do something.
}
}
class bar extends foo {
function new($args) {
// Do something different.
}
}
?>
Just to reinforce and elaborate on what DevilDude at darkmaker dot com said way down there on 22-Sep-2004 07:57.
The recursion detection feature can prove especially perilous when using __set. When PHP comes across a statement that would usually call __set but would lead to recursion, rather than firing off a warning or simply not executing the statement it will act as though there is no __set method defined at all. The default behaviour in this instance is to dynamically add the specified property to the object thus breaking the desired functionality of all further calls to __set or __get for that property.
Example:
<?php
class TestClass{
public $values = array();
public function __get($name){
return $this->values[$name];
}
public function __set($name, $value){
$this->values[$name] = $value;
$this->validate($name);
}
public function validate($name){
/*
__get will be called on the following line
but as soon as we attempt to call __set
again PHP will refuse and simply add a
property called $name to $this
*/
$this->$name = trim($this->$name);
}
}
$tc = new TestClass();
$tc->foo = 'bar';
$tc->values['foo'] = 'boing';
echo '$tc->foo == ' . $tc->foo . '<br>';
echo '$tc ' . (property_exists($tc, 'foo') ? 'now has' : 'still does not have') . ' a property called "foo"<br>';
/*
OUPUTS:
$tc->foo == bar
$tc now has a property called "foo"
*/
?>
Observe:
<?php
class Foo {
function __call($m, $a) {
die($m);
}
}
$foo = new Foo;
print $foo->{'wow!'}();
// outputs 'wow!'
?>
This method allows you to call functions with invalid characters.
PHP 5.2.1
Its possible to call magic methods with invalid names using variable method/property names:
<?php
class foo
{
function __get($n)
{
print_r($n);
}
function __call($m, $a)
{
print_r($m);
}
}
$test = new foo;
$varname = 'invalid,variable+name';
$test->$varname;
$test->$varname();
?>
I just don't know if it is a bug or a feature :)
<?php $myclass->foo['bar'] = 'baz'; ?>
When overriding __get and __set, the above code can work (as expected) but it depends on your __get implementation rather than your __set. In fact, __set is never called with the above code. It appears that PHP (at least as of 5.1) uses a reference to whatever was returned by __get. To be more verbose, the above code is essentially identical to:
<?php
$tmp_array = &$myclass->foo;
$tmp_array['bar'] = 'baz';
unset($tmp_array);
?>
Therefore, the above won't do anything if your __get implementation resembles this:
<?php
function __get($name) {
return array_key_exists($name, $this->values)
? $this->values[$name] : null;
}
?>
You will actually need to set the value in __get and return that, as in the following code:
<?php
function __get($name) {
if (!array_key_exists($name, $this->values))
$this->values[$name] = null;
return $this->values[$name];
}
?>
Please note that PHP5 currently doesn't support __call return-by-reference (see PHP Bug #30959).
Example Code:
<?php
class test {
public function &__call($method, $params) {
// Return a reference to var2
return $GLOBALS['var2'];
}
public function &actual() {
// Return a reference to var1
return $GLOBALS['var1'];
}
}
$obj = new test;
$GLOBALS['var1'] = 0;
$GLOBALS['var2'] = 0;
$ref1 =& $obj->actual();
$GLOBALS['var1']++;
echo "Actual function returns: $ref1 which should be equal to " . $GLOBALS['var1'] . "<br/>\n";
$ref2 =& $obj->overloaded();
$GLOBALS['var2']++;
echo "Overloaded function returns: $ref2 which should be equal to " . $GLOBALS['var2'] . "<br/>\n";
?>
Here's a useful class for logging function calls. It stores a sequence of calls and arguments which can then be applied to objects later. This can be used to script common sequences of operations, or to make "pluggable" operation sequences in header files that can be replayed on objects later.
If it is instantiated with an object to shadow, it behaves as a mediator and executes the calls on this object as they come in, passing back the values from the execution.
This is a very general implementation; it should be changed if error codes or exceptions need to be handled during the Replay process.
<?php
class MethodCallLog {
private $callLog = array();
private $object;
public function __construct($object = null) {
$this->object = $object;
}
public function __call($m, $a) {
$this->callLog[] = array($m, $a);
if ($this->object) return call_user_func_array(array(&$this->object,$m),$a);
return true;
}
public function Replay(&$object) {
foreach ($this->callLog as $c) {
call_user_func_array(array(&$object,$c[0]), $c[1]);
}
}
public function GetEntries() {
$rVal = array();
foreach ($this->callLog as $c) {
$rVal[] = "$c[0](".implode(', ', $c[1]).");";
}
return $rVal;
}
public function Clear() {
$this->callLog = array();
}
}
$log = new MethodCallLog();
$log->Method1();
$log->Method2("Value");
$log->Method1($a, $b, $c);
// Execute these method calls on a set of objects...
foreach ($array as $o) $log->Replay($o);
?>
This allows you to seeminly dynamically overload objects using plugins.
<?php
class standardModule{}
class standardPlugModule extends standardModule
{
static $plugptrs;
public $var;
static function plugAdd($name, $mode, $ptr)
{
standardPlugModule::$plugptrs[$name] = $ptr;
}
function __call($fname, $fargs)
{ print "You called __call($fname)\n";
$func = standardPlugModule::$plugptrs[$fname];
$r = call_user_func_array($func, array_merge(array($this),$fargs));
print "Done: __call($fname)\n";
return $r;
}
function dumpplugptrs() {var_dump(standardPlugModule::$plugptrs); }
}
class a extends standardPlugModule
{ function text() { return "Text"; } }
//Class P contained within a seperate file thats included
class p
{ static function plugin1($mthis, $r)
{ print "You called p::plugin1\n";
print_r($mthis);
print_r($r);
}
} a::plugAdd('callme', 0, array('p','plugin1'));
//Class P contained within a seperate file thats included
class p2
{ static function plugin2($mthis, $r)
{ print "You called p2::plugin2\n";
$mthis->callme($r);
}
} a::plugAdd('callme2', 0, array('p2','plugin2'));
$t = new a();
$testr = array(1,4,9,16);
print $t->text()."\n";
$t->callme2($testr);
//$t->dumpplugptrs();
?>
Will result in:
----------
Text
You called __call(callme2)
You called p2::plugin2
You called __call(callme)
You called p::plugin1
a Object
(
[var] =>
)
Array
(
[0] => 1
[1] => 4
[2] => 9
[3] => 16
)
Done: __call(callme)
Done: __call(callme2)
----------
This also clears up a fact that you can nest __call() functions, you could use this to get around the limits to __get() not being able to be called recursively.
By Design (http://bugs.php.net/bug.php?id=33998) you cannot call a getter from a getter or any function triggered by a getter:
<?php
class test
{
protected $_a = 6;
function __get($key) {
if($key == 'stuff') {
return $this->stuff();
} else if($key == 'a') {
return $this->_a;
}
}
function stuff()
{
return array('random' => 'key', 'using_getter' => 10 * $this->a);
}
}
$test = new test();
print 'this should be 60: '.$test->stuff['using_getter'].'<br/>'; // prints "this should be 60: 0"
// [[ Undefined property: test::$a ]] on /var/www/html/test.php logged.
print 'this should be 6: '.$test->a.'<br/>'; // prints "this should be 6: 6"
?>
for anyone who's thinking about traversing some variable tree
by using __get() and __set(). i tried to do this and found one
problem: you can handle couple of __get() in a row by returning
an object which can handle consequential __get(), but you can't
handle __get() and __set() that way.
i.e. if you want to:
<?php
print($obj->val1->val2->val3); // three __get() calls
?> - this will work,
but if you want to:
<?php
$obj->val1->val2 = $val; // one __get() and one __set() call
?> - this will fail with message:
"Fatal error: Cannot access undefined property for object with
overloaded property access"
however if you don't mix __get() and __set() in one expression,
it will work:
<?php
$obj->val1 = $val; // only one __set() call
$val2 = $obj->val1->val2; // two __get() calls
$val2->val3 = $val; // one __set() call
?>
as you can see you can split __get() and __set() parts of
expression into two expressions to make it work.
by the way, this seems like a bug to me, will have to report it.
Keep in mind that when your class has a __call() function, it will be used when PHP calls some other magic functions. This can lead to unexpected errors:
<?php
class TestClass {
public $someVar;
public function __call($name, $args) {
// handle the overloaded functions we know...
// [...]
// raise an error if the function is unknown, just like PHP would
trigger_error(sprintf('Call to undefined function: %s::%s().', get_class($this), $name), E_USER_ERROR);
}
}
$obj = new TestClass();
$obj->someVar = 'some value';
echo $obj; //Fatal error: Call to undefined function: TestClass::__tostring().
$serializedObj = serialize($obj); // Fatal error: Call to undefined function: TestClass::__sleep().
$unserializedObj = unserialize($someSerializedTestClassObject); // Fatal error: Call to undefined function: TestClass::__wakeup().
?>
Php 5 has a simple recursion system that stops you from using overloading within an overloading function, this means you cannot get an overloaded variable within the __get method, or within any functions/methods called by the _get method, you can however call __get manualy within itself to do the same thing.