PHP重载

PHP中的重载描述了动态创建或访问对象的属性和方法而不必先定义它们的方式。传统上,编程中的重载一词用于描述具有相同名称但具有不同参数的访问对象方法。在PHP中无法执行此操作,因为它将抱怨名称相同的方法,因此该术语描述了调用方法或访问以前未设置或不在范围内的属性。用面向对象的术语表示这意味着方法或属性是私有的。

对象属性重载

属性重载使您可以通过方法访问对象的属性,而不必先编写它们。它也可以用来访问任何不可访问的属性。有两种基本的属性重载方法,分别是__set()和__get(),它们的名称都包含双下划线(_)。

__set()只要设置了对象的未定义或不可访问的属性,并且必须具有以下占用空间,就会调用该方法。

void __set ( string $name , mixed $value )

__get()每当读取对象的未定义或不可访问的属性且必须具有以下占用空间时,就会调用该方法。

mixed __get ( string $name )

下面的简单类以最简单的形式定义了这两个函数以及几个属性。每次使用__set()或__get()方法时,它们都会打印出一些信息。

class OverloadingTest
{
    private $privateProperty;
 
    public $publicProperty;
 
    public function __set($name, $value)
    {
        echo '__set() ' . $name . ' ' . $value. "\n";
        $this->$name = $value;
    }
 
    public function __get($name)
    {
        echo '__get() ' . $name . "\n";
        return $this->$name;
    }
}

为了测试这些功能,我编写了一些简单的代码。

$object = new OverloadingTest();
$object->privateProperty = 'this is a private value';
echo $object->privateProperty . "\n";
 
$object->publicProperty = 'this is a publicvalue';
echo $object->publicProperty . "\n";
 
$object->nonExistingProperty = 'another value';
echo $object->nonExistingProperty . "\n";
 
var_dump($object);

此代码创建以下输出。

__set() privateProperty this is a private value
__get() privateProperty
this is a private value
this is a publicvalue
__set() nonExistingProperty another value
another value
object(OverloadingTest)#1 (3) {
  ["privateProperty:private"]=>
  string(23) "this is a private value"
  ["publicProperty"]=>
  string(21) "this is a publicvalue"
  ["nonExistingProperty"]=>
  string(13) "another value"
}

要注意的一件非常重要的事情是,使用创建属性时__set(),__get()不会使用来检索该属性的值。因此,您可能会看到这样创建的一些类,其中将私有属性创建为数组,并且__get()and__set()方法访问此属性。

class OverloadingTest2
{
    private $privateProperty = array();
 
    public function __set($name, $value)
    {
        echo '__set() ' . $name . ' ' . $value. "\n";
        $this->privateProperty[$name] = $value;
    }
 
    public function __get($name)
    {
        echo '__get() ' . $name . "\n";
        if (!isset($this->privateProperty[$name])) {
            return null;
        }
        return $this->privateProperty[$name];
    }
}

这将强制__get()在读取参数的属性时使用该方法,而不是像前面的示例中那样将其忽略。外部代码看起来与先前的代码非常相似,但是控制数据的类以不同的方式存储它。

$object = new OverloadingTest2();
$object->property = 'this is a value';
echo $object->property . "\n";
 
var_dump($object);

这将产生以下输出。

__set() property this is a private value
__get() property
this is a private value
object(OverloadingTest)#1 (1) {
  ["property:private"]=>
  array(1) {
    ["property"]=>
    string(15) "this is a value"
  }
}

如果习惯了面向对象,那么您可能会看到称为链接的东西,尤其是在使用集合类型方法的地方。使用此__set()方法不起作用,因为将忽略任何返回值,而是返回发送到该方法的$value参数。以下方法将返回$value参数的值,而不是当前对象。

public function __set($name, $value)
{
    echo '__set() ' . $name . ' ' . $value . "\n";
    $this->privateProperty[$name] = $value;
    return $this;
}

存在另外两个属性重载方法,它们称为__isset()和__unset(),从PHP版本5.1.0开始可用。__isset()在isset()或empty()对不必要的属性调用时,将调用此方法,并且该方法必须具有以下占用空间。

bool __isset ( string $name )

在无法访问的属性上__unset()调用该unset()函数时,将调用该方法,并且具有以下占用空间。

void __unset ( string $name )

掌握了这些信息后,我们可以修改我们的类以包括这些功能。

class OverloadingTest3
{
    private $privateProperty = array();
 
    public function __set($name, $value)
    {
        echo '__set() ' . $name . ' ' . $value. "\n";
        $this->privateProperty[$name] = $value;
        return $this;
    }
 
    public function __get($name)
    {
        echo '__get() ' . $name . "\n";
        if (!isset($this->privateProperty[$name])) {
            return null;
        }
        return $this->privateProperty[$name];
    }
 
