PHP Conference Japan 2024

重载

PHP 中的重载提供了一种动态创建属性和方法的方法。这些动态实体通过可以在类中为各种操作类型建立的魔术方法进行处理。

当与尚未声明或在当前作用域中不可见的属性或方法交互时,会调用重载方法。本节的其余部分将使用术语不可访问的属性不可访问的方法来指代声明和可见性的这种组合。

所有重载方法都必须定义为public

注意:

这些魔术方法的任何参数都不能通过引用传递

注意:

PHP 对重载的解释与大多数面向对象的语言不同。传统上,重载提供了具有相同名称但参数数量和类型不同的多个方法的能力。

属性重载

public __set(string $name, mixed $value): void
public __get(string $name): mixed
public __isset(string $name): bool
public __unset(string $name): void

__set() 在写入不可访问(受保护或私有)或不存在的属性的数据时运行。

__get() 用于从不可访问(受保护或私有)或不存在的属性读取数据。

__isset() 由在不可访问(受保护或私有)或不存在的属性上调用isset()empty() 触发。

__unset() 在对不可访问(受保护或私有)或不存在的属性使用unset() 时被调用。

$name 参数是要交互的属性的名称。__set() 方法的$value 参数指定应将$name 属性设置为的值。

属性重载仅在对象上下文中有效。这些魔术方法不会在静态上下文中触发。因此,这些方法不应声明为静态。如果其中一个魔术重载方法被声明为static,则会发出警告。

注意:

__set() 的返回值被忽略,因为 PHP 处理赋值运算符的方式。类似地,当像这样将赋值链接在一起时,永远不会调用__get()

 $a = $obj->b = 8; 

注意:

PHP 不会从同一个重载方法内部调用重载方法。这意味着,例如,在__get() 内部写入return $this->foo 将返回null 并引发E_WARNING(如果未定义foo 属性),而不是第二次调用__get()。但是,重载方法可能会隐式地调用其他重载方法(例如,__set() 触发__get())。

示例 #1 通过__get()__set()__isset()__unset() 方法重载属性

