PSR-1 提到基本程式設計規範,PSR-12 是對已棄用的 PSR-2 再修改,對於 PSR-1 的規範不足之處,再補充說明。
內文筆者會依自己的見解翻譯原文及介紹整理,如果詞不及義或錯誤,歡迎指正。
目錄
- 檔案
- PHP 語法標籤
- 字元編碼
- 副作用
- 行
- 長度
- 縮排
- 換行
- 關鍵字和型別
- 類別
- 命名空間和類別名稱
- 常數
- 屬性
- 命名
- 宣告
- 方法
- 命名
- 參數
- 宣告
- 抽象、終結繼承
- 靜態
- 函式
- 方法及函式呼叫
- 流程控制
- if, elseif, else
- switch, case
- while, do while
- for
- foreach
- try, catch, finally
- 運算子
- 一元運算子
- 二元運算子
- 三元運算子
- 閉包
- 匿名類別
- 總結
檔案 (Files)
PHP 語法標籤 (PHP Tags)
- 檔案中的 PHP 語法標籤只能使用
<?php
和短輸出標籤<?=
,其它不得使用。 - 單純只有 PHP 程式碼的檔案,清除結尾的
?>
標簽。
不建議使用的範例:
- PHP 7.0 以前在 php.ini 中啟用
asp_tags = 1
(預設值:0)後可使用 ASP 樣式標簽。<%
。PHP 7.0 以後此設定已棄用。 - php.ini 中
short_open_tag = 1
(預設值:1)可使用短輸出標籤<?
,PHP 5.4 以後,短輸出標籤<?=
永久可用,不受此值影響。
字元編碼 (Character Encoding)
- 必須 UTF-8 編碼,不帶 BOM(位元組順序記號)。(PSR-1)
以 Notepad++ 為例,轉換檔案的字元編碼,請勿選帶有 BOM 的選項。
副作用 (Side Effects)
- 一個 PHP 檔案應該是「只有聲明類別、函式及變數且沒有其它副作用」,不然就是只有「產生副作用的邏輯操作」。兩者取其一,不應該並存。
「副作用」一詞指的是,執行的邏輯操作並不是和宣告類別、函式和常數等等,而是透過(包含但不限於)載入檔案、修改 ini 設定、產生輸出、丟出異常、修改全域變數或靜態變數、讀寫文件等等。
以下是含有副作用的範例:
<?php
// 副作用: 改變 ini 的設定值。
ini_set('error_reporting', E_ALL);
// 副作用: 載入檔案。
include "file.php";
// 副作用: 產生輸出。
echo "<html>\n";
// 宣告函式
function foo()
{
echo 'Hello, world.';
}
以下是不含有副作用的範例:
<?php
// 宣告函式
function foo()
{
echo 'Hello, world.';
}
// 條件式的宣告 *不是* 副作用。
if (! function_exists('bar')) {
function bar()
{
echo 'Hello, world.';
}
}
要嘛,只有宣告函式、類別或常數。
<?php
ini_set('error_reporting', E_ALL);
include __DIR__ . '../vendor/autoload.php';
include __DIR__ . '../app/init.php';
不然,就只有副作用的邏輯執行。不然,就只有副作用的邏輯執行。
行 (Lines)
長度 (Length)
行的長度沒有硬性規定,以下規範都是軟性建議:
- 長度的限制上限為 120 個字元。
- 長度不應超過 80 個字元,超過的話拆分多行。
- 行尾不能有空格。
- 可以加上空白的行以增加可讀性。
- 程式的陳述句每行只能一個。
縮排 (Indenting)
- 必須使用 4 個空白字元,不能使用 TAB 進行縮排。
換行 (Line Feed)
- 換行的分隔符必須為 Unix-like 的
LF
。 - 最後一行不得為非空白行,只留一行以
LF
行分隔符當作結尾。
不建議使用的範例:
- 在編輯器的右下方通常會標註此檔案的行分隔符,使用 Window 作業系統的話,務必從
CRLF
切到LF
並存檔。
- 檔案尾端只能留一行。
關鍵字和型別 (Keywords and Types)
類別 (Classes)
命名空間和類別名稱 (Namespace and Class Names)
- 命名空間和類別命名必須依照 PSR-4 自動載入的規範。
指的是每一個類別是一個獨立的檔案。類別名稱依 StudlyCaps
命名。(字首大寫駝峰式命名法)。
定義的例子:
namespcae VendorName\PackageName;
class HelloWorld
{
// 省略
}
VendorName 指的是套件供應商名稱,你可以依喜好命名,將你所有的 PHP 作品都統一一個響亮的品牌名稱是很不錯的想法喔。例如筆者的套件的 VendorName 都命名為 Shieldon,接來才接各套件的名稱,例如 Messenger、Event 之類。
駝峰式命名指的是,連續的英文單字,沒有空格直接連在一起,每個字的字首大寫,看起來就像駱駝的駝峰上下起伏一樣。
hello world => HelloWorld
get user name => GetUserName
say hello to the world => SayHelloToTheWorld
常數 (Constants)
- 常數名稱使用大寫字母,並以底線
_
作為分隔。
範例:
<?php
namespace Vendor\Model;
class Foo
{
const VERSION = '1.0';
const DATE_APPROVED = '2012-06-01';
}
屬性 (Properties)
命名 (Naming)
沒有特別建議命名方式,例如:
字首大寫駝峰式命名法:$StudlyCaps
字首小寫駝峰式命名法:$camelCase
底線分隔式命名法:$under_score
- 但無論是採用那一種命名法,都該在合理範圍內保持一致性。這個範圍可以是供應商層級、套件層級、類別層級或方法層級。
- 不能用加「底線作為前輟」的方式來識別 protected 或 private 屬性。這並沒有意義。
宣告 (Declaration)
-
必須宣告屬性的「可見範圍類型」 (public, protected, private)。
-
「var」關鍵字不能用來宣告屬性。
-
不能在同一行宣告多個屬性。
-
宣告屬性和可見範圍之間只能有一個空格。
宣告屬性的範例:
<?php
namespace Vendor\Package;
class ClassName
{
public $foo = null;
public static int $bar = 0;
}
方法 (Methods)
命名 (Naming)
- 必須是字首小寫駝峰式命名法。
- 不能用加「底線作為前輟」的方式來識別 protected 或 private 屬性。這並沒有意義。
參數 (Arguments)
- 在每個逗號
,
之前,不能有空格。 - 有預設值的參數,必須在參數列表的最後面。
參數分長多行:
- 參數列表可以分成多行,每個參數獨立一行並加入一次縮排。
- 右括號
)
和左大括{
號必須獨立一行,之間放一個空格。
範例:
<?php
namespace Vendor\Package;
class ClassName
{
public function aVeryLongMethodName(
ClassTypeHint $arg1,
&$arg2,
array $arg3 = []
) {
// 方法內容
}
}
宣告 (Declaration)
- 宣告方法的「可見範圍類型」(public, protected, private)。
- 方法名稱和左括號
(
之間不能有空格。左大括號{
和右大括號}
必須自成一行。
宣告方法的範例。注意括號()
,逗號,
,空格和大括號{}
的位置:
<?php
namespace Vendor\Package;
class ClassName
{
public function fooBarBaz($arg1, &$arg2, $arg3 = [])
{
// 方法內容
}
}
有宣告返回的型別時:
- 冒號後面的型別宣告,必有有一個空格。冒號
:
和右括號)
沒有空格。 - 可空值的型別宣告,問號
?
和型別宣告之間沒影空格。
範例:
<?php
declare(strict_types=1);
namespace Vendor\Package;
class ReturnTypeVariations
{
public function functionName(int $arg1, $arg2): string
{
return 'foo';
}
public function anotherFunction(
string $foo,
string $bar,
int $baz
): string {
return 'foo';
}
public function functionName(?string $arg1, ?int &$arg2): ?string
{
return 'foo';
}
}
有參考運算子時:
- 有參考運算子
&
時,和參數間不能有空格。(同上例)
有可變長度參數時:
- 有可變長度參數
...
時,和參數間不能有空格。
範例:
public function process(string $algorithm, ...$parts)
{
// 方法內容
}
有參考運算子及可變長度參數併用時:
- 參考運算子
&
及可變長度參數...
兩者之間不能有空格。
public function process(string $algorithm, &...$parts)
{
// 方法內容
}
抽象(abstract)、終結繼承 (final)
- 如果有的話,
abstract
及final
宣告必須在可見範圍宣告 (public, protected, private) 之前。
範例:
<?php
namespace Vendor\Package;
abstract class ClassName
{
protected static $foo;
abstract protected function zim();
final public static function bar()
{
// 方法內容
}
}
靜態 (static)
- 如果有的話,
static
宣告必須在可見範圍宣告 (public, protected, private) 之後。(如上例)
函式 (Functions)
函式的規範與類別的方法相同。
方法及函式呼叫 (Method and Function Calls)
- 方法或函式和左括号
(
之間不能有空格。 - 右括號
)
之前及之後不能有空格。 - 參數列表中,每一個逗號
,
之前不能有空格,之後保持一個空格。
範例:
<?php
bar();
$foo->bar($arg1);
Foo::bar($arg2, $arg3);
- 參數列表可以拆分為多行,每個參數獨立一行。
範例:
<?php
$foo->bar(
$longArgument,
$longerArgument,
$muchLongerArgument
);
- 匿名函式的參數可以單獨拆分多行,但其參數列表不可再拆分為多行。
範例:
<?php
somefunction($foo, $bar, [
// ...
], $baz);
$app->get('/hello/{name}', function ($name) use ($app) {
return 'Hello ' . $app->escape($name);
});
流程控制 (Control Structures)
if, elseif, else
if
的結構看起來如下,右大括號}
和elseif
、else
都在同一行。
範例:
<?php
if ($expr1) {
// if 內容
} elseif ($expr2) {
// elseif 內容
} else {
// else 內容
}
- 使用
elseif
而不是else if
。 - 多個條件判斷式可拆分多行。
- 每一個條件判斷式皆獨立一行,並縮排一次。
- 布林值的條件判斷式必須集中在一開始或最後面,而不是混合在其它判斷式之間。
範例:
<?php
if (
$expr1
&& $expr2
) {
// if 內容
} elseif (
$expr3
&& $expr4
) {
// elseif 內容
}
switch, case
witch
的結構看起來如下。case
宣告必須從switch
縮排一次。break
或其它終止流程的關鍵字必須和 `case 內容的縮排同層級。- 在
case
內容不為空,且無終止流程的關鍵字,必須加上註解// no break
範例:
<?php
switch ($expr) {
case 0:
echo 'First case, with a break';
break;
case 1:
echo 'Second case, which falls through';
// no break
case 2:
case 3:
case 4:
echo 'Third case, return instead of break';
return;
default:
echo 'Default case';
break;
}
- 條件式可分成多行。隨後的條件式至少一次縮排。
- 布林值的條件判斷式必須集中在一開始或最後面,而不是混合在其它判斷式之間。
- 右括號
)
和左大括{
號必須獨立一行,之間放一個空格。
範例:
<?php
switch (
$expr1
&& $expr2
) {
// 內容
}
while, do while
while
宣告的結構看起如下:
範例:
<?php
while ($expr) {
// 內容
}
- 條件式可分成多行。隨後的條件式至少一次縮排。
- 布林值的條件判斷式必須集中在一開始或最後面,而不是混合在其它判斷式之間。
- 右括號
)
和左大括{
號必須獨立一行,之間放一個空格。
範例:
<?php
while (
$expr1
&& $expr2
) {
// 內容
}
do while
宣告的結構看起如下:
<?php
do {
// 內容
} while ($expr);
- 條件式可分成多行。隨後的條件式至少一次縮排。
- 布林值的條件判斷式必須集中在一開始或最後面,而不是混合在其它判斷式之間。
- 右括號
)
和左大括{
號必須獨立一行,之間放一個空格。
範例:
<?php
do {
// 內容
} while (
$expr1
&& $expr2
);
for
for
宣告的結構看起如下:
範例:
<?php
for ($i = 0; $i < 10; $i++) {
// for 內容
}
- 條件式可分成多行。隨後的條件式至少一次縮排。
- 布林值的條件判斷式必須集中在一開始或最後面,而不是混合在其它判斷式之間。
- 右括號
)
和左大括{
號必須獨立一行,之間放一個空格。
範例:
<?php
for (
$i = 0;
$i < 10;
$i++
) {
// for 內容
}
foreach
foreach
宣告的結構看起如下:
範例:
<?php
foreach ($iterable as $key => $value) {
// foreach 內容
}
try, catch, finally
try
、catch
、finally
宣告的結構看起如下:
範例:
<?php
try {
// try 內容
} catch (FirstThrowableType $e) {
// catch 內容
} catch (OtherThrowableType | AnotherThrowableType $e) {
// catch 內容
} finally {
// finally 內容
}
運算子 (Operators)
一元運算子 (Unary operators)
-
遞增及遞減的運算子和變數之間不能有空格。
$i++; ++$j;
-
型別轉換運算子的括號中不能有空格
$intValue = (int) $input;
二元運算子 (Binary operators)
- 所有二進位算術,比較,賦值,位移,邏輯、字串和型別運算子在前面及後面至少一個空格。
if ($a === $b) {
$foo = $bar ?? $a ?? $b;
} elseif ($a > $b) {
$foo = $a + $b * $c;
}
三元運算子 (Ternary operators)
- 三元運算子必須於
?
及:
之間至少留一個空格。
$variable = $foo ? 'foo' : 'bar';
- 三元運算子如果省略中間運算式,則規則同二元運算子。
$variable = $foo ?: 'bar';
閉包 (Closures)
- 宣告閉包函式必須在
function
關鍵字之後留一空格,並在use
關鍵字之前留一空格。 - 左大括號
{
在同一行, 右大括號}
在函式主體之後單獨一行。 - 參數列表中,變數和括號
(
、)
之間不得有空格。 - 參數列表中,每一個逗號
,
之前不能有空格,之後保持一個空格。 - 有預設值的參數,必須在參數列表的最後面。
<?php
$closureWithArgsAndVars = function ($arg1, $arg2) use ($var1, $var2) {
// 內容
};
- 如果有宣告返回型別,則規格與函式相同。
- 如果有使用
use
關鍵字,則冒號:
必須在use
的右括號之後且之間不能有空格。
<?php
$closureWithArgsVarsAndReturn = function ($arg1, $arg2) use ($var1, $var2): bool {
// 內容
};
- 參數列表可以分成多行,每個參數獨立一行並加入一次縮排。
<?php
$longArgs_noVars = function (
$longArgument,
$longerArgument,
$muchLongerArgument
) {
// 內容
};
$noArgs_longVars = function () use (
$longVar1,
$longerVar2,
$muchLongerVar3
) {
// 內容
};
$longArgs_longVars = function (
$longArgument,
$longerArgument,
$muchLongerArgument
) use (
$longVar1,
$longerVar2,
$muchLongerVar3
) {
// 內容
};
$longArgs_shortVars = function (
$longArgument,
$longerArgument,
$muchLongerArgument
) use ($var1) {
// 內容
};
$shortArgs_longVars = function ($arg) use (
$longVar1,
$longerVar2,
$muchLongerVar3
) {
// 內容
};
- 以上規則在參數列中使用閉包時同樣適用。
<?php
$foo->bar(
$arg1,
function ($arg2) use ($var1) {
// 內容
},
$arg3
);
匿名類別 (Anonymous Classes)
- 匿名類別適用和閉包函式同樣規則。
<?php
$instance = new class {};
- 如果
implements
之後的介面在同一行,左大括號{
可以在同一行。<?php
$instance = new class extends \Foo implements \HandleableInterface {
// 類別內容
};
- 如果 implements
之後的介面列表有換行,右大括號{
放在最後一個介面的下一行,獨立一行。
```php
$instance = new class extends \Foo implements
\ArrayAccess,
\Countable,
\Serializable
{
// 類別內容
};
總結
雖然規格看似很多,但還是有沒提到的地方,就依個人習慣了。
例如 Zend Framework,程式碼中會看到像這樣的排版:
public function __construct(
string $containerName,
Router\RouterInterface $router,
?TemplateRendererInterface $template = null
) {
$this->containerName = $containerName;
$this->router = $router;
$this->template = $template;
}
變數的 =
會併排的很整齊。它的陣列排法也是:
return [
'paths' => [
'app' => [__DIR__ . '/../templates/app'],
'error' => [__DIR__ . '/../templates/error'],
'layout' => [__DIR__ . '/../templates/layout'],
],
];
陣列的 =>
也會依層級對齊。
如果有 PSR 建議規範沒提到的地方,不妨參考知名框架的程式碼,能得到一些想法。
程式碼風格建議規範可以大致看一下、瞭解一下即可。一開始還不熟悉的時候,我們有工具可以輔助。再後面的章節會介紹到 PHP CS Fixer 這套工具,能幫助我們在寫程式時提醒應該注意的地方。
下一篇筆者將介紹 PSR-4 自動載入,我們明天見囉。
留言