iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 11
0
Software Development

PHP 大師之路 - 開源的技術淬練系列 第 11

Day 11 - PHP 程式碼風格:PSR-1, PSR-12

  • 分享至 

  • xImage
  •  

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 的選項。

bkwSvB9.png

副作用 (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 並存檔。

3vzIKra.png

  • 檔案尾端只能留一行。

yFGkvoS.png


關鍵字和型別 (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)

  • 如果有的話,abstractfinal 宣告必須在可見範圍宣告 (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 的結構看起來如下,右大括號 }elseifelse 都在同一行。

範例:

<?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

trycatchfinally 宣告的結構看起如下:

範例:

<?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 之後的介面列表有換行,右大括號{放在最後一個介面的下一行,獨立一行。
$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 自動載入,我們明天見囉。


本文同步更新於 TerryL 部落格 Day 11 - PHP 程式碼風格:PSR-1, PSR-12,歡迎前往討論。


上一篇
Day 10 - PHP 建議規範 (PSR)
下一篇
Day 12 - PHP 自動載入機制:PSR-4
系列文
PHP 大師之路 - 開源的技術淬練30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言