什么是PSR瑞驱?
PSR是PHP Standards Recommendation的簡稱捌刮,這個是php-fig組織制定的一套規(guī)范浸间。至今恨憎,php-fig已經(jīng)發(fā)布了五個規(guī)范:
- PSR-0:自動加載標準,2014-10-21該標準已經(jīng)被廢棄验夯,使用PSR-4替代猖吴,不再細講
- PSR-1:基本的編碼風格
- PSR-2:編碼風格(更嚴格)
- PSR-3:日志記錄器接口
- PSR-4:自動加載
PSR-1
PHP標簽:
PHP代碼必須放在<?php ?>
標簽或<?= ?>
標簽中。
副作用:
一個PHP文件可以定義符號(比如類海蔽、函數(shù)、常量等)绑谣,或者執(zhí)行只有唯一副作用的操作(比如輸出結(jié)果党窜、處理數(shù)據(jù)等),但是不能同時做這兩件事借宵,盡量是一個PHP文件的功能單一幌衣。在操作的時候盡量把變量、類壤玫、函數(shù)的聲明分開豁护,通過include
或require
文件的方式來使用。
如下不符合規(guī)范:
<?php
// 改變設(shè)置
ini_set('error_reporting', E_ALL);
// 加載文件
include "file.php";
// 打印輸出
echo "<html>\n";
// 聲明
function foo()
{
// function body
}
符合規(guī)范如下:
<?php
// 聲明
function foo()
{
// function body
}
// 條件判斷
if (! function_exists('bar')) {
function bar()
{
// function body
}
}
命名空間和類:
命名空間和類必須遵循PSR-4自動加載器標準欲间。
類的名稱:
每個類都有自己的命名空間楚里,且都在頂級命名空間下,類名必須使用駝峰式(CamelCase)猎贴。
PHP 5.3 及以上班缎,必須使用正式的命名空間,例如:
<?php
// PHP 5.3 及以后
namespace Vendor\Model;
class Foo
{
}
PHP 5.3一下應該使用Vendor_
開頭的偽命名空間約定她渴,例如:
<?php
// PHP 5.3以下
class Vendor_Model_Foo
{
}
常量:
常量必須全部是用大寫达址,并且使用下劃線(_)分開。例如:
<?php
namespace Vendor\Model;
class Foo
{
const VERSION = '1.0';
const DATE_APPROVED = '2012-06-01';
}
類的方法:
類的方法必須使用小寫字母開頭的駝峰式(camelCase)命名惹骂。
PSR-2
PSR-2是對PSR-1的PHP的擴充苏携。
貫徹PSR-1:
使用PSR-2代碼標準之前要先貫徹PSR-1的代碼標準。
文件和代碼行:
PHP文件必須使用Unix風格的換行符(LF对粪, linefeed)右冻,最后要有一個空行,僅包含PHP代碼的文件而且不能使用PHP關(guān)閉標簽?>
著拭,每行代碼不應該超過80個字符纱扭,每行末尾不能有空格,每行只能有一條語句儡遮,可以在適當?shù)牡胤教砑涌招刑岣叽a的閱讀性乳蛾。
不加上
?>
關(guān)閉標簽,可以避免意料之外的輸出錯誤鄙币,如果加上關(guān)閉標簽肃叶,且在關(guān)閉標簽后有空行,那么空行會被當成輸出十嘿,導致意想不到的錯誤因惭。
縮進:
必須以4個空格為縮進,不能使用制表符(Tab鍵)縮進绩衷。
在不同的編輯器中蹦魔,空格的渲染效果基本一致,而制表符的寬度各有差異咳燕。
關(guān)鍵字:
PHP的關(guān)鍵字必須使用小寫勿决,而且true
, false
, 和 null
也必須小寫。
命名空間和use
聲明:
現(xiàn)在招盲,namespace
聲明之后必須要有一個空行低缩,而且use
聲明必須放在namespace
之后,必須分別使用use
引入命名空間曹货,而且use
后要有空行咆繁,例如:
<?php
namespace Vendor\Package;
use FooClass;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;
// ... additional PHP code ...
類的繼承和實現(xiàn):
extends
和implements
關(guān)鍵字必須和類名在同一行,類控乾、接口和Traits
定義體的起始括號應該在類名之后新起一行么介,結(jié)束括號也必須新起一行,例如:
<?php
namespace Vendor\Package;
use FooClass;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;
class ClassName extends ParentClass implements \ArrayAccess, \Countable
{
// constants, properties, methods
}
如果implements
后面后很多類導致一行很長蜕衡,可以依次將需要的類另起新行并縮進4個空格壤短,如下:
<?php
namespace Vendor\Package;
use FooClass;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;
class ClassName extends ParentClass implements
\ArrayAccess,
\Countable,
\Serializable
{
// constants, properties, methods
}
可見性:
類中的每個屬性和方法都要聲明可見性,有public
慨仿、private
和protected
久脯,不能使用var
關(guān)鍵詞來聲明,老版本的PHP會在私有屬性前加上_
镰吆,一行只能聲明一個屬性帘撰,例如:
<?php
namespace Vendor\Package;
class ClassName
{
public $foo = null;
}
方法:
類中的所有方法也應該定義可見性,方法名后面不能有空格万皿,方法體的括號位置和類定義體的括號位置一樣摧找,都要新起一行核行,結(jié)束括號也要新起一行。方法參數(shù)的起始圓括號之后沒有空格蹬耘,結(jié)束括號之前也沒有空格芝雪,有多個參數(shù)是,每個參數(shù)的逗號后面加一個空格综苔,例如:
<?php
namespace Vendor\Package;
class ClassName
{
public function fooBarBaz($arg1, &$arg2, $arg3 = [])
{
// method body
}
}
如果參數(shù)比較多惩系,需要換行時,可以如下:
<?php
namespace Vendor\Package;
class ClassName
{
public function aVeryLongMethodName(
ClassTypeHint $arg1,
&$arg2,
array $arg3 = []
) {
// method body
}
}
abstract
如筛、final
和static
:
現(xiàn)在堡牡,abstract
、final
必須在可見性修飾符之前杨刨,static
聲明必須放在可見性修飾符之后晤柄,例如:
<?php
namespace Vendor\Package;
abstract class ClassName
{
protected static $foo;
abstract protected function zim();
final public static function bar()
{
// method body
}
}
方法和函數(shù)的調(diào)用:
在調(diào)用方法和函數(shù)時,圓括號必須跟在函數(shù)名之后拭嫁,函數(shù)的參數(shù)之間有一個空格:
<?php
bar();
$foo->bar($arg1);
Foo::bar($arg2, $arg3);
如果參數(shù)比較多可免,一行放不下時,如下處理:
<?php
$foo->bar(
$longArgument,
$longerArgument,
$muchLongerArgument
);
PHP的控制結(jié)構(gòu):
PHP的控制結(jié)構(gòu)包括if做粤、else浇借、elseif、switch怕品、case妇垢、while、do while肉康、for闯估、foreach、try和catch吼和。如果這些關(guān)鍵詞后面有一對原括號涨薪,開始括號前必須有一個空格,與方法和類的定義體不同炫乓,控制結(jié)構(gòu)關(guān)鍵詞后面的起始括號應該和控制結(jié)構(gòu)關(guān)鍵詞寫在同一行刚夺,例如:
<?php
$gorilla = new \Animals\Gorilla;
$ibis = new \Animals\StrawNeckedIbis;
if ($gorilla->isWake() === true) {
do {
$gorilla->beatChest();
} while ($ibis->isAsleep() === true);
$ibis->flyAway();
}
PHP閉包函數(shù):
閉包函數(shù)在聲明時,function
關(guān)鍵詞后必須有一個空格末捣,同時use
關(guān)鍵詞前后也必須有一個空格侠姑。起始大括號不需要另起新行,詳細的如下代碼:
<?php
$closureWithArgs = function ($arg1, $arg2) {
// body
};
$closureWithArgsAndVars = function ($arg1, $arg2) use ($var1, $var2) {
// body
};
閉包函數(shù)有多個參數(shù)時箩做,處理方式和方法的參數(shù)一樣:
<?php
$longArgs_noVars = function (
$longArgument,
$longerArgument,
$muchLongerArgument
) {
// body
};
$noArgs_longVars = function () use (
$longVar1,
$longerVar2,
$muchLongerVar3
) {
// body
};
$longArgs_longVars = function (
$longArgument,
$longerArgument,
$muchLongerArgument
) use (
$longVar1,
$longerVar2,
$muchLongerVar3
) {
// body
};
$longArgs_shortVars = function (
$longArgument,
$longerArgument,
$muchLongerArgument
) use ($var1) {
// body
};
$shortArgs_longVars = function ($arg) use (
$longVar1,
$longerVar2,
$muchLongerVar3
) {
// body
};
注意:以上規(guī)則同樣適用于將閉包作為函數(shù)或方法的參數(shù)莽红,如下:
<?php
$foo->bar(
$arg1,
function ($arg2) use ($var1) {
// body
},
$arg3
);
PSR-3
與PSR-1和PSR-2不同,PSR-3規(guī)定了一套通用的日志記錄器接口(Psr\Log\LoggerInterface
)邦邦,為了符合PSR-3規(guī)范安吁,框架必須實現(xiàn)該規(guī)范中的接口醉蚁,這樣可以更多的兼容第三方應用。PSR-3規(guī)范中包含了9個方法柳畔,每個方法都對應了RFC 5424協(xié)議的一個日志級別馍管,而且都接受兩個參數(shù)$message
和$context
郭赐,如下:
<?php
namespace Psr\Log;
/**
* Describes a logger instance
*
* The message MUST be a string or object implementing __toString().
*
* The message MAY contain placeholders in the form: {foo} where foo
* will be replaced by the context data in key "foo".
*
* The context array can contain arbitrary data, the only assumption that
* can be made by implementors is that if an Exception instance is given
* to produce a stack trace, it MUST be in a key named "exception".
*
* See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
* for the full interface specification.
*/
interface LoggerInterface
{
/**
* System is unusable.
*
* @param string $message
* @param array $context
* @return void
*/
public function emergency($message, array $context = array());
/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message
* @param array $context
* @return void
*/
public function alert($message, array $context = array());
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param array $context
* @return void
*/
public function critical($message, array $context = array());
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param string $message
* @param array $context
* @return void
*/
public function error($message, array $context = array());
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message
* @param array $context
* @return void
*/
public function warning($message, array $context = array());
/**
* Normal but significant events.
*
* @param string $message
* @param array $context
* @return void
*/
public function notice($message, array $context = array());
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param array $context
* @return void
*/
public function info($message, array $context = array());
/**
* Detailed debug information.
*
* @param string $message
* @param array $context
* @return void
*/
public function debug($message, array $context = array());
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
* @return void
*/
public function log($level, $message, array $context = array());
}
關(guān)于message參數(shù):
$message
必須是一個字符串或者是含有__toString()
方法的對象薪韩,$message
應該包含占位符,例如{placeholder_name}
捌锭,占位符由{
俘陷、占位符名稱和}
組成,不能包含空格观谦,占位符名稱可以由A-Z, a-z, 0-9, _
組成拉盾,第三方實現(xiàn)可以用$context
參數(shù)來替換占位符,占位符名稱必須和$context
數(shù)組的key對應豁状。如下例子是使用$context
中的值替換$message
中的占位符:
<?php
/**
* Interpolates context values into the message placeholders.
*/
function interpolate($message, array $context = array())
{
// build a replacement array with braces around the context keys
$replace = array();
foreach ($context as $key => $val) {
// check that the value can be casted to string
if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
$replace['{' . $key . '}'] = $val;
}
}
// interpolate replacement values into the message and return
return strtr($message, $replace);
}
// a message with brace-delimited placeholder names
$message = "User {username} created";
// a context array of placeholder names => replacement values
$context = array('username' => 'Bolivar');
// echoes "User Bolivar created"
echo interpolate($message, $context);
關(guān)于context參數(shù):
$context
是一個數(shù)組參數(shù)捉偏,用于構(gòu)造復雜的日志消息,$context
中的值不能跑出任何PHP異承汉欤或錯誤夭禽。如果$context
中包含Exception
對象,則該對象的key
必須為exception
谊路。
PSR-3日志記錄器的使用
推薦使用monolog/monolog讹躯,這樣可以讓我們不需要浪費更多的時間在編寫一個日志記錄器了。Monolog組建完全實現(xiàn)了PSR-3接口缠劝,而且便于使用自定義的消息格式化程序和處理程序擴展功能潮梯,通過Monolog可以把日志消息寫入文本文件、系統(tǒng)日志和數(shù)據(jù)庫中惨恭,還能通過電子郵件發(fā)送秉馏,并且還支持Slack和遠程服務器。如下展示了如何設(shè)置Monolog脱羡,并把日志消息寫入文本文件:
use Monolog/Logger;
use Monolog/Handler/StreamHandler;
// 創(chuàng)建日志記錄器
$log = new Logger('myApp');
$log->pushHandler(new StreamHandler('logs/development.log, Logger::DEBUG));
$log->pushHandler(new StreamHandler('logs/production.log', Logger::WARNING));
// 使用日志記錄器
$log->debug("This is a debug message");
$log->warning("This is a warning message");
PSR-4
PSR-4規(guī)范描述了一個標準的自動加載器策略萝究,指在運行時按需查找PHP類、接口或Traits
轻黑。支持PSR-4自動加載器標準的PHP組建和框架糊肤,使用同一個自動加載器就能找到相關(guān)代碼,然后將其載入PHP解釋器氓鄙。有了這個功能馆揉,就可以把現(xiàn)代PHP生態(tài)系統(tǒng)中很多客戶操作的組件聯(lián)系起來。
編寫一個PSR-4自動加載器
PSR-4規(guī)范不要求改變代碼的實現(xiàn)方式抖拦,只建議如何使用文件系統(tǒng)目錄結(jié)構(gòu)和PHP命名空間組織代碼升酣,PSR-4規(guī)范以來PHP命名空間和文件系統(tǒng)目錄結(jié)構(gòu)查找并加載PHP類舷暮、接口和Traits
,這正是PSR-4的精髓所在噩茄。下面我們來自己手動實現(xiàn)一個PSR-4自動加載器:
<?php
/**
* 使用SPL組冊這個自動加載函數(shù)后下面,遇到下述代碼時這個函數(shù)會嘗試 從/path/to/project/src/Baz/Qux.php文件中加載\Foo\Bar\Baz\Qux類:
* new \Foo\Bar\Baz\Qux;
* @param string $class 完全限定的類名。
* @return void
**/
spl_autoload_register(function ($class) {
// 項目的命名空間前綴
$prefix = 'Foo\\Bar\\';
// 目錄前綴對應的根目錄
$base_dir = __DIR__ . '/src/';
// 判斷傳入的類是否使用了這個命名空間前綴
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
// 沒有使用绩聘,交給注冊的下一個自動加載器處理
return;
}
// 獲取去掉前綴后的類名
$relative_class = substr($class, $len);
// 把命名空間前綴替換成根目錄沥割,
// 在去掉前綴的類名中,把命名空間分隔符替換成目錄分隔符凿菩,
// 然后在后面加上.php
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
// 如果該文件存在机杜,就將其導入
if (file_exists($file)) {
require $file;
}
});
這樣我們就寫好了一個PSR-4的自動加載器了,更多關(guān)于PHP的自動加載機器可以看我這篇文章衅谷。
參考:
- 《Morden PHP中文版》
- https://www.php-fig.org