重载

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。如果其中一个魔术重载方法被声明为 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
"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;
}

public function
__isset($name)
{
echo
"Is '$name' set?\n";
return isset(
$this->data[$name]);
}

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
"让我们来试验一下名为 '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(string $name, array $arguments): mixed
public static __callStatic(string $name, array $arguments): mixed

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

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

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

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

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

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');
?>

以上示例将输出

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

用户贡献的注释 27 notes

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

此外,这超出了个人偏好,特别是在与其他人协作时。即使没有 IDE,浏览代码并查看硬编码的成员和方法定义也比筛选代码并拼凑 __get 和 __set 中组装的方法/成员名称要容易得多。

如果您仍然决定在类中使用 __get 和 __set 来访问所有内容,请确保添加详细的注释和文档,以便您合作的人(或以后继承代码的人)不必浪费时间来解释您的代码才能使用它。
Anonymous
8 年前
首先,如果你看到这个评论,请为第一个评论点赞,它指出“重载”对于这种行为来说是一个不好的术语。因为它真的是一个不好的名称。你给了一个已经得到认可的 IT 分支术语赋予了新的定义。

其次,我同意你将读到的关于此功能的所有批评。就像把它命名为“重载”一样,这个功能本身也是非常不好的实践。请不要在生产环境中使用它。说实话,最好完全避免使用它。特别是如果你是一个 PHP 初学者。它可能使你的代码产生非常意外的行为。在这种情况下,你可能会学到错误的编码方式!

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

<?php

class BadPractice {
// 两个真正的属性
public $DontAllowVariableNameWithTypos = true;
protected
$Number = 0;
// 一个私有方法
private function veryPrivateMethod() { }
// 以及三个非常神奇的方法,它们会使所有东西看起来与你以前学到的所有关于 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);
}
?>
egingell at sisna dot com
16 年前
一个小词汇说明:这 *不* 是“重载”,这是“覆盖”。

重载:像这样多次声明具有不同参数集的函数
<?php

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

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

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

?>

覆盖:通过像这样重新声明,用新方法替换父类的 method(s)
<?php

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

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

?>
匿名
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);
}
}
pogregoire##live.fr
7 年前
重要的是要理解,封装在 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');
}
}
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;
}
// ...
}

?>
Ant P.
15 年前
使用 __call() 时要格外小心:如果你在某处输入函数调用的错误,它不会触发未定义函数错误,而是传递给 __call(),这可能会导致各种奇怪的副作用。
在 5.3 之前的版本中,没有 __callStatic,对不存在函数的静态调用也会传递给 __call!
这让我困惑了几个小时,希望这条评论可以避免其他人遇到同样的问题。
gabe at fijiwebdesign dot com
9 年前
请注意,你可以通过对现有属性使用 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; // 调用 Test->$property1
?>

如果您想代理或延迟加载属性,但希望在代码和调试中具有文档和可见性(与不存在的不可访问属性上的 __get()、__isset()、__set() 相比),这将很有用。
johannes dot kingma at gmail dot com
2 年前
__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 );
?>

我喜欢它,因为它具有自我文档属性。我在用户输入的实用程序类中使用了它。
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]);
}
}
?>
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
PHP at jyopp dotKomm
18 年前
这是一个用于记录函数调用的有用类。它存储一系列调用和参数,这些参数随后可以应用于对象。这可以用来编写常见的操作序列,或在头文件中创建“可插拔”的操作序列,这些序列可以在稍后在对象上重放。

如果它使用要遮蔽的对象进行实例化,它将充当中介者,并在调用到来时在该对象上执行这些调用,并将执行结果中的值传回。

这是一个非常通用的实现;如果在 Replay 过程中需要处理错误代码或异常,则应更改它。
<?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);
?>
cottton at i-stats dot net
9 年前
实际上,您不需要 __set 等 imo。
您可以使用它来设置(预定义的)受保护的(在“某些”情况下为私有的)属性。但谁想要这样呢?
(通过取消注释私有或受保护来测试它)
(粘贴到粘贴板,因为太长了...)=> http://pastebin.com/By4gHrt5
jstubbs at work-at dot co dot jp
17 年前
<?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];
}
?>
justmyoponion at gmail dot com
4 年前
如果您不够专注,那么就不要使用它。
否则,它非常强大,您可以构建非常复杂的代码来处理很多事情,就像 Zend 框架一样。
matthijs at yourmediafactory dot com
16 年前
虽然 PHP 本身并不支持真正的重载,但我不得不不同意那些说这不能通过 __call 实现的人。

是的,它并不漂亮,但根据其参数的类型重载成员绝对是可能的。一个例子
<?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.'
?>

当然,使用这种方法是有问题的(我自己从未需要它,但话说回来,我的 C++ 和 JAVA 背景非常简陋)。但是,使用这个通用原则并可选地根据其他建议构建,一种“形式”的重载绝对是可能的,前提是您的函数有一些严格的命名约定。

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

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

$c = new c ;
echo
$c->p, "\n" ; // 声明的公共成员值为 empty
$c->p = 5 ;
echo
$c->p, "\n" ; // 声明的公共成员值为 5
unset($c->p) ;
echo
$c->p, "\n" ; // 取消设置后,值为 "__get of p"
?>
alexandre at nospam dot gaigalas dot net
17 年前
PHP 5.2.1

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

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

?>

我不知道这到底是bug还是功能:)
daevid at daevid dot com
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>";
}

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;
}
?>
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() 部分
表达式分成两个表达式使其工作。

顺便说一句,这对我来说似乎是一个错误,我必须报告它。
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;
?>

这将返回类似……

类 A::变量 B
类 B::变量 val
最终值:1

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

希望这对某人有所帮助
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();
/**
* Outputs:
* TestMagicCallMethod::foo
*/

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

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

……可能不是您应该做的事情。始终确保您在 __call 中调用的方法是允许的,因为您可能不希望所有私有/受保护方法都被类型错误或其他问题访问。
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('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('[email protected]');
echo
$s->getName(); //Nanhe Kumar
echo $s->getEmail(); // [email protected]
$s->setAge(10); //Notice: Undefined property setAge in
?>
Adeel Khan
17 年前
观察

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

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

// 输出 'wow!'
?>

这种方法允许您调用包含无效字符的函数。
DevilDude at darkmaker dot com
19 年前
Php 5 具有一个简单的递归系统,它阻止您在重载函数中使用重载,这意味着您无法在__get 方法中获取重载变量,也无法在__get 方法调用的任何函数/方法中获取重载变量,但是您可以在__get 方法中手动调用自己来实现相同的功能。
strata_ranger at hotmail dot com
14 年前
结合前面提到的两点

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

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

?>

还要注意,如果您想打破涉及对象成员的引用而不触发魔术功能,请不要直接取消设置对象成员。而是使用 =& 将对象成员绑定到任何方便的 null 变量。
php at sleep is the enemy dot co dot uk
17 年前
只是为了加强和详细说明 DevilDude 在 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') ? 'now has' : 'still does not have') . ' a property called "foo"<br>';

/*
输出:
$tc->foo == bar
$tc now has a property called "foo"
*/

?>
To Top