## 属性

类的变量成员叫做属性，或者叫字段，在本文档统一称为属性。 属性开头至少使用一个修饰符（比如 访问控制（可见性）、静态（static）关键字或者自 PHP 8.1.0 起支持的 readonly）， 除了 `readonly` 属性之外都是可选的，然后自 PHP 7.4 起可以跟一个类型声明，然后跟一个普通的变量声明来组成。属性中的变量可以初始化，但是初始化的值必须是 常量值。

> 注意:

> 另一种过时的声明类属性的方法是使用 `var` 关键字，而不是使用修饰符。

> 注意: 没有声明 访问控制（可见性） 修饰符的属性将默认声明为 `public`。

在类的成员方法里面，可以用 `->`（对象运算符）：$this->property（其中 `property` 是该属性名）这种方式来访问非静态属性。静态属性则是用 `::`（双冒号）：self::$property 来访问。更多静态属性与非静态属性的区别参见 静态（static）关键字。

当一个方法在类定义内部被调用时，有一个可用的伪变量 $this。$this 是一个到主叫对象的引用（通常是该方法所从属的对象，但如果是从第二个对象静态调用时也可能是另一个对象）。

示例 #1 属性声明

    <?php
    class SimpleClass
    {
       public $var1 = 'hello ' . 'world';
       public $var2 = <<<EOD
    hello world
    EOD;
       public $var3 = 1+2;
       // 错误的属性声明
       public $var4 = self::myStaticMethod();
       public $var5 = $myVar;

       // 正确的属性声明
       public $var6 = myConstant;
       public $var7 = array(true, false);

       public $var8 = <<<'EOD'
    hello world
    EOD;

       // 没有访问控制修饰符：
       static $var9;
       readonly int $var10;
    }
    ?>

> 注意:

> 更多关于类／对象的处理函数，请查看类／对象函数。

### 类型声明

从 PHP 7.4.0 开始，属性定义可以包含类型声明，但 callable 除外。

示例 #2 类型声明的示例

    <?php

    class User
    {
        public int $id;
        public ?string $name;

        public function __construct(int $id, ?string $name)
        {
            $this->id = $id;
            $this->name = $name;
        }
    }

    $user = new User(1234, null);

    var_dump($user->id);
    var_dump($user->name);

    ?>

以上示例会输出：

    int(1234)
    NULL

类型属性必须在访问前初始化，否则会抛出 Error 。

示例 #3 访问属性

    <?php

    class Shape
    {
        public int $numberOfSides;
        public string $name;

        public function setNumberOfSides(int $numberOfSides): void
        {
            $this->numberOfSides = $numberOfSides;
        }

        public function setName(string $name): void
        {
            $this->name = $name;
        }

        public function getNumberOfSides(): int
        {
            return $this->numberOfSides;
        }

        public function getName(): string
        {
            return $this->name;
        }
    }

    $triangle = new Shape();
    $triangle->setName("triangle");
    $triangle->setNumberofSides(3);
    var_dump($triangle->getName());
    var_dump($triangle->getNumberOfSides());

    $circle = new Shape();
    $circle->setName("circle");
    var_dump($circle->getName());
    var_dump($circle->getNumberOfSides());
    ?>

以上示例会输出：

    string(8) "triangle"
    int(3)
    string(6) "circle"

    Fatal error: Uncaught Error: Typed property Shape::$numberOfSides must not be accessed before initialization

### 只读属性

自 PHP 8.1.0 起，可以使用 `readonly` 修饰符声明属性，防止初始化后修改属性。 在 PHP 8.4.0 之前，`readonly` 属性是隐式的私有设置，只能从同一类写入。 从 PHP 8.4.0 开始，`readonly` 属性是隐式的 `protected(set)`， 因此可以从子类设置。如果需要，可以显式覆盖。

示例 #4 只读属性示例

    <?php
    class Test {
       public readonly string $prop;
       public function __construct(string $prop) {
           // 初始化正常。
           $this->prop = $prop;
       }
    }
    $test = new Test("foobar");
    // 读取正常。
    var_dump($test->prop); // string(6) "foobar"
    // 再赋值异常。分配的值是否相同并不重要。
    $test->prop = "foobar";
    // Error: Cannot modify readonly property Test::$prop
    ?>

> 注意:

> readonly 修饰符只能应用于类型化属性。可以使用 Mixed 类型创建没有类型约束的只读属性。

> 注意:

> 不支持对静态属性只读。

只读属性只能初始化一次，并且只能从声明它的作用域内初始化。对属性的任何赋值和修改都会导致 Error 异常。

示例 #5 初始化只读属性异常

    <?php
    class Test1 {
        public readonly string $prop;
    }
    $test1 = new Test1;
    // 私有作用域之外异常初始化。
    $test1->prop = "foobar";
    // Error: Cannot initialize readonly property Test1::$prop from global scope
    ?>

> 注意:

> 禁止在只读属性上指定默认值，因为具有默认值的只读属性等同于常量，因此不是特别有用。

>     <?php

>     class Test {
>         // Fatal error: Readonly property Test::$prop cannot have default value
>         public readonly int $prop = 42;
>     }
>     ?>

> 注意:

> 只读属性一旦初始化就不能 unset()。但可以在初始化之前从声明属性的作用域中取消只读属性。

修改不一定是简单的赋值，以下所有行为也会导致 Error 异常：

    <?php

    class Test {
        public function __construct(
            public readonly int $i = 0,
            public readonly array $ary = [],
        ) {}
    }

    $test = new Test;
    $test->i += 1;
    $test->i++;
    ++$test->i;
    $test->ary[] = 1;
    $test->ary[0][] = 1;
    unset($test->ary[0]);
    $ref =& $test->i;
    $test->i =& $ref;
    byRef($test->i);
    foreach ($test as &$prop);
    ?>

然而，只读属性并不会妨碍内部可变性。存储在只读属性中的对象（或资源）仍然可以在内部修改：

    <?php
    class Test {
        public function __construct(public readonly object $obj) {}
    }
    $test = new Test(new stdClass);
    // 内部可变正常。
    $test->obj->foo = 1;
    // 赋值异常。
    $test->obj = new stdClass;
    ?>

自 PHP 8.3.0 起，使用 __clone() 方法克隆对象时可以重新初始化 readonly 属性。

示例 #6 readonly 属性和克隆

    <?php
    class Test1 {
        public readonly ?string $prop;

        public function __clone() {
            $this->prop = null;
        }

        public function setProp(string $prop): void {
            $this->prop = $prop;
        }
    }

    $test1 = new Test1;
    $test1->setProp('foobar');

    $test2 = clone $test1;
    var_dump($test2->prop); // NULL
    ?>

### 动态属性

如果尝试在 object 上赋值不存在的属性，PHP 将会自动创建相应的属性。动态创建的属性将仅能在此类实例上使用。

警告

自 PHP 8.2.0 起弃用动态属性。建议更改为属性声明。要处理任意属性名称，类应该实现魔术方法 __get() 和 __set()。最后可以使用 `#[\AllowDynamicProperties]` 注解标记此类。

`↑` and `↓` to navigate • `Enter` to select • `Esc` to close • `/` to open