[Day14]PHP Class 类别01

class类

class基本概念

每个类的定义都以关键字 class 开头,後面跟着类的名,再一个括号,里面包含有类的属性与方法的定义。

一个类可以包含有属於自己的 常量,变量(称为“属性”)以及函数(称为“方法”)。

当要调用内部属性或方法时可以使用$this,$this 是一个到当前对象的引用。

1. 简单的类定义
<?php
class SimpleClass
{
    // 声明属性
    public $var = 'a default value';

    // 声明方法
    public function displayVar() {
        echo $this->var;
    }
}
?>

new

要创建一个类的实例,必须使用 new 关键字。当创建新对象时该对象总是被赋值,除非该对象定义了 构造函数 并且在出错时抛出了一个 异常。

构造函数 : 类中的一个特殊函数,当使用 new 操作符创建一个类的实例时,构造函数将会自动调用。当函数与类同名时,这个函数将成为构造函数。如果一个类没有构造函数,则调用基类的构造函数,如果有的话

类应在被实例化之前定义(某些情况下则必须这样)。

如果在 new 之後跟着的是一个包含有类名的字符串 string,则该类的一个实例被创建。如果该类属於一个命名空间,则必须使用其完整名称。

如果没有参数要传递给类的构造函数,类名後的括号则可以省略掉。

2. 创建实例
<?php
$instance = new SimpleClass();

// 也可以这样做:
$className = 'SimpleClass';
$instance = new $className(); // new SimpleClass()
?>
3. 创建新对象
<?php
class Test
{
    static public 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);
?>

// outputs
// bool(true)
// bool(true)
// bool(true)

属性和方法

类的属性和方法存在於不同的“命名空间”中,这代表说同一个类的属性和方法可以使用同样的名字。在类中访问属性和调用方法使用同样的操作符,那到底是访问一个属性还是调用一个方法,取决於你的上下文,即用法是变数的访问还是函数的调用。

4. 访问类属性 vs. 调用类方法
<?php
class Foo
{
    public $bar = 'property';

    public function bar() {
        return 'method';
    }
}

$obj = new Foo();
echo $obj->bar, PHP_EOL, $obj->bar(), PHP_EOL; // PHP_EOL: php换行符号

// property
// method
?>
5. 类属性被赋值为匿名函数时的调用示例

如果你的类属性被分配给一个 匿名函数 你将无法直接调用它。因为访问class类属性的优先级要更高,在此场景下需要用括号括起来使用。

<?php
class Foo
{
    public $bar;

    public function __construct() {
        $this->bar = function() {
            return 10;
        };
    }
}

$obj = new Foo();

echo ($obj->bar)(), PHP_EOL; // 10

extends 继承

一个类可以在声明中用 extends 关键字继承另一个类的方法和属性。 PHP 不支持多重继承,一个类只能继承一个基类。

被继承的方法和属性可以通过用同样的名字重新声明被覆盖。但是如果父类定义方法时使用了 final关键字,则该方法不可被覆盖。可以通过 parent:: 来访问被覆盖的方法或属性

6. 简单的类继承范例
<?php
class ExtendClass extends SimpleClass
{
    // 同样名称的方法,将会覆盖父类的方法
    function displayVar()
    {
        echo "Extending class\n";
        parent::displayVar();
    }
}

$extended = new ExtendClass();
$extended->displayVar();

// outputs
// Extending class
// a default value
?>

签名兼容性规则

当覆盖(override)方法时,签名必须兼容父类方法。否则会导致 Fatal 错误(PHP 8.0.0 之前是 E_WARNING 级错误)。兼容签名是指:遵守协变与逆变规则; 强制参数可以改为可选参数;新参数为可选参数。这就是着名的里氏替换原则(Liskov Substitution Principle),简称 LSP。不过构造器和 私有(private)方法不需要遵循签名兼容规则, 哪怕签名不匹配也不会导致 Fatal(致命) 错误

7. 兼容子类方法
<?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);


// 输出
// Valid
// Valid
8.子类方法移除参数后,导致 Fatal(致命) 错误

演示子类与父类方法不兼容的例子:通过移除参数、修改可选参数为必填参数。

<?php

class Base
{
    public function foo(int $a = 5) {
        echo "Valid\n";
    }
}

class Extend extends Base
{
    function foo()
    {
        parent::foo(1);
    }
}

// 声明必须与Base class 的foo方法兼容
//Fatal error: Declaration of Extend::foo() must be compatible with Base::foo(int $a = 5)

::class

关键词 class 也可用於类名的解析。使用 ClassName::class 可以获取包含类 ClassName 的完全限定名称。这对使用了 命名空间 的类尤其有用。

9. 类名的解析
<?php
namespace NS {
    class ClassName {
    }

    echo ClassName::class;
}
?>
// 输出
// NS\ClassName

使用 ::class 解析类名操作会在底层编译时进行。这意味着在执行该操作时,类还没有被加载。因此,即使要调用的类不存在,类名也会被展示。在此种场景下,并不会发生错误。

10. 解析不存在的类名
<?php
print Does\Not\Exist::class;
?>

输出: Does\Not\Exist

11. 类名解析

自 PHP 8.0.0 起,与上述情况不同,此时解析将会在运行时进行。此操作的运行结果和 get_class() 函数一致

<?php
namespace NS {
    class ClassName {
    }
}
$c = new ClassName();
print $c::class;
?>

// 输出
// NS\ClassName

Nullsafe 方法和属性

自PHP 8.0.0 起,类属性和方法可以通过"nullsafe" 操作符访问:除了一处不同,nullsafe 操作符和以上原来的属性、方法访问是一致的: 对象引用解析(dereference)为 并且如果是链式调用中的一部分,剩余链条会直接跳过。?->nullnull

此操作的结果,类似於在每次访问前使用

12. Nullsafe 操作符
<?php

// 自 PHP 8.0.0 起可用
$result = $repository?->getUser(5)?->name;

// 上面的code和下面相同
if (is_null($repository)) {
    $result = null;
} else {
    $user = $repository->getUser(5);
    if (is_null($user)) {
        $result = null;
    } else {
        $result = $user->name;
    }
}
?>

注意
仅当null 被认为是属性或方法返回的有效和预期的可能值时,才推荐使用nullsafe 操作符。如果业务中需要明确指示错误,抛出异常会是更好的处理方式。

资料来源: https://www.php.net/


<<:  Day 10 Compose UI migration 到目前的专案上!

>>:  Day 09: 机器学习你知多少?

【Day20】比较Nodelist与HTML collection的差异

前面提到透过DOM API取得网页节点的方法: //根据传入的id 名称,找到DOM里面相同id名...

【C++】Binary Search Tree

Binary Search Tree的优势在於寻找、插入的时间复杂度较低,它只需要O(log n)~...

[想试试看JavaScript ] 运算子 (算术运算子)

这是我第一次写铁人赛,我没有先规划大纲 所以运算子我就先写比较简单的部分,对於比较难的部分,後面有篇...

EP 12 - [TDD] 设定环境变数

Youtube 频道:https://www.youtube.com/c/kaochenlong ...

工业控制系统(ICS)

以下是 NIST SP 800-82 R2 的摘要: 控制系统用於许多不同的工业部门和关键基础设施,...