<?php
class PropertyTest
{
/** 重载数据的位置。 */
private $data = array();

/** 已声明的属性不使用重载。 */
public $declared = 1;

/** 仅在类外部访问时使用此属性的重载。 */
private $hidden = 2;

public function
__set($name, $value)
{
echo
"设置 '$name' 为 '$value'\n";
$this->data[$name] = $value;
}

public function
__get($name)
{
echo
"获取 '$name'\n";
if (
array_key_exists($name, $this->data)) {
return
$this->data[$name];
}

$trace = debug_backtrace();
trigger_error(
'通过 __get() 获取未定义的属性: ' . $name .
' 在 ' . $trace[0]['file'] .
' 的第 ' . $trace[0]['line'] . ' 行',
E_USER_NOTICE);
return
null;
}

public function
__isset($name)
{
echo
'是否设置了 '$name'?\n";
return isset(
$this->data[$name]);
}

public function
__unset($name)
{
echo
"取消设置 '$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
"让我们尝试名为 'hidden' 的私有属性:\n";
echo
"私有属性在类内部可见,因此不使用 __get()...\n";
echo
$obj->getHidden() . "\n";
echo
"私有属性在类外部不可见,因此使用 __get()...\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

方法重载

public __call(字符串 $name, 数组 $arguments): 混合
public static __callStatic(字符串 $name, 数组 $arguments): 混合

__call() 在对象上下文中调用不可访问的方法时被触发。

__callStatic() 在静态上下文中调用不可访问的方法时被触发。

$name 参数是被调用的方法的名称。$arguments 参数是一个枚举数组,包含传递给 $name 方法的参数。

示例 #2 通过 __call()__callStatic() 方法重载方法

<?php
class MethodTest
{
public function
__call($name, $arguments)
{
// 注意:$name 的值区分大小写。
echo "调用对象方法 '$name' "
. implode(', ', $arguments). "\n";
}

public static function
__callStatic($name, $arguments)
{
// 注意:$name 的值区分大小写。
echo "调用静态方法 '$name' "
. implode(', ', $arguments). "\n";
}
}

$obj = new MethodTest;
$obj->runTest('在对象上下文中');

MethodTest::runTest('在静态上下文中');
?>

以上示例将输出

Calling object method 'runTest' in object context
Calling static method 'runTest' in static context
添加注释

用户贡献的注释 27 条注释

376
theaceofthespade at gmail dot com
12 年前
警告!这似乎很明显,但请记住,在决定是否使用 __get、__set 和 __call 作为访问类中数据的方法(而不是硬编码 getter 和 setter)时,请记住这将阻止任何自动完成、突出显示或您的 IDE 可能提供的文档。

此外,在与其他人合作时,这超出了个人喜好。即使没有 IDE,遍历并查看代码中硬编码的成员和方法定义,也比不得不筛选代码并将 __get 和 __set 中组装的方法/成员名称拼凑在一起要容易得多。

如果您仍然决定对类中的所有内容都使用 __get 和 __set,请务必包含详细的注释和文档,以便您正在与之合作的人员(或以后从您那里继承代码的人员)不必浪费时间解释您的代码才能使用它。
272
匿名
8 年前
首先,如果您阅读了本文,请为本列表中的第一条评论点赞,该评论指出“重载”对于这种行为来说是一个不好的术语。因为它确实是一个不好的名字。您正在为一个已经得到认可的 IT 分支术语赋予新的定义。

其次,我同意您将阅读到的关于此功能的所有批评。就像将它命名为“重载”一样,此功能也是非常不好的实践。请不要在生产环境中使用它。老实说,根本不要使用它。尤其是在您是 PHP 初学者的情况下。它可能使您的代码产生非常意外的反应。在这种情况下,您可能会学习到无效的编码!

最后,由于 __get、__set 和 __call,以下代码得以执行。这是一种异常行为。并且可能导致很多问题/错误。



<?php

class BadPractice {
// 两个真实的属性
public $DontAllowVariableNameWithTypos = true;
protected
$Number = 0;
// 一个私有方法
private function veryPrivateMethod() { }
// 以及三个非常神奇的方法,它们会让所有你学到的关于 PHP 的知识都变得不一致。
// 与你曾经学到的所有关于 PHP 的知识都格格不入。
public function __get($n) {}
public function
__set($n, $v) {}
public function
__call($n, $v) {}
}

// 让我们在生产环境中看看我们的 BadPractice!
$UnexpectedBehaviour = new BadPractice;

// 在大多数 IDE 中没有语法高亮显示
$UnexpectedBehaviour->SynTaxHighlighting = false;

// 在大多数 IDE 中没有自动完成
$UnexpectedBehaviour->AutoCompletion = false;

// 这将导致问题等待发生
$UnexpectedBehaviour->DontAllowVariableNameWithTyphos = false; // 参见下文

// 获取、设置和调用任何你想要的!
$UnexpectedBehaviour->EveryPosibleMethodCallAllowed(true, 'Why Not?');

// 当然,为什么不使用你能想到的最不合法的属性名称呢?
$UnexpectedBehaviour->{'100%Illegal+Names'} = 'allowed';

// 这种非常混乱的语法似乎允许访问 $Number,但由于
// 可见性降低,它会转到 __set()。
$UnexpectedBehaviour->Number = 10;

// 我们似乎也可以递增它!(这真的很动态!:-) NULL++ LMAO
$UnexpectedBehaviour->Number++;

// 当然,这会输出 NULL(通过 __get)而不是可能预期的 11
var_dump($UnexpectedBehaviour->Number);

// 当然,私有方法调用现在看起来有效了!
// (这会转到 __call,所以没有致命错误)
$UnexpectedBehaviour->veryPrivateMethod();

// 因为前面被 __set 设置为 false,所以下一个表达式为 true
// 如果我们没有 __set,前面的赋值将失败
// 然后你就会纠正错别字,并且这段代码将不会
// 被执行。(这确实可能是一个很大的痛苦)
if ($UnexpectedBehaviour->DontAllowVariableNameWithTypos) {
// 如果此代码块删除了一个文件,或者对
// 数据库进行删除操作,你可能会非常难过很长时间!
$UnexpectedBehaviour->executeStuffYouDontWantHere(true);
}
?>
187
egingell at sisna dot com
17 年前
一个小词汇注释:这*不是*“重载”,这是“重写”。

重载:像这样多次声明一个函数,但参数集不同
<?php

function foo($a) {
return
$a;
}

function
foo($a, $b) {
return
$a + $b;
}

echo
foo(5); // 输出 "5"
echo foo(5, 2); // 输出 "7"

?>

重写:通过像这样重新声明来替换父类的函数
<?php

class foo {
function new(
$args) {
// 做一些事情。
}
}

class
bar extends foo {
function new(
$args) {
// 做一些不同的事情。
}
}

?>
66
Anonymous
9 年前
使用魔法方法,尤其是 __get()、__set() 和 __call() 将有效地禁用大多数 IDE(例如:IntelliSense)中受影响类的自动完成功能。

为了克服这种不便,使用 phpDoc 让 IDE 了解这些魔法方法和属性:@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);
}
}
71
pogregoire##live.fr
8 年前
重要的是要理解,封装在 PHP 中很容易被破坏。例如
class Object{

}

