每個類的定義都以關鍵字 class 開頭,後面跟著類的名,再一個括號,裡面包含有類的屬性與方法的定義。
一個類可以包含有屬於自己的 常量,變量(稱為“屬性”)以及函數(稱為“方法”)。
當要調用內部屬性或方法時可以使用$this,$this 是一個到當前對象的引用。
<?php
class SimpleClass
{
// 聲明屬性
public $var = 'a default value';
// 聲明方法
public function displayVar() {
echo $this->var;
}
}
?>
要創建一個類的實例,必須使用 new 關鍵字。當創建新對象時該對象總是被賦值,除非該對象定義了 構造函數 並且在出錯時拋出了一個 異常。
構造函數 : 類中的一個特殊函數,當使用 new 操作符創建一個類的實例時,構造函數將會自動調用。當函數與類同名時,這個函數將成為構造函數。如果一個類沒有構造函數,則調用基類的構造函數,如果有的話
類應在被實例化之前定義(某些情況下則必須這樣)。
如果在 new 之後跟著的是一個包含有類名的字符串 string,則該類的一個實例被創建。如果該類屬於一個命名空間,則必須使用其完整名稱。
如果沒有參數要傳遞給類的構造函數,類名後的括號則可以省略掉。
<?php
$instance = new SimpleClass();
// 也可以這樣做:
$className = 'SimpleClass';
$instance = new $className(); // new SimpleClass()
?>
<?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)
類的屬性和方法存在於不同的“命名空間”中,這代表說同一個類的屬性和方法可以使用同樣的名字。在類中訪問屬性和調用方法使用同樣的操作符,那到底是訪問一個屬性還是調用一個方法,取決於你的上下文,即用法是變數的訪問還是函數的調用。
<?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
?>
如果你的類屬性被分配給一個 匿名函數 你將無法直接調用它。因為訪問class類屬性的優先級要更高,在此場景下需要用括號括起來使用。
<?php
class Foo
{
public $bar;
public function __construct() {
$this->bar = function() {
return 10;
};
}
}
$obj = new Foo();
echo ($obj->bar)(), PHP_EOL; // 10
一個類可以在聲明中用 extends 關鍵字繼承另一個類的方法和屬性。 PHP 不支持多重繼承,一個類只能繼承一個基類。
被繼承的方法和屬性可以通過用同樣的名字重新聲明被覆蓋。但是如果父類定義方法時使用了 final關鍵字,則該方法不可被覆蓋。可以通過 parent:: 來訪問被覆蓋的方法或屬性
<?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(致命) 錯誤
<?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
演示子類與父類方法不兼容的例子:通過移除參數、修改可選參數為必填參數。
<?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 也可用於類名的解析。使用 ClassName::class 可以獲取包含類 ClassName 的完全限定名稱。這對使用了 命名空間 的類尤其有用。
<?php
namespace NS {
class ClassName {
}
echo ClassName::class;
}
?>
// 輸出
// NS\ClassName
使用 ::class 解析類名操作會在底層編譯時進行。這意味著在執行該操作時,類還沒有被加載。因此,即使要調用的類不存在,類名也會被展示。在此種場景下,並不會發生錯誤。
<?php
print Does\Not\Exist::class;
?>
輸出: Does\Not\Exist
自 PHP 8.0.0 起,與上述情況不同,此時解析將會在運行時進行。此操作的運行結果和 get_class() 函數一致
<?php
namespace NS {
class ClassName {
}
}
$c = new ClassName();
print $c::class;
?>
// 輸出
// NS\ClassName
自PHP 8.0.0 起,類屬性和方法可以通過"nullsafe" 操作符訪問:除了一處不同,nullsafe 操作符和以上原來的屬性、方法訪問是一致的: 對象引用解析(dereference)為 並且如果是鍊式調用中的一部分,剩餘鏈條會直接跳過。?->nullnull
此操作的結果,類似於在每次訪問前使用
<?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/