iT邦幫忙

2021 iThome 鐵人賽

DAY 14
0
自我挑戰組

後端新手PHP+Laravel筆記系列 第 14

[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/


上一篇
[Day13]PHP 匿名函式及箭頭函式
下一篇
[Day15]PHP Class 類別02
系列文
後端新手PHP+Laravel筆記30

尚未有邦友留言

立即登入留言