魔术方法

__construct()__destruct()__call()__callStatic()__get()__set()__isset()__unset()__sleep()__wakeup()__toString()__invoke()__set_state()__clone()__debugInfo() 等方法在 PHP 中被称为魔术方法。

PHP 允许开发者在一个类中定义一个方法作为构造函数。具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。

<?php

class Foo
{
    /**
     * Just a test property.
     *
     * @var string
     */
    public string $property;

    /**
     * Initialize the value of the property.
     *
     * @param  void
     * @return void
     */
    public function __construct()
    {
        $this->property = 'foo';
    }
}

$foo = new Foo();
var_dump($foo->property); // string(3) "foo"

PHP 引入了析构函数的概念,析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。

<?php

class Foo
{
    /**
     * Just a test destructor.
     *
     * @param  void
     * @return void
     */
    public function __destruct()
    {
        var_dump('foo');
    }
}

$foo = new Foo();
unset($foo);
// string(3) "foo"

在对象中调用一个不可访问方法时, __call() 会被调用。

<?php

class Foo
{
    /**
     * Return the specified string.
     *
     * @param  string $name
     * @param  array  $args
     * @return string
     */
    public function __call(string $name, array $args): string
    {
        return $name . '+' . implode('+', $args);
    }
}

$foo = new Foo();
var_dump($foo->method('foo', 'bar')); // string(14) "method+foo+bar"

在静态上下文中调用一个不可访问方法时, __callStatic() 会被调用。

<?php

class Foo
{
    /**
     * Return the specified string.
     *
     * @param  string $name
     * @param  array  $args
     * @return string
     */
    public static function __callStatic(string $name, array $args): string
    {
        return $name . '+' . implode('+', $args);
    }
}

var_dump(Foo::method('foo', 'bar')); // string(14) "method+foo+bar"

在给不可访问属性赋值时, __set() 会被调用。

<?php

class Foo
{
    /**
     * Set the value of the specified property.
     *
     * @param  string $name
     * @param  string $value
     * @return void
     */
    public function __set(string $name, string $value)
    {
        $this->$name = $value;
    }
}

$foo = new Foo();
$foo->property = 'foo';
var_dump($foo->property); // string(3) "foo"

读取不可访问属性的值时, __get() 会被调用。

<?php

class Foo
{
    /**
     * Private property.
     *
     * @var string
     */
    private string $property = 'foo';

    /**
     * Return the specified property.
     *
     * @param  string $name
     * @return string
     */
    public function __get(string $name): string
    {
        return $this->$name;
    }
}

$foo = new Foo();
var_dump($foo->property); // string(3) "foo"

当对不可访问属性调用 isset()empty() 时, __isset() 会被调用。

<?php

class Foo
{
    /**
     * Private property.
     *
     * @var string
     */
    private string $property = 'foo';

    /**
     * Check the specified property.
     *
     * @param  string  $name
     * @return bool
     */
    public function __isset(string $name): bool
    {
        return isset($this->$name);
    }
}

$foo = new Foo();
var_dump(isset($foo->property)); // bool(true)

当对不可访问属性调用 unset() 时, __unset() 会被调用。

<?php

class Foo
{
    /**
     * Private property.
     *
     * @var string
     */
    private string $property = 'foo';

    /**
     * Check the specified property.
     *
     * @param  string  $name
     * @return bool
     */
    public function __isset(string $name): bool
    {
        return isset($this->$name);
    }

    /**
     * Unset the specified property.
     *
     * @param  string $name
     * @return void
     */
    public function __unset(string $name)
    {
        unset($this->$name);
    }
}

$foo = new Foo();
var_dump(isset($foo->property)); // bool(true)
unset($foo->property);
var_dump(isset($foo->property)); // bool(false)

serialize() 函数会检查类中是否存在一个魔术方法 __sleep() 。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误。 __sleep() 方法常用于提交未提交的数据,或类似的清理操作。同时,如果有一些很大的对象,但不需要全部保存,这个功能就很好用。

<?php

class Foo
{
    /**
     * Public property.
     *
     * @var string
     */
    public string $public = 'public';

    /**
     * Protected property.
     *
     * @var string
     */
    protected string $protected = 'protected';

    /**
     * Private property.
     *
     * @var string
     */
    private string $private = 'private';

    /**
     * Return an array of the specified properties.
     *
     * @param  void
     * @return array
     */
    public function __sleep(): array
    {
        return ['public'];
    }
}

$foo = new Foo();
var_dump(serialize($foo)); // string(40) "O:3:"Foo":1:{s:6:"public";s:6:"public";}"

与之相反, unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup() 方法,预先准备对象需要的资源。 __wakeup() 经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。

<?php

class Foo
{
    /**
     * Public property.
     *
     * @var string
     */
    public string $public = 'public';

    /**
     * Protected property.
     *
     * @var string
     */
    protected string $protected = 'protected';

    /**
     * Private property.
     *
     * @var string
     */
    private string $private = 'private';

    /**
     * Set the value of the specified property.
     *
     * @param  void
     * @return void
     */
    public function __wakeup()
    {
        $this->property = 'foo';
    }
}