$Object = new Object();
$Objet->barbarianProperties = 'boom';

var_dump($Objet);// object(Objet)#1 (1) { ["barbarianProperties"]=> string(7) "boom" }

因此,可以从类定义中添加一个属性。
然后,为了保护封装,有必要在类中引入 __set()

class Objet{
public function __set($name,$value){
throw new Exception ('no');
}
}
2
turabgarip at gmail dot com
3 年前
我同意“重载”对于此功能来说是一个错误的术语。但我不同意此功能完全错误。你也可以用正确的代码做“不好的实践”。

例如,__call() 非常适用于我正在使用的外部集成实现,用于将调用中继到不需要本地实现的 SOAP 方法。因此,您不必编写“空主体”函数。假设您连接的 SOAP 服务有一个“库存更新”方法。您只需将产品代码和库存数量传递给 SOAP 即可。

<?php

class Inventory {

public
__construct() {
// 配置并连接到 SOAP 服务
$this->soap = new SoapClient();
}

public
__call($soapMethod, $params) {
$this->soap->{$soapMethod}(params);
}
}

// 现在您可以使用任何 SOAP 方法而无需包装器
$stock = new Inventory();
$stock->updatePrice($product_id, 20);
$stock->saveProduct($product_info);

?>

当然,您需要参数映射,但在我看来,它比拥有大量镜像方法要好得多,例如

<?php