    public function __isset($name) {
        echo '__isset() ' . $name . "\n";
        return isset($this->privateProperty[$name]);
    }
 
    public function __unset($name) {
        echo '__unset() ' . $name . "\n";
        unset($this->privateProperty[$name]);
    }
}

我们可以像这样测试这些新功能。

$object = new OverloadingTest3();
$object->property = 'this is a value';
echo $object->property . "\n";
 
var_dump(isset($object->property));
unset($object->property);
var_dump(isset($object->property));
 
var_dump($object);

这将产生以下输出。

__set() property this is a value
__get() property
this is a value
__isset() property
bool(true)
__unset() property
__isset() property
bool(false)
object(OverloadingTest3)#1 (1) {
  ["privateProperty:private"]=>
  array(0) {
  }
}

对象方法重载

方法重载的工作方式与属性重载的方式几乎相同,但显然用于不存在或不可访问的方法。通过__call()在类中添加一个称为的方法,您可以拦截对无法访问的方法的所有调用。该__call()方法必须具有以下占用空间。

mixed __call ( string $name , array $arguments )

$name参数将包含被调用函数的名称,并且区分大小写,$arguments参数将包含发送到方法调用的所有参数的数组。以下是该方法的一个非常简单的示例。

class OverloadMethodTest
{
    public function __call($name, $arguments)
    {
        echo '__call ' . $name . "\n";
        print_r($arguments);
    }
}
 
$obj = new OverloadMethodTest();
$obj->runSomething('parameter1', 2);

这将产生以下输出。

__call runSomething
Array
(
    [0] => parameter1
    [1] => 2
)

出于兴趣,我想知道如果我将一个私有方法添加runSomething()到该类并尝试运行相同的代码,会发生什么情况。

class OverloadMethodTest
{
    public function __call($name, $arguments)
    {
        echo '__call ' . $name . "\n";
        print_r($arguments);
    }
 
    private function runSomething()
    {
        echo 'runSomething() is a private method';
    }
}
 
$obj = new OverloadMethodTest();
$obj->runSomething('parameter1', 2);

这产生了与以前完全相同的输出,这意味着该__call()方法将拦截对现有私有方法的所有调用,而不是产生有关访问私有方法的致命错误。

从PHP 5.3.0开始,还有一个称为的方法__callStatic(),可用于为所提供的静态方法调用提供相同的功能__call()。该__callStatic()方法还具有与该方法相同的占用空间,__call()并且以相同的方式调用。

mixed __callStatic ( string $name , array $arguments )

这是一个使用的简单类__callStatic()。

class OverloadMethodTest
{
    public function __call($name, $arguments)
    {
        echo '__call ' . $name . "\n";
        print_r($arguments);
    }
 
    public static function __callStatic($name, $arguments)
    {
        echo '__callStatic ' . $name . "\n";
        print_r($arguments);
    }
}
 
$obj = new OverloadMethodTest();
$obj->runSomething('parameter1', 2);
OverloadMethodTest::runSomething('in static context');

这将产生以下输出。

__call runSomething
Array
(
    [0] => parameter1
    [1] => 2
)
__callStatic runSomething
Array
(
    [0] => in static context
)

您无法调用一种方法,它会__call()从方法内部遍历该__call()方法。换句话说,递归调用__call()不起作用。您可以尝试自己运行它,看看使用此代码会发生什么。

class OverloadMethodTest2
{
    public function __call($name, $arguments)
    {
        echo '__call ' . $name . "\n";
        $this->wibble($name);
        print_r($arguments);
    }
}
 
$obj = new OverloadMethodTest2();
$obj->runSomething('parameter1', 2);

运行此代码时,您只会从服务器收到空响应。

可以从同一个对象中调用无法访问的方法,您只需要从其他方法中进行操作即可。下面的代码显示了一个有效的示例。

class OverloadMethodTest3
{
    public function __call($name, $arguments)
    {
        echo '__call ' . $name . "\n";
        print_r($arguments);
    }
 
    public function test()
    {
        $this->runSomething('parameter1', 2);
    }
}
 
$obj = new OverloadMethodTest3();
$obj->test();

这将产生以下输出。

__call runSomething
Array
(
    [0] => parameter1
    [1] => 2
)

当代码运行该test()方法时,这是有意义的,而该方法又运行该runSomething()方法。

当然,简单地允许您的类处理针对它们运行的任何方法名称可能会有些危险。因此,最好在__call()方法内部使用switch语句,以阻止正在运行的所有您不允许的方法。下面的示例演示了这一操作。

class OverloadMethodTest4
{
    public function __call($name, $arguments)
    {
        switch ($name) {
            case 'function1':
                    // 用$arguments做某事
                break;
        }
    }
}

如果您想了解有关重载的更多信息,可以查看PHP网站上的重载手册页。