$foo = 'O:3:"Foo":1:{s:6:"public";s:6:"public";}';
var_dump(unserialize($foo)); // object(Foo)#1 (4) { ["public"]=> string(6) "public" ["protected":protected]=> string(9) "protected" ["private":"Foo":private]=> string(7) "private" ["property"]=> string(3) "foo" }

__toString() 方法用于一个类被当成字符串时应怎样回应。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。

<?php

class Foo
{
    /**
     * Return a string.
     *
     * @param  void
     * @return string
     */
    public function __toString(): string
    {
        return 'foo';
    }
}

$foo = new Foo();
echo $foo . "\r\n";  // foo
print $foo . "\r\n"; // foo

当尝试以调用函数的方式调用一个对象时, __invoke() 方法会被自动调用。

<?php

class Foo
{
    /**
     * Return a string.
     *
     * @param  void
     * @return string
     */
    public function __invoke(): string
    {
        return 'foo';
    }
}

$foo = new Foo();
var_dump($foo()); // string(3) "foo"

当调用 var_export() 函数导出类的相关信息时, __set_state() 静态方法会被调用。此方法的唯一参数是一个数组,其中包含按数组键值对格式排列的类属性。

<?php

class Foo
{
    /**
     * Just a test property.
     *
     * @var string
     */
    public string $property;

    /**
     * Return an initialized object.
     *
     * @param  array  $array
     * @return Foo
     */
    public static function __set_state(array $array): Foo
    {
        $object = new self;
        $object->property = $array['property'];
        return $object;
    }
}

$foo = new Foo();
$foo->property = 'foo';
var_export($foo); // Foo::__set_state(array( 'property' => 'foo', ))

$bar = Foo::__set_state(array( 'property' => 'foo', ));
var_dump($bar);   // object(Foo)#2 (1) { ["property"]=> string(3) "foo" }

对象复制可以通过 clone 关键字来完成(如果可能,这将调用对象的 __clone() 方法)。对象中的 __clone() 方法不能被直接调用。当对象被复制后, PHP 会对对象的所有属性执行一个浅复制。所有的引用属性仍然会是一个指向原来的变量的引用。当复制完成时,如果定义了 __clone() 方法,则新创建的对象(复制生成的对象)中的 __clone() 方法会被调用,可用于修改属性的值(如果有必要的话)。

<?php

class Cat
{
    /**
     * The name of cat.
     *
     * @var string
     */
    public string $name;

    /**
     * The age of cat.
     *
     * @var int
     */
    public int $age;

    /**
     * Store the name and age.
     *
     * @param  string $name
     * @param  int    $age
     * @return void
     */
    public function __construct(string $name, int $age)
    {
        $this->name = $name;
        $this->age = $age;
    }
}

class Dog
{
    /**
     * The name of dog.
     *
     * @var string
     */
    public string $name;

    /**
     * Store an instance of cat.
     *
     * @var Cat
     */
    public Cat $cat;

    /**
     * Complete deep copy.
     *
     * @param  void
     * @return void
     */
    public function __clone()
    {
        $this->cat = clone $this->cat;
    }
}

$spike = new Dog();
$spike->name = 'Spike';
$spike->cat = new Cat('Tom', 6);
$tyke = clone $spike;
var_dump($spike); // object(Dog)#1 (2) { ["name"]=> string(5) "Spike" ["cat"]=> object(Cat)#2 (2) { ["name"]=> string(3) "Tom" ["age"]=> int(6) } }
var_dump($tyke);  // object(Dog)#3 (2) { ["name"]=> string(5) "Spike" ["cat"]=> object(Cat)#4 (2) { ["name"]=> string(3) "Tom" ["age"]=> int(6) } }

$tyke->name = 'Tyke';
$tyke->cat->age = 2;
var_dump($spike); // object(Dog)#1 (2) { ["name"]=> string(5) "Spike" ["cat"]=> object(Cat)#2 (2) { ["name"]=> string(3) "Tom" ["age"]=> int(6) } }
var_dump($tyke);  // object(Dog)#3 (2) { ["name"]=> string(4) "Tyke" ["cat"]=> object(Cat)#4 (2) { ["name"]=> string(3) "Tom" ["age"]=> int(2) } }

当调用 var_dump() 函数打印类的相关信息时, __debugInfo 方法会被调用。如果未在对象上定义该方法,则将显示所有公共属性,受保护属性和私有属性。

<?php

class Foo
{
    /**
     * Public property.
     *
     * @var string
     */
    public string $public = 'public';

    /**
     * Protected property.
     *
     * @var string
     */
    protected string $protected = 'protected';

    /**
     * Private property.
     *
     * @var string
     */
    private string $private = 'private';

    /**
     * Return all visibility properties.
     *
     * @param  void
     * @return array
     */
    public function __debugInfo(): array
    {
        return [
            'public'    => $this->public,
            'protected' => $this->protected,
            'private'   => $this->private
        ];
    }
}

$foo = new Foo();
var_dump($foo); // object(Foo)#1 (3) { ["public"]=> string(6) "public" ["protected"]=> string(9) "protected" ["private"]=> string(7) "private" }

results matching ""

    No results matching ""