class Inventory {

public function
updateStock($product_id, $stock) {
$soapClient->updateStock($product_id, $stock;
}
public function
updatePrice($product_id, $price) {
$soapClient->updateStock($product_id, $price;
}
// ...
}

?>
19
Ant P.
15 年前
在使用 __call() 时要格外小心:如果您在某处打错了函数调用,它不会触发未定义函数错误,而是会传递给 __call(),这可能会导致各种奇怪的副作用。
在 5.3 之前的版本中,如果没有 __callStatic,对不存在函数的静态调用也会落到 __call!
这让我困惑了几个小时,希望这条评论能帮助其他人避免同样的问题。
11
gabe at fijiwebdesign dot com
10 年前
请注意,您可以通过 unset() 已有属性来在运行时为现有属性启用类的实例的“重载”。

例如

<?php
class Test {

public
$property1;

public function
__get($name)
{
return
"Get called for " . get_class($this) . "->\$$name \n";
}

}
?>

公共属性 `$property1` 可以使用 `unset()` 函数清除,以便通过 `__get()` 方法进行动态处理。

<?php
$Test
= new Test();
unset(
$Test->property1); // 启用重载
echo $Test->property1; // Get called for Test->$property1
?>

如果您希望代理或延迟加载属性,同时希望在代码和调试中拥有文档和可见性,而不是对不存在的不可访问属性使用 `__get()`、`__isset()`、`__set()`,则此方法很有用。
3
johannes dot kingma at gmail dot com
3 年前
`__get` 函数的一个有趣用途是属性/函数的混合使用,即属性和函数使用相同的名称。

示例
<?php
class prop_fun {
private
$prop = 123;

public function
__get( $property ) {
if(
property_exists( $this, $property ) ){
return
$this-> $property;
}
throw new
Exception( "no such property $property." );
}
public function
prop() {
return
456;
}
}

$o = new prop_fun();

echo
$o-> prop . '<br>' . PHP_EOL;
echo
$o-> prop() . '<br>' . PHP_EOL;
?>

这将输出 123 和 456。这看起来像一个有趣的技巧,但我将其用于包含日期类型属性和函数的类,允许我编写

<?php
class date_class {
/** @property int $date */
private $the_date;

public function
__get( $property ) {
if(
property_exists( $this, $property ) ){
return
$this-> $property;
}
throw new
Exception( "no such property $property." );
}
public function
the_date( $datetime ) {
return
strtotime( $datetime, $this-> the_date );
}

public function
__construct() {
$this-> the_date = time();
}
}

$date_object = new date_class();

$today = $date_object-> the_date;
$nextyear = $date_object-> the_date("+1 year");

echo
date( "d/m/Y", $today) . '<br>';
echo
date( "d/m/Y", $nextyear );
?>

我喜欢它,因为它可以实现属性的自文档化。我将其用于用户输入的实用程序类中。
24
navarr at gtaero dot net
14 年前
如果您希望使其更自然地适用于数组 `$obj->variable[]` 等,则需要通过引用返回 `__get`。

<?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]);
}
}
?>
12
php at lanar dot com dot au
14 年前
请注意,在链式检查中不会调用 `__isset`。
如果执行 `isset( $x->a->b )`,其中 `$x` 是一个声明了 `__isset()` 方法的类,则不会调用 `__isset()` 方法。

<?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' ) ; // 返回一个类以用于演示
}
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 ) ;
?>

输出

执行 `isset( $x->a )` 时,如预期的那样在 `demo` 上调用 `__isset()`
C:\htdocs\test.php:31 demo::__isset(a) instance demo
执行 `isset( $x->a->b )` 时,在 `demo` 上调用 `__get()` 而不调用 `__isset()`
C:\htdocs\test.php:26 demo::__get(a) instance demo
C:\htdocs\test.php:31 demo::__isset(b) instance autocreated
10
PHP at jyopp dotKomm
18 年前
这是一个用于记录函数调用的实用类。它存储一系列调用和参数,这些参数随后可以应用于对象。这可以用于编写常见的操作序列,或在头文件中创建“可插拔”的操作序列,这些序列可以在以后应用于对象。

如果使用要隐藏的对象实例化它,它将充当中介,并在传入调用时在该对象上执行这些调用,并将执行结果的值传回。

这是一个非常通用的实现;如果需要在“回放”过程中处理错误代码或异常,则应进行更改。


<?php
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);
// 在一组对象上执行这些方法调用...
foreach ($array as $o) $log->Replay($o);
?>
5
[email protected]
10 年前
实际上,我认为你不需要 __set 等。
你可以用它来设置(预定义的)受保护的(以及在“某些”情况下是私有的)属性。但谁会想要那样做呢?
(通过取消注释 private 或 protected 来测试它)
(因为太长了,所以用 pastebin )=> http://pastebin.com/By4gHrt5
7
[email protected]
18 年前
<?php $myclass->foo['bar'] = 'baz'; ?>

当覆盖 __get 和 __set 时,上面的代码可以工作(如预期的那样),但它依赖于你的 __get 实现而不是你的 __set。事实上,__set 从未被上面的代码调用。看来 PHP(至少在 5.1 版本中)使用 __get 返回的任何内容的引用。更详细地说,上面的代码与以下代码基本相同

<?php
$tmp_array
= &$myclass->foo;
$tmp_array['bar'] = 'baz';
unset(
$tmp_array);
?>

因此,如果你的 __get 实现类似于以下代码,则上面的代码将不会执行任何操作

<?php
function __get($name) {
return
array_key_exists($name, $this->values)
?
$this->values[$name] : null;
}
?>

你实际上需要在 __get 中设置值并返回该值,如下面的代码所示

