--- tags: PHP, Backend --- # PHP學習筆記(持續更新中) ## PHP的資料型態 ### Bollean布林值 算是最簡單的資料型態,可以為true跟false不區分大小寫。 ```php <?php $foo = true; // 設置foo變數為true ?> ``` 常用在判斷式 ```php <?php // 兩個等於代表操作符,檢測兩個遍量是否相等,返回布林值 if ($password == '123'){ echo 'pass'; } // 這樣寫是不必要的 if ($check == true){ echo 'check in'; } // 可以使用以下方式 if ($check){ echo 'check in'; } ?> ``` --- ### Integer整數型態 整型值 int 可以使用十进制,十六进制,八进制或二进制表示,前面可以加上可選的符號(- 或者 +)。 可以用負運算符 来表示一个負的int。 ```php <?php $a = 10; // 十進位 $a = 012; // 八進位 (等於十進位 10) $a = 0xa; // 十六進位 (等于十進制 10) $a = 0b1010; // 二進制数字 (等于十進制 10) $a = 1_0_1_0_; // 整型数值 (PHP 7.4.0 以后)輸出1010 ?> ``` 整數溢出,如果超過int範圍會轉換成float型態。 ```php <?php // var_dump()方法是判斷一個變量的類型與長度,並輸出變量的數值 $large_number = 2147483647; var_dump($large_number); // int(2147483647) $large_number = 2147483648; var_dump($large_number); // float(2147483648) ?> ``` --- ### Float符點數 擁有小數點的正負數值, 通常最大值是 1.8e308 並具有 14 位十進制數字的精度。 ```php <?php $num = 99.01; $num = -50.30; ?> ``` --- ### string字符串 由字符組成,每個字符等同於一個字節。這意味著 PHP 只能支持 256 的字符集,因此不支持 Unicode 。 >注意:在 32 位版本中,string 最大可以達到 2GB(最多 2147483647 字節)。 定義一個字符串最簡單就是用''單引號刮起來。 ```php <?php $text = 'This is test string'; echo $text; // 輸出: This is test string ?> ``` 如果包含在雙引號內就可以對特殊字符進行解析。 ```php <?php // 可以直接將變數帶入 $food = 'noodles'; echo "Hi \n"; // 換行 echo "I like to eat $food"; ?> ``` --- ### Array數組 PHP中的array 實際上是一個有序映射。映射是一種把 values 關聯到 keys 的類型。 可以用 array() 方法來結構一個 array 。接受任意數量用逗號分隔的 鍵(key) => 值(value) 。以下範例: ```php <?php $arry = array( 0 => 'apple', 1 => 'tomato', 2 => 'banana',); echo $arry[0]; // apple // 可以使用以下短數組語法 $arry = [ 0 => 'apple', 1 => 'tomato', 2 => 'banana',]; echo $arry[0]; // apple ?> ``` 沒有鍵名的索引數組 ```php <?php $array = array("apple", "tomato", "banana", ); var_dump($array); // 輸出 array(4) { [0]=> string(5) "apple" [1]=> string(6) "tomato" [2]=> string(6) "banana" } ?> ``` 數組可以用在許多地方,以下有些範例 ```php <?php $map = array( 'version' => 4, 'OS' => 'Linux', 'lang' => 'english', 'short_tags' => true ); // . . .完全等同於: $a = array(); $a['version'] = 4; $a['os'] = 'Linux'; $a['lang'] = 'english'; $a['short_tags'] = true; unset($a['os']); // 刪除 "Linux" ?> ``` 輸出集合 ```php <?php $maps = array( 'version' => 4, 'OS' => 'Linux', 'lang' => 'english', 'short_tags' => true ); foreach ($maps as $key => $value) { echo "$key is $value\n"; } // version is 4 // OS is Linux // lang is english // short_tags is 1 ?> ``` Array 是有序的。也可以使用不同的排序函数来改變順序。 數組排序範例 ```php <?php sort($files); // 對value排序 print_r($files); ?> ``` --- ### Iterable可迭代對象 它接受任何 array 或實現了 Traversable(可遍歷) 接口的對象。 這些類型都能用 foreach 迭代, 也可以和 生成器 里的 yield from 一起使用。 ```php <?php function gen(): iterable { // 建立一個可迭代生成器 yield 1; yield 2; yield 3; } $iterable = gen(); // 實例化 foreach($iterable as $value){ echo "$value\n"; } // 輸出 // 1 // 2 // 3 // 答案相同 function gen(): iterable { return [1, 2, 3]; } $iterable = gen(); foreach($iterable as $value){ echo "$value\n"; } ?> ``` --- ### Object 對象 要創建一个新的對象 object,使用 new 語句實例化一个類: ```php <?php class SayHi { function do_sayhi() { echo "Hello."; } } $bar = new foo; $bar->do_sayhi(); // Hello ?> ``` 如果將Object轉換成Object將不會有任何變化,如果其它任何類型的值被轉換成對象,將會創建一个内置類 stdClass 的實例。如果该值为 null,則新的實例為空。 array 轉換成 object 將使Key值成為屬性名並具有相對應的值,參考以下範例 ```php <?php $obj = (object) array('1' => 'foo'); var_dump(isset($obj->{'1'})); // PHP 7.2.0 後輸出 'bool(true)',之前版本會輸出 'bool(false)' var_dump(key($obj)); // PHP 7.2.0 後輸出 'string(1) "1"',之前版本輸出 'int(1)' ?> ``` 對於其他值,會包含進成員變量名 scalar。 ```php <?php $obj = (object) 'hello'; echo $obj->scalar; // outputs 'hello' ?> ``` --- ### Null類型 特殊的 null 值表示一个變數没有值。NULL 類型唯一可能的值就是 null。 在下列情况下一个變數被认为是 null: - 被賦值为 null。 - 尚未被賦值。 - 被 unset()。 null 類型只有一个值,就是不區分大小寫的常量 null。 ```php <?php $var = NULL; ?> ``` ## PHP變數命名規則 PHP 中的變數用一个美元符號後面跟變數名稱来表示。變數名是區分大小寫的。 變數名與 PHP 中其它的標籤一样遵循相同的規則。一个有效的變量名由字母或者底線開頭,後面加上任意数量的字母,数字,或者底線。 按照正常的正規表示法,他將被表達為:'\^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$'。 > $this 是一個特殊變數,他不能被賦值。 ```php= <?php $firstName = 'Wang'; $lastName = 'Joe'; echo "$firstName, $lastName"; // 输出 "Wang, Joe" $4site = 'not yet'; // 非法變數名;以數字開頭 $_4site = 'not yet'; // 合法變數名;以底線開頭 $i站點is = 'TW'; // 合法變數名;可以用中文 ?> ``` ## PHP常量 可以使用 const 關鍵字或 define() 函數兩種方法來定義一個常量。函數 define() 允許將常量定義為一個表達式,而 const 關鍵字有一些限制。一個常量一旦被定義,就不能再改變或者取消定義。 使用 const 關鍵字定義常量時,只能包含標量數據(bool、int、float 、string)。可以將常量定義為一個表達式,也可以定義為一個 array。還可以定義 resource 為常量,但應盡量避免,因為可能會造成不可預料的結果。 可以簡單的通過指定其名字來取得常量的值,與變量不同,不應該在常量前面加上 $ 符號。如果常量名是動態的,也可以用函數 constant() 來獲取常量的值。用 get_defined_constants() 可以獲得所有已定義的常量列表。 以下為定義常量範例 ```php= <?php define("CONSTANT", "Hello world."); echo CONSTANT; // 輸出 "Hello world." echo Constant; // 拋出錯誤:未定義的常量 "Constant" // 在 PHP 8.0.0 之前,输出 "Constant" 會併發出一個提示級別錯誤訊息 ?> ``` 以下範例為使用關鍵字 const 定義常量 ```php= <?php // 簡單的標量值 const CONSTANT = 'Hello World'; echo CONSTANT; // 標量表達式 const ANOTHER_CONST = CONSTANT.'; Goodbye World'; echo ANOTHER_CONST; const ANIMALS = array('dog', 'cat', 'bird'); echo ANIMALS[1]; // 將輸出 "cat" // 常量數組 define('ANIMALS', array( 'dog', 'cat', 'bird' )); echo ANIMALS[1]; // 將輸出 "cat" ?> ``` 魔術常量 | 名字 | 說明 | | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | \_\_LINE\_\_ | 文件中的當前行號 | | \_\_FILE\_\_ | 文件的完整路徑和文件名。如果用在被包含文件中,則返回被包含的文件名。 | | \_\_DIR\_\_ | 文件所在的目錄。如果用在被包括文件中,則返回被包括的文件所在的目錄。它等價於 dirname(\_\_FILE\_\_)。除非是根目錄,否則目錄中名不包括末尾的斜杠。 | | \_\_FUNCTION\_\_ | 當前函數的名稱。匿名函數則為 {closure}。 | | \_\_CLASS\_\_ | 當前類的名稱。類名包括其被聲明的作用域(例如 Foo\Bar)。當用在 trait 方法中時,\_\_CLASS\_\_ 是調用 trait 方法的類的名字 | | \_\_TRAIT\_\_ | Trait 的名字。 Trait 名包括其被聲明的作用域(例如 Foo\Bar)。 | | \_\_METHOD\_\_ | class的方法名 | | \_\_NAMESPACE\_\_ | 當前命名空間名稱 | | ClassName::class | 完整的class名稱 | 以下為魔術常量範例: ```php= <?php namespace UserModel { class User { public function __construct() { echo 'I am in '.__CLASS__."\n"; } public function showData() { echo 'I am in '.__METHOD__."\n"; } } $obj = new User; $obj->showData(); echo __NAMESPACE__; } ?> // outputs // I am in UserModel\User // I am in UserModel\User::showData // UserModel ``` ## PHP運算符 ### 算數運算符 跟國中小數學基本數學知識相同。 ```php= <?php $a = 1; $b = 2; echo $a + 1 * $b; // output: 3 ?> ``` 以下圖表為算術運算符 | 例子 | 名称 | 结果 | | -------- | ---- | ----------------------------------- | | +$a | 標識 | 根據情况將 $a 轉化為 int 或 float。 | | -$a | 取反 | $a 的負值。 | | $a + $b | 加法 | $a 和 $b 的和。 | | $a - $b | 减法 | $a 和 $b 的差。 | | $a * $b | 乘法 | $a 和 $b 的積。 | | $a / $b | 除法 | $a 除以 $b 的商。 | | $a % $b | 取模 | $a 除以 $b 的餘數。 | | $a ** $b | 求幂 | $a 的 $b次方的值。 | --- ### 賦值運算符 基本的賦值運算符是“=”。一開始可能會以為它是“等於”,其實不是的。它實際上意味著把右邊表達式的值賦給左邊的運算數 下圖為算術賦值運算符 | 例子 | 等同於 | 操作 | | --------- | ------------- | ------ | | $a += $b | $a = $a + $b | 加法 | | $a -= $b | $a = $a - $b | 减法 | | $a *= $b | $a = $a * $b | 乘法 | | $a /= $b | $a = $a / $b | 除法 | | $a %= $b | $a = $a % $b | 取餘數 | | $a **= $b | $a = $a ** $b | 指數 | 下圖為其他賦值運算符 | 例子 | 等同於 | 操作 | | -------- | ------------- | ---------- | | $a .= $b | $a = $a . $b | 字符串拼接 | | $a ?? $b | $a = $a ?? $b | NULL合併 | --- ### 位元運算 位運算符允許對整型數中指定的位進行求值和操作。 下圖為位元運算符號 | 例子 | 名稱 | 结果 | | -------- | ------------------- | -------------------------------------------------------- | | $a & $b | And(按位與) | 將把 $a 和 $b 中都為 1 的位設為 1。 | | $a \| $b | Or(按位或) | 將把 $a 和 $b 中任何一個為 1 的位設為 1。 | | $a ^ $b | Xor(按位異或) | 將把 $a 和 $b 中一個為 1 另一個為 0 的位設為 1。 | | ~ $a | Not(按位取反) | 將 $a 中為 0 的位設為 1,反之亦然。 | | $a << $b | Shift left(左移) | 將 $a 中的位向左移動 $b 次(每一次移動都表示“乘以 2”)。 | | $a >> $b | Shift right(右移) | 將 $a 中的位向右移動 $b 次(每一次移動都表示“除以 2”)。 | --- ### 比較運算符 比較運算符,如同它們名稱所暗示的,允許對兩個值進行比較。 | 例子 | 名稱 | 結果 | | --------- | -------------------------- | ------------------------------------------------------------------ | | $a == $b | 等於 | true,如果類型轉換後 $a 等於 $b。 | | $a === $b | 全等於 | true,如果 $a 等於 $b,並且它們的類型也相同。 | | $a != $b | 不等於 | true,如果類型轉換後 $a 不等於 $b。 | | $a <> $b | 不等於 | true,如果類型轉換後 $a 不等於 $b。 | | $a !== $b | 不全等 | true,如果 $a 不等於 $b,或者它們的類型不同。 | | $a < $b | 小於 | true,如果 $a 嚴格小於 $b。 | | $a > $b | 大於 | true,如果 $a 嚴格大於 $b。 | | $a <= $b | 小於等於 | true,如果 $a 小於或者等於 $b。 | | $a >= $b | 大於等於 | true,如果 $a 大於或者等於 $b。 | | $a <=> $b | 太空船運算符(組合比較符) | 當$a小於、等於、大於 $b時 分别返回一個小於、等於、大於0的 int 值。 | --- ### 執行運算符 PHP 支持一個執行運算符:反引號(\`\`)。注意這不是單引號! PHP 將嘗試將反引號中的內容作為 shell 命令來執行,並將其輸出信息返回(即,可以賦給一個變量而不是簡單地丟棄到標準輸出)。使用反引號運算符“\`”的效果與函數 shell_exec() 相同。 ```php <?php $output = `ipconfig /all`; echo "<pre>$output</pre>"; // 輸出所有IP資訊 ?> ``` --- ### 遞增/遞減運算符 :::warning 注意: 遞增/遞減運算符不影響布爾值。遞減 null 值也沒有效果,但是遞增 null 的結果是 1。:zap: ::: | 例子 | 名称 | 效果 | | ---- | ---- | --------------------------| | ++$a | 先加 | $a 的值加一,然後返回 $a。 | | $a++ | 後加 | 返回 $a,然後將 $a 的值加一。 | | --$a | 先减 | $a 的值减一, 然后返回 $a。 | | $a-- | 後减 | 返回 $a,然後將 $a 的值减一。 | 簡易的範例 ```php <?php echo "後增量(PostIncrement)\n"; $a = 5; echo "Should be 5: " . $a++ . "<br />\n"; echo "Should be 6: " . $a . "<br />\n"; echo "先增量(PreIncrement)\n"; $a = 5; echo "Should be 6: " . ++$a . "<br />\n"; echo "Should be 6: " . $a . "<br />\n"; echo "後減(PostDecrement)\n"; $a = 5; echo "Should be 5: " . $a-- . "<br />\n"; echo "Should be 4: " . $a . "<br />\n"; echo "先減(Predecrement)\n"; $a = 5; echo "Should be 4: " . --$a . "<br />\n"; echo "Should be 4: " . $a . "<br />\n"; ?> ``` --- ### 邏輯運算符 | 例子 | 名稱 | 结果 | | --------- | --------------- |----| | $a and $b | And(和) | true,如果 $a 和 $b 都為 true。| | $a or $b | Or(或) | true,如果 $a 或 $b 任一為 true。| | $a xor $b | Xor(異或) | true,如果 $a 或 $b 任一為 true,但不同時是。| | ! $a | Not(非) | true,如果 $a 不為 true。 | | $a && $b | And(與) | true,如果 $a 和 $b 都為 true。| | $a \|\| $b | Or(或) | true,如果 $a 或 $b 任一為 true。 | --- ### 字符串運算符 有兩個字符串(string)運算符。第一個是連接運算符(“.”),它返回其左右參數連接後的字符串。第二個是連接賦值運算符(“.=”),它將右邊參數附加到左邊的參數之後。 範例程式碼 ```php <?php $a = "Hello "; $b = $a . "World!"; echo $b."\n"; // output "Hello World!" $a = "Hello "; $a .= "World!"; // output "Hello World!" echo $a; ?> ``` --- ### 數組運算符 |例子 | 名稱 | 结果 | |------ |-------|----------------| |$a + $b | 聯合 | $a 和 $b 的联合。| |$a == $b | 相等 | 如果 $a 和 $b 具有相同的键/值,則為 true。| |$a === $b| 全等 | 如果 $a 和 $b 具有相同的键/值,並且順序和類型都相同則為 true。| |$a != $b | 不等於 | 如果 $a 不等於 $b 則為 true。| |$a <> $b | 不等於 | 如果 $a 不等於 $b 則為 true。| |$a !== $b| 不相等| 如果 $a 不全等於 $b 則為 true。| 如果是運用運算符將數組相加,會把加號右邊的附加到加號左邊數組, 兩個數組中都有的鍵名,則只用左邊數組中的,右邊的被忽略 以下簡單範例示範: ```php <?php $a = array("a" => "apple", "b" => "banana"); $b = array("a" => "pear", "b" => "strawberry", "c" => "cherry"); $c = $a + $b; // Union of $a and $b var_dump($c); $c = $b + $a; // Union of $b and $a var_dump($c); // 輸出: // array(3) { // ["a"]=> // string(5) "apple" // ["b"]=> // string(6) "banana" // ["c"]=> // string(6) "cherry" // } // array(3) { // ["a"]=> // string(4) "pear" // ["b"]=> // string(10) "strawberry" // ["c"]=> // string(6) "cherry" // } ?> ``` 以下示範比較數組 ```php <?php $a = array("apple", "banana"); $b = array(1 => "banana", "0" => "apple"); var_dump($a == $b); // bool(true) var_dump($a === $b); // bool(false) ?> ``` --- ### 類型運算符 instanceof 方法用於確定一個 PHP 變數是否屬於某一類 class 的實例, 也可以用來確認某一個變數,是否繼承自某一父類或子類的實例。 以下範例 ```php <?php class MyClass { } class NotMyClass { } $a = new MyClass; var_dump($a instanceof MyClass); var_dump($a instanceof NotMyClass); ?> // 輸出 // bool(True) // bool(False) ``` --- ## PHP流程控制 ### If Else 判斷句 If 可以使用在判斷某條件達成時執行語句,else則是在不滿該條下執行。 以下使用範例示範 ```php <?php if ($a > $b){ console.log("a win"); }else { console.log("b win"); } ?> ``` --- ### else if判斷句 是if 語句的延伸,可以在原來的 if 表達式值為 false 時執行不同語句。但是和 else 不一樣的是,它僅在 elseif 的條件表達式值為 true 時執行語句。 以下使用範例示範: ```php <?php if($a > $b){ echo "a is bigger than b"; } elseif($a == $b){ echo "a is equal to b"; }else{ echo "a is smaller than b"; } ?> ``` --- ### while迴圈 只要條件成立就會重複執行語句,表達式在每次開始循環前檢查,所以即使值在循環語句中改變,語句也不會停止執行,值到此次循環結束。 如果while表達式一開始的值為false,則循環語句一次都不會執行。 ```php <?php $i = 1; while ($i <= 10) { // i > 10停止迴圈 echo $i++; // 先輸出 i 後再加一 } ?> ``` --- ### do-while迴圈 和while迴圈相似,差別在於表達式的檢查是在循環結束時才檢查,所以do-while語句一定會執行循環語句一次。 以下範例: ```php <?php $i = 0; do{ echo $i; } while($i<0); ?> ``` 剛好執行一次後停下,因為條件不符合。 --- ### for迴圈 for迴圈的語法是: ``` for (expr1; expr2; expr3) statement ``` expr1:此表達式會在執行前,無條件執行一次。 expr2:在每次循環開始前求值。如果值為 true,則繼續循環,執行statement的循環語句。如果值為 false,則終止循環。 expr3:在循環之後,賦值(並執行) 以下所有範例為顯示1~10數字: ```php <?php /* example 1 */ for ($i = 1; $i <= 10; $i++) { echo $i; } /* example 2 */ for ($i=1; ; $i++){ if ($i > 10){ break; } echo $i; } /* example 3 */ for (;;){ if ($i > 10) { break; } echo $i; $i++; } /* example 4 */ for ($i = 1, $j = 0; $i <= 10; $j += $i, print $i, $i++); ?> ``` --- ### foreach foreach 語法結構提供了可以遍歷array的簡單方式,foreach只能用於array和object,用於其他數據類型的變數,會發出錯誤。 foreach語法如下: ``` foreach (iterable_expression as $value) statement foreach (iterable_expression as $key => $value) statement ``` 第一種格式遍歷,會將iterable_expression內的值賦予給$value。 第二種格式遍歷與第一種相似,但會在每次循環將當前的鍵值賦予給$key。 以下為範例: ```php <?php /* foreach example 1: value only */ $arr = array(2, 4, 6, 8); foreach($arr as $value){ echo "value = $value \n"; } /* foreach example 2: key and value */ $arr = array( "name" => "joe", "cel" => 1234567, "address" => "Taiwan" ); foreach($arr as $key => $value){ echo "$key => $value \n"; } ?> /* foreach example 3: dynamic arrays */ foreach (array(2, 4, 6, 8, 10) as $v) { echo "$v\n"; } ``` 用 list() 給嵌套的數組解包 可以遍歷一個數組的數組並且把嵌套的數組解包到循環變量中,只需將 list() 作為值提供。 ```php <?php $array = [ [1, 2], [3, 4], ]; foreach ($array as list($a, $b)) { // $a contains the first element of the nested array, // and $b contains the second element. echo "A: $a; B: $b\n"; } ?> ``` 範例輸出: ``` A: 1; B: 2 A: 3; B: 4 ``` list() 中的單元可以少於嵌套數組的,此時多出來的數組單元將被忽略,如果list中單元多於嵌套數組,則會報錯: ```php <?php $array = [ [1, 2], [3, 4], ]; foreach ($array as list($a)) { // Note that there is no $b here. echo "A: $a;\n"; } ?> ``` 輸出 ``` A: 1; A: 3; ``` --- ### break break 結束當前 for,foreach,while,do-while 或者 switch 結構的執行。 break可以接受一個可選參數,來決定要跳出幾層迴圈。 以下為範例: ```php <?php $arr = array('one', 'two', 'three', 'four', 'stop', 'five'); // 使用each() 和list() 結合用來遍歷數組 while (list (, $val) = each($arr)) { if ($val == 'stop') { break; /* You could also write 'break 1;' here. */ } echo "$val<br />\n"; } /* 使用可選参数 */ $i = 0; while (++$i) { switch ($i) { case 5: echo "At 5<br />\n"; break 1; /* 只退出 switch. */ case 10: echo "At 10; quitting<br />\n"; break 2; /* 退出 switch 和 while 循环 */ default: break; } } ?> ``` --- ### continue continue 在循環結構用用來跳過本次循環中剩餘的代碼並在條件求值為真時開始執行下一次循環。 以下為範例: ```php <?php for($i=1; $i<5; $i++){ if($i==2){ continue; } echo "$i\n"; // outputs: 1 3 4 } ?> ``` --- ### switch switch 語句類似於具有同一個表達式的一系列 if 語句。很多情況下需要把同一個變數(或表達式)與很多不同的值比較,並根據它等於哪個值來執行不同的代碼。 以下為範例,利用if/else if 和 switch實現不同方法實現相同的事: ```php <?php if ($i == 0) { echo "i equals 0"; } elseif ($i == 1) { echo "i equals 1"; } elseif ($i == 2) { echo "i equals 2"; } switch ($i) { case 0: echo "i equals 0"; break; case 1: echo "i equals 1"; break; case 2: echo "i equals 2"; break; } ?> ``` 一個 case 的特例是 default。它匹配了任何和其它 case 都不匹配的情況。例如: ```php <?php switch ($i) { case 0: echo "i equals 0"; break; case 1: echo "i equals 1"; break; case 2: echo "i equals 2"; break; default: echo "i is not equal to 0, 1 or 2"; } ?> ``` --- ### match 與switch語句類似, match表達式具有與多個備選方案進行比較的主題表達式,與switch不同,它將計算為一個很像三元表達式的值。與switch不同的是,比較的是強型別相等檢查 (\=\==) 而不是弱型別相等檢查 (==)。匹配表達式從 PHP 8.0.0 開始可用。 範例如下: ```php <?php $age = 23; $result = match (true) { $age >= 65 => 'senior', $age >= 25 => 'adult', $age >= 18 => 'young adult', default => 'kid', }; var_dump($result); echo($result); ?> // outputs: // string(11) "young adult" // young adult ``` --- ### declare > declare不是一個函數,算是一個語言結構 declare用來設定一段code的執行指令。 ```php declare (directive) statement ``` directive的部分允許設定declare代碼段的行為。 如果declare 語句後面沒有括號,則該指令適用於文件中的其餘代碼。 以下為範例: ```php <?php // 兩個相等: // 可以這樣用: declare(ticks=1) { // 這裡寫腳本 } // 也可以這樣用: declare(ticks=1); // 這裡寫腳本 ?> ``` declare指令是在文件編譯時處理的,所以指令只接受字面量的值。 無法使用變量和常量。下面為範例程式碼: ```php <?php // 有效程式碼: declare(ticks=1); // 無效,無法指定常量: const TICK_VALUE = 1; declare(ticks=TICK_VALUE); ?> ``` 可以聲明三個指令:ticks,encoding和 strict_types. ticks每次執行指定數量(N)可計時的指令時,該指令都會發送一個tick事件。可以使用register_tick_function() ,它會在每次event事件觸發時運行。 不是所有語句都可計時。通常條件表達式和參數表達式都不可計時。 以下為範例: ```php <?php declare(ticks=1); // 每次 tick 事件都會調用此函数 function tick_handler() { echo "tick_handler() called\n"; } register_tick_function('tick_handler'); $a = 1; if ($a > 0) { $a += 2; print($a); } ?> ``` 該encoding指令用於指示文件使用的字符編碼。它不能用於塊,它必須應用於整個文件。 ```php <?php declare(encoding='ISO-8859-1'); // code here ?> ``` 當strict_types指令設置,錯誤類型傳遞到函數參數時不會被強制轉換為正確的類型,而是拋出致命錯誤。 範例如下: ```php // 正常情況下 <?php function add(int $a, int $b): int { return $a + $b; } var_dump(add(1.0, 1.0)); // output // int(2) ``` 當使用strict_types後,變成強型別 ```php <?php declare(strict_types=1); //加入这句 function add(int $a, int $b): int { return $a + $b; } var_dump(add(1.0, 1.0)); // 拋出TypeError:必須為integer整數 ``` --- ### return return 將程序控制返還給調用模塊。將在調用模塊中執行的下一句表達式中繼續。 如果在一個函數中調用 return 語句,將立即結束此函數的執行並將它的參數作為函數的值返回。 return 也會終止 eval() 語句或者腳本文件的執行 eval(): 把字符串作為PHP代碼執行 --- ### require require 和 include 幾乎完全一樣,除了處理失敗的方式不同之外。 require 在出錯時產生 E_COMPILE_ERROR 級別的錯誤。換句話說將導致腳本中止而 include 只產生警告(E_WARNING),腳本會繼續運行。 --- ### include include 語句包含並運行指定文件。 以下也適用於 require。 被包含文件先按參數給出的路徑尋找,如果沒有給出目錄(只有文件名)時則按照 include_path 指定的目錄尋找。如果在 include_path 下沒找到該文件則 include 最後才在調用腳本文件所在的目錄和當前工作目錄下尋找。如果最後仍未找到文件則 include 結構會發出一條警告;這一點和 require 不同,後者會發出一個致命錯誤。 基本include用法: ```php vars.php <?php $color = 'red'; $car = 'vovlo'; ?> test.php <?php echo "A $color $car"; // A include 'vars.php'; echo "A $color $car"; // A red vovlo ?> ``` include和return語句 ```php return.php <?php $var = 'PHP'; return $var; ?> noreturn.php <?php $var = 'PHP'; ?> testreturns.php <?php $foo = include 'return.php'; echo $foo; // 輸出 'PHP' $bar = include 'noreturn.php'; echo $bar; // 輸出 1(因為成功返回true = 1) ?> ``` --- ### require_once require_once 語句和 require 語句完全相同,唯一區別是 PHP 會檢查該文件是否已經被包含過,如果是則不會再次包含。 --- ### include_once include_once 語句在腳本執行期間包含並運行指定文件。此行為和 include 語句類似,唯一區別是如果該文件中已經被包含過,則不會再次包含,且 include_once 會返回 true。如同此語句名字暗示的那樣,該文件只會包含一次。 include_once 可以用於在腳本執行期間同一個文件有可能被包含超過一次的情況下,想確保它只被包含一次以避免函數重定義,變量重新賦值等問題。 ## PHP函數 ### 自定義函數 函數可用以下語法來定義 ```php= <?php function foo($arg_1, $arg_2, /* ..., */ $arg_n) { echo "Example function.\n"; return $retval; } ?> ``` 函數不需要在使用之前被定義,除非是函數是有條件被定義時,才必須在使用前定義 舉以下範例 1. 有條件的函數: ```php= <?php $makefoo = true; /* 不能在此處調用foo()函數, 因為它還不存在,但可以調用bar()函數。*/ bar(); if ($makefoo) { function foo() { echo "I don't exist until program execution reaches me.\n"; } } /* 現在可以安全調用函數 foo() 因為 $makefoo 值為真true */ if ($makefoo) foo(); function bar() { echo "I exist immediately upon program start.\n"; } ?> ``` 2. 函數中的函數 ```php= <?php function foo() { function bar() { echo "I don't exist until foo() is called.\n"; } } /* 現在還不能調用 bar() 函數,因為它還不存在 */ foo(); /* 現在可以調用 bar() 函數了,因為 foo() 函數的執行使得 bar() 函數變為已定義的函數 */ bar(); ?> ``` :::warning PHP 中的所有函數和類都具有全局作用域,可以定義在一個函數之內而在之外調用,反之亦然。 PHP 不支持函數重載,也不可能取消定義或者重定義已聲明的函數。 ::: 還有遞迴函數,也就是自己呼叫自己 舉個費氏數列的例子(從0,1開始,後面的數是前兩個數相加) ```php= <?php function fib($n) { if($n == 0) return 0; if($n == 1) return 1; return fib($n-1) + fib($n - 2); } for($i = 0; $i <= 5; $i++){ echo fib("$i "); } // outputs // 0,1,1,2,3,5 ``` :::danger 注意: 但是要避免遞歸函數/方法調用超過 100-200 層,因為可能會使堆積崩潰從而使當前腳本終止。無限遞歸可視為編程錯誤 ::: --- ### 函數的參數 通過給予函數參數,傳遞訊息給函數,可以放入多個參數用逗號分隔,也可以設置參數默認的值。 #### 1. 向函數傳遞array: ```php= <?php function takes_array($nums) { echo '$nums[0] + $nums[1] = ', $nums[0] + $nums[1]; } ``` 從php8開始參數尾巴逗號是會被忽略的 #### 2. 使用尾巴逗號 ```php= <?php function takes_many_args( $first_name, $last_name, $age, $gender = 1, $cellphone = null, // 在 8.0.0 之前,尾部逗號是不允许的。 ) { // ... } ?> ``` 從 PHP 8.0.0 開始,不推薦在可選參數之後傳遞強制參數。 這通常可以通過刪除默認值來解決。 此規則的一個例外是 Type $param = null 形式的參數,其中 null 默認值使類型隱式可為空。 這種用法仍然被允許,但建議改用顯式可為空類型。 #### 3. 在強制參數之後傳遞可選參數 ```php= <?php function foo($a = [], $b) {} // 之前 function foo($a, $b) {} // 之後 function bar(A $a = null, $b) {} // 同時可用 function bar(?A $a, $b) {} // 官方推薦 ?> ``` #### 4. 在函數中使用默認參數 PHP 還允許使用數組 array 和特殊類型 null 作為默認參數。 ```php= <?php function makecoffee($type = "cappuccino") { return "Making a cup of $type.\n"; } echo makecoffee(); echo makecoffee(null); echo makecoffee("espresso"); ?> // outputs // Making a cup of cappuccino. // Making a cup of . // Making a cup of espresso. ``` #### 5. 函數默認參數的不正確用法 默認值必須是常量表達式,不能是,如變量,類成員,或者函數調用等。 注意當使用默認參數時,任何默認參數必須放在任何非默認參數的右側;否則,函數將不會按照預期的情況工作。 ```php= <?php function makeyogurt($type = "acidophilus", $flavour) { return "Making a bowl of $type $flavour.\n"; } echo makeyogurt("raspberry"); // won't work as expected ?> // Warning: Missing argument 2 in call to makeyogurt() ``` #### 6. 函數默認參數正確用法 ```php= <?php function makeyogurt($flavour, $type = "acidophilus") { return "Making a bowl of $type $flavour.\n"; } echo makeyogurt("raspberry"); // works as expected ?> // outputs // Making a bowl of acidophilus raspberry. ``` #### 7. 使用 ... 來訪問變量參數 PHP 在用戶自定義函數中支持可變數量的參數列表。由 ... 語法實現。 包含 ... 的參數,會轉換為指定參數變量的一個數組(類似字典),以下示例: ```php= <?php function sum(...$numbers) { $acc = 0; foreach ($numbers as $n) { $acc += $n; } return $acc; } echo sum(1, 2, 3, 4); ?> // 10 ``` #### 8. 使用...來傳遞參數 ```php= <?php function add($a, $b) { return $a + $b; } echo add(...[1, 2])."\n"; $a = [1, 2]; echo add(...$a); ?> // 3 // 3 ``` #### 9. 輸入提示的變量參數 ```php= <?php function total_intervals($unit, DateInterval ...$intervals) { // 必須傳遞DateInterval類的參數 $time = 0; foreach ($intervals as $interval) { $time += $interval->$unit; } return $time; } $a = new DateInterval('P1D'); $b = new DateInterval('P2D'); echo total_intervals('d', $a, $b).' days'; // This will fail, since null isn't a DateInterval object. echo total_intervals('d', null); ?> // outputs // 3 days // Catchable fatal error: Argument 2 passed to total_intervals() must be an instance of DateInterval, // null given, called in - on line 14 and defined in - on line 2 ``` #### 命名函數 PHP 8.0.0 開始引入了命名參數作為現有位置參數的擴展。命名參數允許根據參數名而不是參數位置向函數傳參。這使得參數的含義自成體系,參數與順序無關,並允許任意跳過默認值。 命名參數通過在參數名前加上冒號來傳遞。允許使用保留關鍵字作為參數名。參數名必須是一個標識符,不允許動態指定。 #### 10. 命名參數語法 ```php= <?php function myFunction($paramName){ echo $paramName; } myFunction(paramName: $value); // $value指定給予paramName這個參數 array_foobar(array: $value); // NOT supported. function_name($variableStoringParamName: $value); // 不能動態指定 ?> ``` #### 11. 通過位置傳參與命名參數的對比 ```php= <?php // 使用順序傳遞: array_fill(0, 100, 50); // 使用命名參數: array_fill(start_index: 0, count: 100, value: 50); ?> ``` 順序不重要,下面輸出跟上面一樣 ```php= <?php array_fill(value: 50, count: 100, start_index: 0); ?> ``` #### 12. 不可多次傳遞給同一個參數 ```php= <?php function foo($param) { ... } foo(param: 1, param: 2); // Error: Named parameter $param overwrites previous argument foo(1, param: 2); // Error: Named parameter $param overwrites previous argument ?> ``` --- ### 返回值return 值通過使用可選的返回語句返回。可以返回包括數組和對象的任意類型。返回語句會立即中止函數的運行,並且將控制權交回調用該函數的代碼行 :::warning 如果省略了 return,則返回值為 null。 ::: #### 1. 基礎語法 ```php= <?php function square($num) { return $num * $num; } echo square(5); // outputs '25'. ?> ``` #### 2. 返回一個數組以得到多個返回值 函數不能返回多個值,但可以通過返回一個數組來得到類似的效果。 ```php= <?php function small_numbers() { return [0, 1, 2]; } // 使用短數組語法將数组中的值賦给一組變數 [$zero, $one, $two] = small_numbers(); // 在 7.1.0 之前,唯一相等的選擇是使用 list() 結構 list($zero, $one, $two) = small_numbers(); ?> ``` #### 3. 返回一個引用 從函數返回一個引用,必須在函數聲明和指派返回值給一個變量時都使用引用運算符 & ```php= <?php function &returns_reference() { return $someref; } $newref =& returns_reference(); ?> ``` --- ### 可變函數 PHP 支持可變函數的概念。就是說如果一個變量名後有圓括號,PHP 就會先去尋找這個變數名稱的函數執行。可變函數可以用來實現包括回調函數,以及函數表在內的一些用途。 來看看範例 #### 1. 可變函數示例 ```php= <?php function foo() { echo "In foo()<br />\n"; } function bar($arg = '') { echo "In bar(); argument was '$arg'.<br />\n"; } // 使用名為 echo 的函數 function echoit($string) { echo $string; } $func = 'foo'; $func(); // This calls foo() $func = 'bar'; $func('test'); // This calls bar() $func = 'echoit'; $func('test'); // This calls echoit() ?> ``` #### 2. 可變方法範例 ```php= <?php class Foo { function Variable() { $name = 'Bar'; $this->$name(); // This calls the Bar() method } function Bar() { echo "This is Bar"; } } $foo = new Foo(); $funcname = "Variable"; $foo->$funcname(); // This calls $foo->Variable() ?> ``` #### 3. Variable 方法和靜態屬性範例 當調用靜態方法時,函數調用要比靜態屬性優先 ```php= <?php class Foo { static $variable = 'static property'; static function Variable() { echo 'Method Variable called'; } } echo Foo::$variable; // This prints 'static property'. It does need a $variable in this scope. $variable = "Variable"; Foo::$variable(); // This calls $foo->Variable() reading $variable in this scope. ?> ``` #### 4. 複雜的可調用對象 ```php= <?php class Foo { static function bar() { echo "bar\n"; } function baz() { echo "baz\n"; } } $func = array("Foo", "bar"); $func(); // prints "bar" $func = array(new Foo, "baz"); $func(); // prints "baz" $func = "Foo::bar"; $func(); // prints "bar" // 這樣可是會出錯的哦,沒有先實體化類別 $func = array("Foo", "baz"); $func(); // Uncaught Error: Non-static method Foo::baz() cannot be called statically in .... ?> ``` --- ### 内部(内置)函数 PHP 有很多標準的函數和結構。還有一些函數需要和特定地 PHP 擴展模塊一起編譯,否則在使用它們的時候就會得到一個致命的“未定義函數”錯誤。 例如要連接MySQL,要使用 mysqli_connect() 函數,就需要在編譯 PHP 的時候加上 MySQLi 支持。可以使用 phpinfo() 或者 get_loaded_extensions() 可以得知 PHP 加載了那些擴展庫 --- ### 匿名函数 匿名函數(Anonymous functions),也稱作閉包函數(closures),它允許臨時創建一個沒有指定名稱的函數。最經常用作回調函數 callable參數的值。當然,也有其它應用的情況。 #### 1. 匿名函數範例 ```php= <?php // 使用回調執行正則表達式搜索和替換 echo preg_replace_callback('~-([a-z])~', function ($match) { return strtoupper($match[1]); // 將所選的變成大寫,從index: 1開始過濾掉原先"-" }, 'hello-world'); // 输出 helloWorld ?> ``` 閉包函數也可以作為變量的值來使用。 PHP 會自動把此種表達式轉換成內置類 Closure 的對象實例。把一個 closure 對象賦值給一個變量的方式與普通變量賦值的語法是一樣的,最後也要加上分號 ; #### 2. 匿名函數變量賦值示例 ```php= <?php $sayHi = function($name) { echo "Hello $name"; }; // 記得加上分號 $sayHi('joe'); $sayHi('cherry'); ?> ``` #### 3. 從父作用域繼承變數 閉包可以從父作用域中繼承變量。任何此類變量都應該用 use 語言結構傳遞進去。 PHP 7.1 起,不能傳入此類變量: superglobals(超全局便量)、 $this 或者和參數重名。 從 PHP 8.0.0 開始,作用域繼承的變量列表可能包含一個尾部的逗號,這個逗號將被忽略 ```php= <?php $message = 'hello'; // 没有 "use" php會不知道這變數從哪來 $example = function () { var_dump($message); }; $example(); // 繼承父作用域 $message $example = function () use ($message) { var_dump($message); }; $example(); // Inherited variable's value is from when the function // is defined, not when called $message = 'world'; $example(); // 不會受到改變因為已經在上面先調用了(14行) // Reset message $message = 'hello'; // Inherit by-reference $example = function () use (&$message) { var_dump($message); }; $example(); // The changed value in the parent scope // is reflected inside the function call // 父作用域更改值,影響函式回調 $message = 'world'; $example(); // Closures can also accept regular arguments // 閉包可使用常規變數 $example = function ($arg) use ($message) { var_dump($arg . ' ' . $message); }; $example("hello"); ?> // outputs // Notice: Undefined variable: ... // NULL // string(5) "hello" // string(5) "hello" // string(5) "hello" // string(5) "world" // string(11) "hello world" ``` --- ### 箭頭函數 箭頭函數是更簡潔的匿名函數,都是屬於Closure類(閉包)的實現 箭頭函數的語法為 `fn (argument_list) => expr。` 箭頭函數支持與 匿名函數 相同的功能,只是其父作用域的變量總是自動的。 當表達式中使用的變量是在父作用域中定義的,它將被隱式地按值捕獲。 在下面的例子中,函數 $fn1 和 $fn2 的行為是一樣的 #### 1. 箭頭函數自動捕捉變數的值 ```php= <?php $y = 1; $fn1 = fn($x) => $x + $y; // 相當於 using $y by value: $fn2 = function ($x) use ($y) { return $x + $y; }; // var_export -- 輸出或返回一個變量的字串表示 var_export($fn1(3)); // 4 ?> ``` #### 2. 箭頭函數自動捕捉變量的值,即使在嵌套的情況下 通常在A程序執行中,尚未結束前又開始執行B程序,B程序結束後,繼續執行A程序,就稱為嵌套。 ```php= <?php $z = 1; $fn = fn($x) => fn($y) => $x * $y + $z; // 輸出 21 var_export($fn(5)(4)); ?> ``` #### 3. 箭頭函數有效用法例子 和匿名函數一樣,箭頭函數語法同樣允許標準的函數聲明,包括參數和返回類型、缺省值、變量,以及通過引用傳遞和返回。以下都是箭頭函數的有效例子。 ```php= <?php fn(array $x) => $x; static fn(): int => $x; fn($x = 42) => $x; fn(&$x) => $x; fn&($x) => $x; fn($x, ...$rest) => $rest; ?> ``` #### 4. 來自外部範圍的值不能在箭頭函數內修改 箭頭函數會自動綁定上下文變量,這相當於對箭頭函數內部使用的每一個變量 \$x 執行了一個 use\(\$x)。這意味著不可能修改外部作用域的任何值,若要實現對值的修改,可以使用 匿名函數 來替代。 ```php= <?php $x = 1; $fn = fn() => $x++; // 不會影響 x 的值 $fn(); var_export($x); // 輸出 1 ?> ``` ## class類 ### class基本 每個類的定義都以關鍵字 class 開頭,後面跟著類的名,再一個括號,裡面包含有類的屬性與方法的定義。 一個類可以包含有屬於自己的 常量,變量(稱為“屬性”)以及函數(稱為“方法”)。 當要調用內部屬性或方法時可以使用\$this,$this 是一個到當前對象的引用。 ##### 1. 簡單的類定義 ```php= <?php class SimpleClass { // 聲明屬性 public $var = 'a default value'; // 聲明方法 public function displayVar() { echo $this->var; } } ?> ``` #### new 要創建一個類的實例,必須使用 new 關鍵字。當創建新對象時該對象總是被賦值,除非該對象定義了 構造函數 並且在出錯時拋出了一個 異常。 構造函數 : 類中的一個特殊函數,當使用 new 操作符創建一個類的實例時,構造函數將會自動調用。當函數與類同名時,這個函數將成為構造函數。如果一個類沒有構造函數,則調用基類的構造函數,如果有的話 類應在被實例化之前定義(某些情況下則必須這樣)。 如果在 new 之後跟著的是一個包含有類名的字符串 string,則該類的一個實例被創建。如果該類屬於一個命名空間,則必須使用其完整名稱。 :::warning 如果沒有參數要傳遞給類的構造函數,類名後的括號則可以省略掉。 ::: ##### 2. 創建實例 ```php= <?php $instance = new SimpleClass(); // 也可以這樣做: $className = 'SimpleClass'; $instance = new $className(); // new SimpleClass() ?> ``` ##### 3. 創建新對象 ```php= <?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= <?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= <?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= <?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= <?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= <?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= <?php namespace NS { class ClassName { } echo ClassName::class; } ?> // 輸出 // NS\ClassName ``` :::warning 使用 ::class 解析類名操作會在底層編譯時進行。這意味著在執行該操作時,類還沒有被加載。因此,即使要調用的類不存在,類名也會被展示。在此種場景下,並不會發生錯誤。 ##### 10. 解析不存在的類名 ```php= <?php print Does\Not\Exist::class; ?> ``` 輸出: `Does\Not\Exist` ::: ##### 11. 類名解析 自 PHP 8.0.0 起,與上述情況不同,此時解析將會在運行時進行。此操作的運行結果和 get_class() 函數一致 ```php= <?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 // 自 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; } } ?> ``` :::warning 注意 僅當null 被認為是屬性或方法返回的有效和預期的可能值時,才推薦使用nullsafe 操作符。如果業務中需要明確指示錯誤,拋出異常會是更好的處理方式。 ::: --- ### class屬性 類的變量成員叫做“屬性”,或者叫“字段”、“特徵”,在PHP文檔統一稱為“屬性”。屬性聲明是由關鍵字 public(公開),protected(非公開) 或者 private(私有) 開頭,然後跟一個普通的變量聲明來組成。屬性中的變量可以初始化,但是初始化的值必須是常數,這裡的常數是指 PHP 腳本在編譯階段時就可以得到其值,而不依賴於運行時的信息才能求值。 在類的成員方法裡面,可以用 ->(對象運算符):\$this->property(其中 property 是該屬性名)這種方式來訪問非靜態屬性。 靜態屬性則是用 ::(雙冒號):self::$property 來訪問。 以下為屬性聲明範例 ```php= <?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); //在 PHP 5.3.0 及之後,下面的聲明也正確 public $var8 = <<<'EOD' hello world EOD; } ?> ``` --- ### 類常量 可以把在類中始終保持不變的值定義為常量。在定義和使用常量的時候不需要使用 $ 符號。 常量的值必須是一個定值,不能是變量,類屬性,數學運算的結果或函數調用。 如何定義和使用類常量,查看以下範例 ```php= <?php class MyClass { const constant = 'constant value'; function showConstant() { echo self::constant . "\n"; } } echo MyClass::constant . "\n"; $classname = "MyClass"; echo $classname::constant . "\n"; // 自 5.3.0 起 $class = new MyClass(); $class->showConstant(); echo $class::constant."\n"; // 自 PHP 5.3.0 起 ?> ``` --- ### 類的自動加載 在編寫有關於對象(OOP) 程序時,很多開發者為每個類新建一個 PHP 文件。這會帶來一個煩惱:每個腳本的開頭,都需要包含(include)一個長長的列表(每個類都有個文件)。 在 PHP 5 中,已經不再需要這樣了。 spl_autoload_register() 函數可以註冊任意數量的自動加載器,當使用尚未被定義的類(class)和接口(interface)時自動去加載。通過註冊自動加載器,腳本引擎在 PHP 出錯失敗前有了最後一個機會加載所需的類。 :::info 儘管 __autoload() 函數也能自動加載類和接口,但更建議使用 spl_autoload_register() 函數。 spl_autoload_register() 提供了一種更加靈活的方式來實現類的自動加載(同一個應用中,可以支持任意數量的加載器,比如第三方庫中的)。因此,不再建議使用 __autoload() 函數,在以後的版本中它可能被棄用 ::: ##### 1. 自動加載範例 本例嘗試分別從 MyClass1.php 和 MyClass2.php 文件中加載 MyClass1 和 MyClass2 類。 ```php= <?php spl_autoload_register(function ($class_name) { require_once $class_name . '.php'; }); $obj = new MyClass1(); $obj2 = new MyClass2(); ?> ``` ##### 2. 自動加載在 PHP 5.3.0+ 中的異常處理 ```php= <?php spl_autoload_register(function ($name) { echo "Want to load $name.\n"; throw new Exception("Unable to load $name."); }); try { $obj = new NonLoadableClass(); } catch (Exception $e) { echo $e->getMessage(), "\n"; } ?> // Want to load NonLoadableClass. // Unable to load NonLoadableClass. ``` ### 構造函數和析構函數 #### 構造函數 `__construct(mixed ...$values = ""): void` PHP 允許開發者在一個類中定義一個方法作為構造函數。具有構造函數的類會在每次創建新對象時先調用此方法,所以非常適合在使用對象之前做一些初始化工作 :::warning 注意: 如果子類中定義了構造函數則不會隱式調用其父類的構造函數。要執行父類的構造函數,需要在子類的構造函數中調用 parent::__construct()。如果子類沒有定義構造函數則會如同一個普通的類方法一樣從父類繼承(假如沒有被定義為 private 的話)。 ::: ##### 1. 繼承中的構造函數 ```php= <?php class BaseClass { function __construct() { print "In BaseClass constructor\n"; } } class SubClass extends BaseClass { function __construct() { parent::__construct(); print "In SubClass constructor\n"; } } class OtherSubClass extends BaseClass { // 繼承 BaseClass 的構造函數 } // In BaseClass constructor $obj = new BaseClass(); // In BaseClass constructor // In SubClass constructor $obj = new SubClass(); // In BaseClass constructor $obj = new OtherSubClass(); ?> ``` ##### 2. 構造器函數 因此可以定義任何數量的參數,可以是必選、可以有類型、可以有默認值。構造器的參數放在類名後的括號裡調用。 ```php= <?php class Point { protected int $x; protected int $y; public function __construct(int $x, int $y = 0) { $this->x = $x; $this->y = $y; } } // 兩個參數都傳入 $p1 = new Point(4, 5); // 僅傳入必填的參數。 $y 會默認為0。 $p2 = new Point(4); // 使用命名參數(PHP 8.0 起): $p3 = new Point(y: 5, x: 4); ?> ``` 如果一個類沒有構造函數,以及構造函數的參數不是必填項時,括號就可以省略。 #### 析構函數 `__destruct(): void` ##### 1. 析構函數範例 ```php= <?php class MyDestructableClass { function __construct() { print "In constructor\n"; } function __destruct() { print "Destroying " . __CLASS__ . "\n"; } } $obj = new MyDestructableClass(); ``` 和構造函數一樣,父類的析構函數不會被引擎暗中調用。要執行父類的析構函數,必須在子類的析構函數體中顯式調用 parent::__destruct()。 此外也和構造函數一樣,子類如果自己沒有定義析構函數則會繼承父類的。 析構函數即使在使用 exit() 終止腳本運行時也會被調用。 在析構函數中調用 exit() 將會中止其餘關閉操作的運行。 :::warning 注意: 析構函數在腳本關閉時調用,此時所有的 HTTP 頭信息已經發出。腳本關閉時的工作目錄有可能和在 SAPI(如 apache)中時不同 ::: :::warning 注意: 試圖在析構函數(在腳本終止時被調用)中拋出一個異常會導致致命錯誤。 ::: --- ### 訪問控制(可見性) 對屬性或方法的訪問控制(PHP 7.1.0 以後支持常量),通過在前面添加關鍵字 public(公有),protected(受保護)或 private(私有)來實現的。 被定義為公有的類成員可以在任何地方被訪問。被定義為受保護的類成員則可以被其自身以及其子類和父類訪問。被定義為私有的類成員則只能被其定義所在的類訪問。 #### 屬性的訪問控制 ##### 1. 屬性聲明 類屬性必須定義為公有,受保護,私有之一。如果用 var 定義,則被視為公有 ```php= <?php /** * Define MyClass */ class MyClass { public $public = 'Public'; protected $protected = 'Protected'; private $private = 'Private'; function printHello() { echo $this->public; echo $this->protected; echo $this->private; } } $obj = new MyClass(); echo $obj->public; // 這行能被正常執行 echo $obj->protected; // 這行會產生一個致命錯誤 echo $obj->private; // 這行也會產生一個致命錯誤 $obj->printHello(); // 輸出 Public、Protected 和 Private /** * Define MyClass2 */ class MyClass2 extends MyClass { // 可以對 public 和 protected 進行重定義,但 private 而不能 public $public = 'Public2'; protected $protected = 'Protected2'; function printHello() { echo $this->public; echo $this->protected; echo $this->private; } } $obj2 = new MyClass2(); echo $obj2->public; // 這行能被正常執行 echo $obj2->protected; // 這行會產生一個致命錯誤 echo $obj2->private; // 未定義 private $obj2->printHello(); // 輸出 Public2、Protected2 和 Undefined ?> ``` #### 方法的訪問控制 ##### 方法聲明 ```php= <?php /** * Define MyClass */ class MyClass { // 聲明一個公有的構造函數 public function __construct() { } // 聲明一個公有的方法 public function MyPublic() { } // 聲明一個受保護的方法 protected function MyProtected() { } // 聲明一個私有的方法 private function MyPrivate() { } // 預設為公開方法 function Foo() { $this->MyPublic(); $this->MyProtected(); $this->MyPrivate(); } } $myclass = new MyClass; $myclass->MyPublic(); // 這行能被正常執行 $myclass->MyProtected(); // 會出現致命錯誤 $myclass->MyPrivate(); // 會出現致命錯誤 $myclass->Foo(); // 公有,受保護,私有都可以執行 /** * Define MyClass2 */ class MyClass2 extends MyClass { // 為公開的函數 function Foo2() { $this->MyPublic(); $this->MyProtected(); $this->MyPrivate(); // 會發生錯誤 } } $myclass2 = new MyClass2; $myclass2->MyPublic(); // 这行能被正常执行 $myclass2->Foo2(); // 公有,受保護,私有不可執行只能在原class裡執行 class Bar { public function test() { $this->testPrivate(); $this->testPublic(); } public function testPublic() { echo "Bar::testPublic\n"; } private function testPrivate() { echo "Bar::testPrivate\n"; } } class Foo extends Bar { public function testPublic() { echo "Foo::testPublic\n"; } private function testPrivate() { echo "Foo::testPrivate\n"; } } $myFoo = new foo(); $myFoo->test(); // Bar::testPrivate // Foo::testPublic ?> ``` #### 常量的控制訪問 ##### php 7.1 up 中常量聲明 ```php= <?php /** * Define MyClass */ class MyClass { // 公有常量 public const MY_PUBLIC = 'public'; // 受保護的常量 protected const MY_PROTECTED = 'protected'; // 私有常量 private const MY_PRIVATE = 'private'; public function foo() { echo self::MY_PUBLIC; echo self::MY_PROTECTED; echo self::MY_PRIVATE; } } $myclass = new MyClass(); MyClass::MY_PUBLIC; // 這行可以正常執行 MyClass::MY_PROTECTED; // 這行會產生一個致命錯誤 MyClass::MY_PRIVATE; // 這行會產生一個致命錯誤 $myclass->foo(); // 將會輸出:Public Protected Private /** * Define MyClass2 */ class MyClass2 extends MyClass { // This is public function foo2() { echo self::MY_PUBLIC; echo self::MY_PROTECTED; echo self::MY_PRIVATE; // 這行會產生一個致命錯誤 } } $myclass2 = new MyClass2; echo MyClass2::MY_PUBLIC; // 這行可以正常執行 $myclass2->foo2(); // 將會輸出:Public Protected,MY_PRIVATE 是私有常量,無法輸出 ?> ``` #### 其他對象的訪問控制 ##### 訪問同一個對像類型的私有成員 同一個類的對象即使不是同一個實例也可以互相訪問對方的私有與受保護成員。這是由於在這些對象的內部具體實現的細節都是已知的 ```php= <?php class Test { private $foo; public function __construct($foo) { $this->foo = $foo; } private function bar() { echo 'Accessed the private method.'; } public function baz(Test $other) { // We can change the private property: $other->foo = 'hello'; var_dump($other->foo); // We can also call the private method: $other->bar(); } } $test = new Test('test'); $test->baz(new Test('other')); ?> // outputs // string(5) "hello" // Accessed the private method. ``` ### 範圍解析操作符(::) php可以利用這一對冒號來訪問靜態成員,類常量,還可以用於覆蓋類中的屬性和方法 ##### 在類的外部使用 :: 操作符 ```php= <?php class MyClass { const CONST_VALUE = 'A constant value'; } $classname = 'MyClass'; echo $classname::CONST_VALUE; // 自 PHP 5.3.0 起 echo MyClass::CONST_VALUE; ?> ``` self,parent 和 static 這三個特殊的關鍵字是用於在類定義的內部對其屬性或方法進行訪問的。 ##### 在類定義內部使用 :: ```php= <?php class OtherClass extends MyClass { public static $my_static = 'static var'; public static function doubleColon() { echo parent::CONST_VALUE . "\n"; echo self::$my_static . "\n"; } } $classname = 'OtherClass'; echo $classname::doubleColon(); // 自 PHP 5.3.0 起 OtherClass::doubleColon(); ?> ``` 當一個子類覆蓋其父類中的方法時,PHP 不會調用父類中已被覆蓋的方法。是否調用父類的方法取決於子類。這種機制也作用於構造函數和析構函數,重載以及魔術方法。 ##### 調用父類的方法 ```php= <?php class MyClass { protected function myFunc() { echo "MyClass::myFunc()\n"; } } class OtherClass extends MyClass { // 覆蓋了父類定義 public function myFunc() { // 但還是可以調用父類中被覆盖的方法 parent::myFunc(); echo "OtherClass::myFunc()\n"; } } $class = new OtherClass(); $class->myFunc(); ?> ``` ### 靜態(static)關鍵字 聲明類屬性或方法為靜態,就可以不實例化類而直接訪問。靜態屬性不能通過一個類已實例化的對象來訪問(但靜態方法可以) #### 靜態方法 ##### 靜態方法示例 ```php= <?php class Foo { public static function aStaticMethod() { // ... } } Foo::aStaticMethod(); $classname = 'Foo'; $classname::aStaticMethod(); ?> ``` #### 靜態屬性 靜態屬性不可以由對象通過 -> 操作符來訪問。 就像其它所有的 PHP 靜態變量一樣,靜態屬性在初始化時遵循和 const 表達式一樣的規則: 有一些特定的表達式是可行的,取決於它們是否能在編譯時就計算出值。 通過變量來引用一個類是可行的,但這個變量的值不能是一個保留字 (例如self,parent和 static) ```php= <?php class Foo { public static $my_static = 'foo'; public function staticValue() { return self::$my_static; } } class Bar extends Foo { public function fooStatic() { return parent::$my_static; } } print Foo::$my_static . "\n"; $foo = new Foo(); print $foo->staticValue() . "\n"; print $foo->my_static . "\n"; // 未定義的 "屬性" my_static print $foo::$my_static . "\n"; $classname = 'Foo'; print $classname::$my_static . "\n"; print Bar::$my_static . "\n"; $bar = new Bar(); print $bar->fooStatic() . "\n"; ?> ``` ### 抽象類 PHP也支援抽象類的和抽象方法,被定義為抽象類的方法不能被實體化,在任何一個類別中, 如果他至少有一個方法被聲明為抽象,那這個類必須要被聲明為抽象類。被定義為抽象類的方法只能聲明他該怎麼使用,不能定義具體功能實現。 當有類別繼承抽象類別時,子類別必須要定義父類別中的所以抽象方法,另外,這些方法的訪問控制必須和父類中一樣(或者更為寬鬆)。例如某個抽象方法是被聲明為受保護的,那麼子類中實現的方法就應該聲明為受保護的或者公有的,而不能定義為私有的。 此外方法的調用方式必須匹配,即類型和所需參數數量必須一致。例如,子類定義了一個可選參數,而父類抽象方法的聲明里沒有,則兩者的聲明並無衝突。 > 這也適用於 PHP 5.4 起的構造函數。在 PHP 5.4 之前的構造函數聲明可以不一樣的。 #### 1. 抽象類範例1 ```php= <?php abstract class AbstractClass { // 子類必須定義這些方法,否則會出錯 abstract protected function getValue(); abstract protected function prefixValue($prefix); // 普通方法(非抽象方法) public function printOut() { print $this->getValue() . "\n"; } } class ConcreteClass1 extends AbstractClass { protected function getValue() { return "ConcreteClass1"; } public function prefixValue($prefix) { return "{$prefix}ConcreteClass1"; } } class ConcreteClass2 extends AbstractClass { public function getValue() { return "ConcreteClass2"; } public function prefixValue($prefix) { return "{$prefix}ConcreteClass2"; } } $class1 = new ConcreteClass1; $class1->printOut(); echo $class1->prefixValue('FOO_') ."\n"; $class2 = new ConcreteClass2; $class2->printOut(); echo $class2->prefixValue('FOO_') ."\n"; ?> // outputs // ConcreteClass1 // FOO_ConcreteClass1 // ConcreteClass2 // FOO_ConcreteClass2 ``` #### 2. 抽象類範例2 ```php= <?php abstract class AbstractClass { // 我们的抽象方法仅需要定义需要的参数 abstract protected function prefixName($name); } class ConcreteClass extends AbstractClass { // 子類的方法可以定義父類方法中不存在的可選參數,不衝突 public function prefixName($name, $separator = ".") { if ($name == "Pacman") { $prefix = "Mr"; } elseif ($name == "Pacwoman") { $prefix = "Mrs"; } else { $prefix = ""; } return "{$prefix}{$separator} {$name}"; } } $class = new ConcreteClass; echo $class->prefixName("Pacman"), "\n"; echo $class->prefixName("Pacwoman"), "\n"; ?> // outputs // Mr. Pacman // Mrs. Pacwoman ``` ### Interface對象接口也稱介面 使用介面(interface),可以指定某個類必須實現哪些方法,但不需要定義這些方法的具體內容。由於介面(interface)和類(class)、trait 共享了命名空間,所以它們不能重名。介面就像定義一個標準的類一樣,通過 interface 關鍵字替換掉 class 關鍵字來定義,但其中所有的方法都是空的。介面中定義的所有方法都必須是公有,這是介面的特性。在實踐中,往往出於兩個輔助目的使用介面: 因為實現了同一個介面,所以開發者創建的對象雖然源自不同的類,但可能可以交換使用。常用於多個數據庫的服務訪問、多個支付網關、不同的緩存策略等。可能不需要任何代碼修改,就能切換不同的實現方式。能夠讓函數與方法接受一個符合介面的參數,而不需要關心對像如何做、如何實現。這些介面常常命名成類似 Iterable、Cacheable、Renderable, 以便於體現出功能的含義。 :::warning 注意: 雖然沒有禁止,但是強烈建議不要在接口中使用 構造器。因為這樣在對象實現接口時,會大幅降低靈活性。此外,也不能強制確保構造器遵守繼承規則,將導致不可預料的行為結果。 ::: #### 實現implements 要使用一個介面,使用 implements 操作符。類中必須使用介面中定義的所有方法,否則會報一個致命錯誤。類可以實現多個接口,用逗號來分隔多個接口的名稱。 :::danger 警告:類使用(implement)兩個接口時,如果它們定義了相同名稱的方法,只有簽名相同的時候才是允許的。 ::: :::danger 警告:使用介面的時候,class 中的參數名稱不必和介面完全一致。然而, PHP 8.0 起語法開始支持命名參數, 也就是說調用方會依賴介面中參數的名稱。因此,強烈建議開發者的參數的命名,在類和介面中保持一致。 ::: :::warning 注意:接口也可以通過 extends 操作符繼承 ::: :::warning 注意:類實現介面時,必須以兼容的簽名定義介面中所有方法。 ::: ##### 1. 介面範例 ```php= <?php // 聲明一個'Template'介面 interface Template { public function setVariable($name, $var); public function getHtml($template); } // 使用介面 // 正確寫法如下 class WorkingTemplate implements Template { private $vars = []; public function setVariable($name, $var) { $this->vars[$name] = $var; } public function getHtml($template) { foreach($this->vars as $name => $value) { $template = str_replace('{' . $name . '}', $value, $template); } return $template; } } // 下面的寫法是錯誤的,會報錯,因為沒有使用到該介面方法 getHtml(): // Fatal error: Class BadTemplate contains 1 abstract methods // and must therefore be declared abstract (Template::getHtml) class BadTemplate implements Template { private $vars = []; public function setVariable($name, $var) { $this->vars[$name] = $var; } } ?> ``` ##### 2. 可擴充介面 ```php= <?php interface A { public function foo(); } interface B extends A { public function baz(Baz $baz); } // 正確寫法 class C implements B { public function foo() { } public function baz(Baz $baz) { } } // 錯誤寫法導致致命錯誤 class D implements B { public function foo() { } public function baz(Foo $foo) // 參考錯誤 { } } ?> ``` ##### 3. 拓展多介面 ```php= <?php interface A { public function foo(); } interface B { public function bar(); } interface C extends A, B { public function baz(); } class D implements C { public function foo() { } public function bar() { } public function baz() { } } ?> ``` ##### 4. 使用介面常量 ```php= <?php interface A { const B = 'Interface constant'; } // 输出接口常量 echo A::B; // 錯誤寫法,因為常量不能被覆蓋。接口常量的概念和類常量是一樣的。 class B implements A { const B = 'Class constant'; } ?> ``` ##### 5. 抽象(abstract)類的介面使用 ```php= <?php interface A { public function foo(string $s): string; public function bar(int $i): int; } // 抽象類可能僅實現了接口的一部分。 // 擴展該抽象類時必須實現剩餘部分 abstract class B implements A { pubic function foo(string $s): string { return $s . PHP_EOL; } } class C extends B { public function bar(int $i): int { return $i * 2; } } ?> ``` ##### 6. 同時使用繼承和介面 ```php= <?php class One { /* ... */ } interface Usable { /* ... */ } interface Updatable { /* ... */ } // 關鍵字順序至關重要: 'extends' 必須在前面 class Two extends One implements Usable, Updatable { /* ... */ } ?> ``` 資料來源: https://www.php.net/