mirror of
https://github.com/php/doc-zh.git
synced 2026-03-23 22:52:08 +01:00
808 lines
20 KiB
XML
Executable File
808 lines
20 KiB
XML
Executable File
<?xml version="1.0" encoding="utf-8"?>
|
||
<!-- $Revision$ -->
|
||
<!-- EN-Revision: fc068b4857342eb409efeafe6723058f39ab1045 Maintainer: avenger Status: ready -->
|
||
<!-- CREDITS: mowangjuanzi, Luffy -->
|
||
<sect1 xml:id="language.oop5.basic" xmlns="http://docbook.org/ns/docbook">
|
||
<title>基本概念</title>
|
||
|
||
<sect2 xml:id="language.oop5.basic.class">
|
||
<title>class</title>
|
||
<para>
|
||
每个类的定义都以关键字 <literal>class</literal>
|
||
开头,后面跟着类名,后面跟着一对花括号,里面包含有类的属性与方法的定义。
|
||
</para>
|
||
<para>
|
||
类名可以是任何不是 PHP <link linkend="reserved">保留字</link>
|
||
的有效标签。自 PHP 8.4.0 起,弃用使用单个下划线 <literal>_</literal>
|
||
作为类名。有效类名以字母或下划线开头,后面跟着若干字母、数字或下划线。以正则表达式表示为
|
||
<code>^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$</code>。
|
||
</para>
|
||
<para>
|
||
一个类可以包含有属于自己的 <link
|
||
linkend="language.oop5.constants">常量</link>,<link
|
||
linkend="language.oop5.properties">变量</link>(称为“属性”)以及函数(称为“方法”)。
|
||
</para>
|
||
<example>
|
||
<title>简单的类定义</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
class SimpleClass
|
||
{
|
||
// 声明属性
|
||
public $var = 'a default value';
|
||
|
||
// 声明方法
|
||
public function displayVar() {
|
||
echo $this->var;
|
||
}
|
||
}
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
<para>
|
||
当一个方法在类定义内部被调用时,有一个可用的伪变量
|
||
<varname>$this</varname>。<varname>$this</varname>
|
||
是一个到当前对象的引用。
|
||
</para>
|
||
<warning>
|
||
<para>
|
||
以静态方式去调用一个非静态方法,将会抛出一个
|
||
<classname>Error</classname>。
|
||
在 PHP 8.0.0 之前版本中,将会产生一个废弃通知,同时
|
||
<varname>$this</varname> 将会被声明为未定义。
|
||
</para>
|
||
<example xml:id="language.oop5.basic.class.this">
|
||
<title>使用 <varname>$this</varname> 伪变量的示例</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
class A
|
||
{
|
||
function foo()
|
||
{
|
||
if (isset($this)) {
|
||
echo '$this is defined (';
|
||
echo get_class($this);
|
||
echo ")\n";
|
||
} else {
|
||
echo "\$this is not defined.\n";
|
||
}
|
||
}
|
||
}
|
||
|
||
class B
|
||
{
|
||
function bar()
|
||
{
|
||
A::foo();
|
||
}
|
||
}
|
||
|
||
$a = new A();
|
||
$a->foo();
|
||
|
||
A::foo();
|
||
|
||
$b = new B();
|
||
$b->bar();
|
||
|
||
B::bar();
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs.7;
|
||
<screen>
|
||
<![CDATA[
|
||
$this is defined (A)
|
||
|
||
Deprecated: Non-static method A::foo() should not be called statically in %s on line 27
|
||
$this is not defined.
|
||
|
||
Deprecated: Non-static method A::foo() should not be called statically in %s on line 20
|
||
$this is not defined.
|
||
|
||
Deprecated: Non-static method B::bar() should not be called statically in %s on line 32
|
||
|
||
Deprecated: Non-static method A::foo() should not be called statically in %s on line 20
|
||
$this is not defined.
|
||
]]>
|
||
</screen>
|
||
&example.outputs.8;
|
||
<screen>
|
||
<![CDATA[
|
||
$this is defined (A)
|
||
|
||
Fatal error: Uncaught Error: Non-static method A::foo() cannot be called statically in %s :27
|
||
Stack trace:
|
||
#0 {main}
|
||
thrown in %s on line 27
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</warning>
|
||
|
||
<sect3 xml:id="language.oop5.basic.class.readonly">
|
||
<title>只读类</title>
|
||
<para>
|
||
自 PHP 8.2.0 起,可以使用 <modifier>readonly</modifier> 修饰符来标记类。将类标记为 <modifier>readonly</modifier>
|
||
只会向每个声明的属性添加 <link linkend="language.oop5.properties.readonly-properties"><modifier>readonly</modifier>
|
||
修饰符</link>并禁止创建<link linkend="language.oop5.properties.dynamic-properties">动态属性</link>。此外,不能通过使用
|
||
<classname>AllowDynamicProperties</classname> 注解来添加对后者的支持。尝试这样做会触发编译错误。
|
||
</para>
|
||
<informalexample>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
#[\AllowDynamicProperties]
|
||
readonly class Foo {
|
||
}
|
||
|
||
// Fatal error: Cannot apply #[AllowDynamicProperties] to readonly class Foo
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</informalexample>
|
||
|
||
<para>
|
||
由于无类型的属性和静态属性不能用 <literal>readonly</literal> 修饰符,所以 readonly 也不会对其声明:
|
||
</para>
|
||
<informalexample>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
readonly class Foo
|
||
{
|
||
public $bar;
|
||
}
|
||
|
||
// Fatal error: Readonly property Foo::$bar must have type
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
readonly class Foo
|
||
{
|
||
public static int $bar;
|
||
}
|
||
|
||
// Fatal error: Readonly class Foo cannot declare static properties
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</informalexample>
|
||
<para>
|
||
仅当子类也是 <modifier>readonly</modifier> 类时,才可以<link
|
||
linkend="language.oop5.basic.extends">继承</link> <modifier>readonly</modifier> 类。
|
||
</para>
|
||
</sect3>
|
||
</sect2>
|
||
|
||
<sect2 xml:id="language.oop5.basic.new">
|
||
<title>new</title>
|
||
<para>
|
||
要创建一个类的实例,必须使用 <literal>new</literal>
|
||
关键字。当创建新对象时该对象总是被赋值,除非该对象定义了 <link
|
||
linkend="language.oop5.decon">构造函数</link> 并且在出错时抛出了一个 <link
|
||
linkend="language.exceptions">异常</link>。类应在被实例化之前定义(某些情况下则必须这样)。
|
||
</para>
|
||
<para>
|
||
如果一个变量包含一个类名的 <type>string</type> 和 <literal>new</literal> 时,将创建该类的一个新实例。
|
||
如果该类属于一个命名空间,则必须使用其完整名称。
|
||
</para>
|
||
|
||
<note>
|
||
<para>
|
||
如果没有参数要传递给类的构造函数,类名后的括号则可以省略掉。
|
||
</para>
|
||
</note>
|
||
|
||
<example>
|
||
<title>创建实例</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
class SimpleClass {
|
||
}
|
||
|
||
$instance = new SimpleClass();
|
||
var_dump($instance);
|
||
|
||
// 也可以这样做:
|
||
$className = 'SimpleClass';
|
||
$instance = new $className(); // new SimpleClass()
|
||
var_dump($instance);
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
<para>
|
||
PHP 8.0.0 起,支持任意表达式中使用 <literal>new</literal>。如果表达式生成一个
|
||
<type>string</type>,这将允许更复杂的实例化。表达式必须使用括号括起来。
|
||
</para>
|
||
<example>
|
||
<title>使用任意表达式创建实例</title>
|
||
<para>
|
||
在下列示例中,我们展示了多个生成类名的任意有效表达式的示例。展示了函数调用,string 连接和 <constant>::class</constant> 常量。
|
||
</para>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
class ClassA extends \stdClass {}
|
||
class ClassB extends \stdClass {}
|
||
class ClassC extends ClassB {}
|
||
class ClassD extends ClassA {}
|
||
|
||
function getSomeClass(): string
|
||
{
|
||
return 'ClassA';
|
||
}
|
||
|
||
var_dump(new (getSomeClass()));
|
||
var_dump(new ('Class' . 'B'));
|
||
var_dump(new ('Class' . 'C'));
|
||
var_dump(new (ClassD::class));
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs.8;
|
||
<screen>
|
||
<![CDATA[
|
||
object(ClassA)#1 (0) {
|
||
}
|
||
object(ClassB)#1 (0) {
|
||
}
|
||
object(ClassC)#1 (0) {
|
||
}
|
||
object(ClassD)#1 (0) {
|
||
}
|
||
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
<para>
|
||
在类定义内部,可以用 <literal>new self</literal> 和 <literal>new parent</literal> 创建新对象。
|
||
</para>
|
||
<para>
|
||
当把一个对象已经创建的实例赋给一个新变量时,新变量会访问同一个实例,就和用该对象赋值一样。此行为和给函数传递入实例时一样。可以用
|
||
<link linkend="language.oop5.cloning">克隆</link> 给一个已创建的对象建立一个新实例。
|
||
</para>
|
||
<example>
|
||
<title>对象赋值</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
class SimpleClass {
|
||
public string $var;
|
||
}
|
||
|
||
$instance = new SimpleClass();
|
||
|
||
$assigned = $instance;
|
||
$reference =& $instance;
|
||
|
||
$instance->var = '$assigned will have this value';
|
||
|
||
$instance = null; // $instance 和 $reference 变为 null
|
||
|
||
var_dump($instance);
|
||
var_dump($reference);
|
||
var_dump($assigned);
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
NULL
|
||
NULL
|
||
object(SimpleClass)#1 (1) {
|
||
["var"]=>
|
||
string(30) "$assigned will have this value"
|
||
}
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
<para>
|
||
有几种方法可以创建一个对象的实例。
|
||
</para>
|
||
<example>
|
||
<title>创建新对象</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
class Test
|
||
{
|
||
public static function getNew()
|
||
{
|
||
return new static();
|
||
}
|
||
}
|
||
|
||
class Child extends Test {}
|
||
|
||
$obj1 = new Test(); // 通过类名
|
||
$obj2 = new $obj1(); // 通过包含对象的变量
|
||
var_dump($obj1 !== $obj2);
|
||
|
||
$obj3 = Test::getNew(); // 通过类方法
|
||
var_dump($obj3 instanceof Test);
|
||
|
||
$obj4 = Child::getNew(); // 通过子类方法
|
||
var_dump($obj4 instanceof Child);
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
bool(true)
|
||
bool(true)
|
||
bool(true)
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
|
||
<para>
|
||
可以通过一个表达式来访问新创建对象的成员:
|
||
</para>
|
||
<example>
|
||
<title>访问新创建对象的成员</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
echo (new DateTime())->format('Y'), PHP_EOL;
|
||
|
||
// 从 PHP 8.4.0 起,周围的括号是可选的
|
||
echo new DateTime()->format('Y'), PHP_EOL;
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs.similar;
|
||
<screen>
|
||
<![CDATA[
|
||
2025
|
||
2025
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
|
||
<note>
|
||
<simpara>
|
||
在 PHP 7.1 之前,如果类没有定义构造函数,则不对参数进行执行。
|
||
</simpara>
|
||
</note>
|
||
</sect2>
|
||
|
||
<sect2 xml:id="language.oop5.basic.properties-methods">
|
||
<title>属性和方法</title>
|
||
<para>
|
||
类的属性和方法存在于不同的“命名空间”中,这意味着同一个类的属性和方法可以使用同样的名字。
|
||
在类中访问属性和调用方法使用同样的操作符,具体是访问一个属性还是调用一个方法,取决于你的上下文,即用法是变量访问还是函数调用。
|
||
</para>
|
||
<example>
|
||
<title>访问类属性 vs. 调用类方法</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
class Foo
|
||
{
|
||
public $bar = 'property';
|
||
|
||
public function bar() {
|
||
return 'method';
|
||
}
|
||
}
|
||
|
||
$obj = new Foo();
|
||
echo $obj->bar, PHP_EOL, $obj->bar(), PHP_EOL;
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
property
|
||
method
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
<para>
|
||
这意味着,如果你的类属性被分配给一个
|
||
<link linkend="functions.anonymous">匿名函数</link>
|
||
你将无法直接调用它。因为访问类属性的优先级要更高,在此场景下需要用括号包裹起来调用。
|
||
</para>
|
||
<example>
|
||
<title>类属性被赋值为匿名函数时的调用示例</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
class Foo
|
||
{
|
||
public $bar;
|
||
|
||
public function __construct() {
|
||
$this->bar = function() {
|
||
return 42;
|
||
};
|
||
}
|
||
}
|
||
|
||
$obj = new Foo();
|
||
|
||
echo ($obj->bar)(), PHP_EOL;
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
42
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</sect2>
|
||
|
||
<sect2 xml:id="language.oop5.basic.extends">
|
||
<!-- TODO Example about class constant redefinition -->
|
||
<!-- TODO Split into it's own page? -->
|
||
<title>extends</title>
|
||
<para>
|
||
一个类可以在声明中用 <literal>extends</literal>
|
||
关键字继承另一个类的方法和属性。PHP 不支持多重继承,一个类只能继承一个基类。
|
||
</para>
|
||
<para>
|
||
被继承的方法和属性可以通过用同样的名字重新声明被覆盖。但是如果父类定义方法或者常量时使用了
|
||
<link linkend="language.oop5.final">final</link>,则不可被覆盖。可以通过
|
||
<link linkend="language.oop5.paamayim-nekudotayim">parent::</link>
|
||
来访问被覆盖的方法或属性。
|
||
</para>
|
||
<note>
|
||
<simpara>
|
||
从 PHP 8.1.0 起,常量可以声明为 final。
|
||
</simpara>
|
||
</note>
|
||
<example>
|
||
<title>简单的类继承</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
class SimpleClass
|
||
{
|
||
function displayVar()
|
||
{
|
||
echo "Parent class\n";
|
||
}
|
||
}
|
||
|
||
class ExtendClass extends SimpleClass
|
||
{
|
||
// 同样名称的方法,将会覆盖父类的方法
|
||
function displayVar()
|
||
{
|
||
echo "Extending class\n";
|
||
parent::displayVar();
|
||
}
|
||
}
|
||
|
||
$extended = new ExtendClass();
|
||
$extended->displayVar();
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
Extending class
|
||
Parent Class
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
|
||
<sect3 xml:id="language.oop.lsp">
|
||
<title>签名兼容性规则</title>
|
||
<para>
|
||
当覆盖(override)方法时,签名必须兼容父类方法。否则会导致 Fatal 错误,PHP 8.0.0 之前是 <constant>E_WARNING</constant> 级错误。
|
||
兼容签名是指:遵守<link linkend="language.oop5.variance">协变与逆变</link>规则;强制参数可以改为可选参数;添加的新参数只能是可选;放宽可见性而不是继续限制。这就是著名的里氏替换原则(Liskov
|
||
Substitution Principle),简称 LSP。不过<link linkend="language.oop5.decon.constructor">构造方法</link>和私有(<literal>private</literal>)方法不需要遵循签名兼容规则,哪怕签名不匹配也不会导致 Fatal 错误。
|
||
</para>
|
||
<example>
|
||
<title>兼容子类方法</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
class Base
|
||
{
|
||
public function foo(int $a) {
|
||
echo "Valid\n";
|
||
}
|
||
}
|
||
|
||
class Extend1 extends Base
|
||
{
|
||
function foo(int $a = 5)
|
||
{
|
||
parent::foo($a);
|
||
}
|
||
}
|
||
|
||
class Extend2 extends Base
|
||
{
|
||
function foo(int $a, $b = 5)
|
||
{
|
||
parent::foo($a);
|
||
}
|
||
}
|
||
|
||
$extended1 = new Extend1();
|
||
$extended1->foo();
|
||
$extended2 = new Extend2();
|
||
$extended2->foo(1);
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
Valid
|
||
Valid
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
|
||
<para>
|
||
下面演示子类与父类方法不兼容的例子:通过移除参数、修改可选参数为必填参数。
|
||
</para>
|
||
<example>
|
||
<title>子类方法移除参数后,导致 Fatal 错误</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
class Base
|
||
{
|
||
public function foo(int $a = 5) {
|
||
echo "Valid\n";
|
||
}
|
||
}
|
||
|
||
class Extend extends Base
|
||
{
|
||
function foo()
|
||
{
|
||
parent::foo(1);
|
||
}
|
||
}
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs.8.similar;
|
||
<screen>
|
||
<![CDATA[
|
||
Fatal error: Declaration of Extend::foo() must be compatible with Base::foo(int $a = 5) in /in/evtlq on line 13
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
<example>
|
||
<title>子类方法把可选参数改成强制参数,导致 Fatal 错误</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
class Base
|
||
{
|
||
public function foo(int $a = 5) {
|
||
echo "Valid\n";
|
||
}
|
||
}
|
||
|
||
class Extend extends Base
|
||
{
|
||
function foo(int $a)
|
||
{
|
||
parent::foo($a);
|
||
}
|
||
}
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs.8.similar;
|
||
<screen>
|
||
<![CDATA[
|
||
Fatal error: Declaration of Extend::foo(int $a) must be compatible with Base::foo(int $a = 5) in /in/qJXVC on line 13
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
|
||
<warning>
|
||
<para>
|
||
重命名子类方法的参数名称也是签名兼容的。
|
||
然而我们不建议这样做,因为使用<link linkend="functions.named-arguments">命名参数</link>时,
|
||
这种做法会导致运行时的 <classname>Error</classname>。
|
||
</para>
|
||
<example>
|
||
<title>在子类中重命一个命名参数,导致 Error</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
class A {
|
||
public function test($foo, $bar) {}
|
||
}
|
||
|
||
class B extends A {
|
||
public function test($a, $b) {}
|
||
}
|
||
|
||
$obj = new B;
|
||
|
||
// 按 A::test() 的签名约定传入参数
|
||
$obj->test(foo: "foo", bar: "bar"); // ERROR!
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs.similar;
|
||
<screen>
|
||
<![CDATA[
|
||
Fatal error: Uncaught Error: Unknown named parameter $foo in /in/XaaeN:14
|
||
Stack trace:
|
||
#0 {main}
|
||
thrown in /in/XaaeN on line 14
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</warning>
|
||
</sect3>
|
||
</sect2>
|
||
|
||
<sect2 xml:id="language.oop5.basic.class.class">
|
||
<title>::class</title>
|
||
|
||
<para>
|
||
关键词 <literal>class</literal> 也可用于类名的解析。使用 <literal>ClassName::class</literal>
|
||
可以获取包含类 <literal>ClassName</literal> 的完全限定名称。这对使用了
|
||
<link linkend="language.namespaces">命名空间</link> 的类尤其有用。
|
||
</para>
|
||
<para>
|
||
<example xml:id="language.oop5.basic.class.class.name">
|
||
<title>类名的解析</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
namespace NS {
|
||
class ClassName {
|
||
}
|
||
|
||
echo ClassName::class;
|
||
}
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
NS\ClassName
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
<note>
|
||
<para>使用 <literal>::class</literal>
|
||
解析类名操作会在底层编译时进行。这意味着在执行该操作时,类还没有被加载。
|
||
因此,即使要调用的类不存在,类名也会被展示。在此种场景下,并不会发生错误。
|
||
</para>
|
||
<example xml:id="language.oop5.basic.class.class.fail">
|
||
<title>解析不存在的类名</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
print Does\Not\Exist::class;
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
Does\Not\Exist
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</note>
|
||
<para>
|
||
自 PHP 8.0.0 起,<literal>::class</literal> 也可用于对象。
|
||
与上述情况不同,此时解析将会在运行时进行。此操作的运行结果和在对象上调用
|
||
<function>get_class</function> 相同。
|
||
</para>
|
||
<example xml:id="language.oop5.basic.class.class.object">
|
||
<title>类名解析</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
namespace NS {
|
||
class ClassName {
|
||
}
|
||
|
||
$c = new ClassName();
|
||
print $c::class;
|
||
}
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
NS\ClassName
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</sect2>
|
||
<sect2 xml:id="language.oop5.basic.nullsafe">
|
||
<title>Nullsafe 方法和属性</title>
|
||
<para>
|
||
自 PHP 8.0.0 起,类属性和方法可以通过 "nullsafe" 操作符访问:
|
||
<literal>?-></literal>。
|
||
除了一处不同,nullsafe 操作符和以上原来的属性、方法访问是一致的:
|
||
对象引用解析(dereference)为 &null; 时不抛出异常,而是返回 &null;。
|
||
并且如果是链式调用中的一部分,剩余链条会直接跳过。
|
||
</para>
|
||
<para>
|
||
此操作的结果,类似于在每次访问前使用 <function>is_null</function>
|
||
函数判断方法和属性是否存在,但更加简洁。
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>Nullsafe 操作符</title>
|
||
<programlisting role="php" annotations="non-interactive">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
// 自 PHP 8.0.0 起可用
|
||
$result = $repository?->getUser(5)?->name;
|
||
|
||
// 上边那行代码等价于以下代码
|
||
if (is_null($repository)) {
|
||
$result = null;
|
||
} else {
|
||
$user = $repository->getUser(5);
|
||
if (is_null($user)) {
|
||
$result = null;
|
||
} else {
|
||
$result = $user->name;
|
||
}
|
||
}
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<note>
|
||
<para>
|
||
仅当 null 被认为是属性或方法返回的有效和预期的可能值时,才推荐使用 nullsafe
|
||
操作符。如果业务中需要明确指示错误,抛出异常会是更好的处理方式。
|
||
</para>
|
||
</note>
|
||
</sect2>
|
||
</sect1>
|
||
|
||
<!-- Keep this comment at the end of the file
|
||
Local variables:
|
||
mode: sgml
|
||
sgml-omittag:t
|
||
sgml-shorttag:t
|
||
sgml-minimize-attributes:nil
|
||
sgml-always-quote-attributes:t
|
||
sgml-indent-step:1
|
||
sgml-indent-data:t
|
||
indent-tabs-mode:nil
|
||
sgml-parent-document:nil
|
||
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
|
||
sgml-exposed-tags:nil
|
||
sgml-local-catalogs:nil
|
||
sgml-local-ecat-files:nil
|
||
End:
|
||
vim600: syn=xml fen fdm=syntax fdl=2 si
|
||
vim: et tw=78 syn=sgml
|
||
vi: ts=1 sw=1
|
||
-->
|