<?php
function __get($name) {
if (!
array_key_exists($name, $this->values))
$this->values[$name] = null;
return
$this->values[$name];
}
?>
6
[email protected]
5 年前
如果你不够专注,那就不要使用它。
否则它非常强大,你可以构建非常复杂的代码来处理很多事情,就像 Zend Framework 所做的那样。
5
[email protected]
16 年前
虽然 PHP 本身不支持真正的重载,但我不得不不同意那些声称这无法通过 __call 实现的人。

是的,它并不漂亮,但绝对可以通过参数的类型来重载成员。一个例子
<?php
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.";
}
}

B {
}

$class = new A;
$class -> test(array()); // 输出 'Array.'
$class -> test(new B); // 输出 'Object.'
?>

当然,这种用法的可取性是有疑问的(我自己从未需要过它,但话又说回来,我的 C++ 和 JAVA 背景非常基础)。但是,使用这个一般原则并根据其他建议选择性地构建“一种”重载是绝对可能的,前提是在你的函数中有一些严格的命名约定。

当然,一旦 PHP 允许你多次声明同一个成员但使用不同的参数,它就会变得容易得多,因为如果你将它与反射类结合起来,“真正的”重载就会掌握在一个优秀的 OO 程序员手中。让我们拭目以待吧!
5
[email protected]
16 年前
如果已取消设置对象的已声明公有成员,则将调用 __get 重载方法。

<?php
c {
public
$p ;
public function
__get($name) { return "__get of $name" ; }
}

$c = new c ;
echo
$c->p, "\n" ; // 已声明的公有成员值为空
$c->p = 5 ;
echo
$c->p, "\n" ; // 已声明的公有成员值是 5
unset($c->p) ;
echo
$c->p, "\n" ; // 取消设置后,值为 "__get of p"
?>
9
[email protected]
17 年前
PHP 5.2.1

可以使用变量方法/属性名称来调用具有无效名称的魔术方法

<?php

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();

?>

我只是不知道这是个 bug 还是个特性 :)
5
[email protected]
15 年前
这是一个方便的小程序,可以建议你尝试设置但不存在的属性。例如

尝试在 'User' 类中获取不存在的属性/变量 'operator_id'。

正在检查操作符并建议以下内容

* id_operator
* operator_name
* operator_code

享受。

<?php
/**
* 如果 __get() 或 __set() 失败,则建议替代属性
*
* @param string $property
* @return string
* @author Daevid Vincent [[email protected]]
* @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>";
}

只需将其放入你的 __get() 或 __set() 中,如下所示:

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;
}
?>
3
Marius
19 年前
对于任何考虑遍历某个变量树的人
通过使用 __get() 和 __set()。我尝试这样做并发现了一个
问题:您可以通过返回来处理连续的 __get() 调用
可以处理后续 __get() 的对象,但您无法
以这种方式处理 __get() 和 __set()。
例如,如果您想要
<?php
print($obj->val1->val2->val3); // 三次 __get() 调用
?> - 这将起作用,
但如果您想要
<?php
$obj
->val1->val2 = $val; // 一次 __get() 和一次 __set() 调用
?> - 这将失败并显示消息
"致命错误:无法访问具有
重载属性访问的对象的未定义属性"
但是,如果您在一个表达式中不混合 __get() 和 __set(),
它将起作用
<?php
$obj
->val1 = $val; // 仅一次 __set() 调用
$val2 = $obj->val1->val2; // 两次 __get() 调用
$val2->val3 = $val; // 一次 __set() 调用
?>

如您所见,您可以将 __get() 和 __set() 部分
表达式拆分为两个表达式以使其工作。

顺便说一句,这在我看来像是一个错误,需要报告。
2
dans at dansheps dot com
13 年前
由于这让我困扰了一段时间,我想我最好在这里插话...

对于嵌套调用私有/受保护的变量(可能还有函数),它所做的是对第一个对象调用 __get(),如果您返回嵌套对象,则它会对嵌套对象调用 __get(),因为,嗯,它也是受保护的。

例如
<?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;
?>

这将返回类似以下内容...

Class A::Variable B
Class B::Variable val
Final Value: 1

它将调用分成 $A->B 和 $B->val

希望这对某些人有所帮助
4
Daniel Smith
13 年前
在使用 __call 时要注意受保护/私有方法。这样做

<?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();
/**
* 输出:
* TestMagicCallMethod::foo
*/

$test->bar();
/**
* 输出:
* TestMagicCallMethod::__call
* TestMagicCallMethod::bar
*/

$test->baz();
/**
* 输出:
* TestMagicCallMethod::__call
* TestMagicCallMethod::baz
*/
?>

…可能不是你应该做的事情。始终确保你在 __call 中调用的方法是允许的,因为你可能不希望所有私有/受保护的方法都因错别字或其他原因被访问。
1
Nanhe Kumar
10 年前
<?php
//如何更好地理解并实现__call函数
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('未定义的属性 ' . $name . ' 在 ' . $trace[0]['file'] . ' 的第 ' . $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('未定义的属性 ' . $name . ' 在 ' . $trace[0]['file'] . ' 的第 ' . $trace[0]['line'] . ' 行' , E_USER_NOTICE);
return
null;
}

break;
default :
return
FALSE;
}
}

}

$s = new Employee();
$s->setName('Nanhe Kumar');
$s->setEmail('[email protected]');
echo
$s->getName(); //Nanhe Kumar
echo $s->getEmail(); // [email protected]
$s->setAge(10); //Notice: Undefined property setAge in
?>
2
Adeel Khan
17 年前
观察

<?php
class Foo {
function
__call($m, $a) {
die(
$m);
}
}

$foo = new Foo;
print
$foo->{'wow!'}();

// 输出 'wow!'
?>

此方法允许您使用无效字符调用函数。
1
DevilDude at darkmaker dot com
20 年前
Php 5 有一个简单的递归系统,可以阻止您在重载函数中使用重载,这意味着您无法在 __get 方法中或 _get 方法调用的任何函数/方法中获取重载变量,但是您可以在自身内部手动调用 __get 来做同样的事情。
0
strata_ranger at hotmail dot com
15 年前
结合前面提到的两点

1 - 取消设置对象成员会将其完全从对象中删除,随后对该成员的使用将由魔术方法处理。
2 - PHP 不会在自身内部递归调用一个魔术方法(至少对于相同的 $name 来说)。

这意味着如果一个对象成员已被取消设置,则可以通过在对象的 __set() 方法中创建它来重新声明该对象成员(为 public),如下所示

<?php
class Foo
{
function
__set($name, $value)
{
// 为此对象添加一个新的(public)成员。
// 因为 __set() 不会递归调用自身,所以这可以工作。
$this->$name= $value;
}
}

$foo = new Foo();

// 此时 $foo 没有成员
var_dump($foo);

// 在这里将调用 __set()
$foo->bar = 'something'; // 调用 __set()

// $foo 现在包含一个成员
var_dump($foo);

// 不会调用 __set(),因为 'bar' 现在已声明
$foo->bar = 'other thing';

?>

还要注意,如果您想中断涉及对象成员的引用而不触发魔术功能,请不要直接取消设置对象成员。相反,使用 =& 将对象成员绑定到任何方便的 null 变量。
0
php at sleep is the enemy dot co dot uk
17 年前
只是为了加强和详细说明 DevilDude at darkmaker dot com 在 2004 年 9 月 22 日 07:57 说的内容。

递归检测功能在使用 __set 时可能会特别危险。当 PHP 遇到通常会调用 __set 但会导致递归的语句时,它不会发出警告或根本不执行该语句,而是会表现得好像根本没有定义 __set 方法。在这种情况下,默认行为是动态地向对象添加指定的属性,从而破坏对该属性的所有进一步调用 __set 或 __get 的所需功能。

示例

<?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
但是一旦我们尝试再次调用 __set
PHP 将拒绝并简单地向 $this 添加一个
名为 $name 的属性
*/
$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') ? '现在有' : '仍然没有') . ' 一个名为 "foo" 的属性<br>';

/*
输出:
$tc->foo == bar
$tc 现在有一个名为 "foo" 的属性
*/

?>
To Top