鍍金池/ 教程/ PHP/ 開發(fā)實踐
依賴管理
安全
測試
使用模板
開發(fā)實踐
入門指南
服務(wù)器與部署
社區(qū)
語言亮點
錯誤與異常
虛擬化技術(shù)
資源
文檔撰寫
數(shù)據(jù)庫
依賴注入
緩存
代碼風(fēng)格指南

開發(fā)實踐

基礎(chǔ)知識

PHP 是一門龐大的語言,各個水平層次的開發(fā)者都可以利用它進行迅捷高效的開發(fā)。然而在對語言逐漸深入的學(xué)習(xí)過程中,我們往往會因為走捷徑和/或不良習(xí)慣而忘記(或忽視掉)基礎(chǔ)的知識。為了幫助徹底解決這個問題,這一章的目的就是提醒開發(fā)人員注意有關(guān) PHP 的基礎(chǔ)編程實踐。

日期和時間

PHP 中 DateTime 類的作用是在你讀、寫、比較或者計算日期和時間時提供幫助。除了 DateTime 類之外,PHP 還有很多與日期和時間相關(guān)的函數(shù),但 DateTime 類為大多數(shù)常規(guī)使用提供了優(yōu)秀的面向?qū)ο蠼涌?。它還可以處理時區(qū),不過這并不在這篇簡短的介紹之內(nèi)。

在使用 DateTime 之前,通過 createFromFormat() 工廠方法將原始的日期與時間字符串轉(zhuǎn)換為對象或使用 new DateTime 來取得當(dāng)前的日期和時間。使用 format() 將 DateTime 轉(zhuǎn)換回字符串用于輸出。

{% highlight php %}
<?php
$raw = '22. 11. 1968';
$start = DateTime::createFromFormat('d. m. Y', $raw);

echo 'Start date: ' . $start->format('Y-m-d') . "\n";
{% endhighlight %}

對 DateTime 進行計算時可以使用 DateInterval 類。DateTime 類具有例如 add()sub() 等將 DateInterval 當(dāng)作參數(shù)的方法。編寫代碼時注意不要認為每一天都是由相同的秒數(shù)構(gòu)成的,不論是夏令時(DST)還是時區(qū)轉(zhuǎn)換,使用時間戳計算都會遇到問題,應(yīng)當(dāng)選擇日期間隔。使用 diff() 方法來計算日期之間的間隔,它會返回新的 DateInterval,非常容易進行展示。

{% highlight php %}
<?php
// create a copy of $start and add one month and 6 days
$end = clone $start;
$end->add(new DateInterval('P1M6D'));

$diff = $end->diff($start);
echo 'Difference: ' . $diff->format('%m month, %d days (total: %a days)') . "\n";
// Difference: 1 month, 6 days (total: 37 days)
{% endhighlight %}

DateTime 對象之間可以直接進行比較:

{% highlight php %}
<?php
if ($start < $end) {
    echo "Start is before end!\n";
}
{% endhighlight %}

最后一個例子來演示 DatePeriod 類。它用來對循環(huán)的事件進行迭代。向它傳入開始時間、結(jié)束時間和間隔區(qū)間,會得到這其中所有的事件。

{% highlight php %}
<?php
// output all thursdays between $start and $end
$periodInterval = DateInterval::createFromDateString('first thursday');
$periodIterator = new DatePeriod($start, $periodInterval, $end, DatePeriod::EXCLUDE_START_DATE);
foreach ($periodIterator as $date) {
    // output each date in the period
    echo $date->format('Y-m-d') . ' ';
}
{% endhighlight %}

設(shè)計模式

當(dāng)你在編寫自己的應(yīng)用程序時,最好在項目的代碼和整體架構(gòu)中使用通用的設(shè)計模式,這將幫助你更輕松地對程序進行維護,也能夠讓其他的開發(fā)者更快地理解你的代碼。

當(dāng)你使用框架進行開發(fā)時,絕大部分的上層代碼以及項目結(jié)構(gòu)都會基于所使用的框架,因此很多關(guān)于設(shè)計模式的決定已經(jīng)由框架幫你做好了。當(dāng)然,你還是可以挑選你最喜歡的模式并在你的代碼中進行應(yīng)用。但如果你并沒有使用框架的話,你就需要自己去尋找適合你的應(yīng)用的最佳模式了。

使用 UTF-8 編碼

本章是由 Alex Cabal 最初撰寫在 PHP Best Practices 中的,我們使用它作為進行建議的基礎(chǔ)。

這不是在開玩笑。請小心、仔細并且前后一致地處理它。

目前,PHP 仍未在底層實現(xiàn)對 Unicode 的支持。雖然有很多途徑可以確保 UTF-8 字符串能夠被正確地處理,但這并不是很簡單的事情,通常需要對 Web 應(yīng)用進行全方面的檢查,從 HTML 到 SQL 再到 PHP。我們將爭取進行一個簡潔實用的總結(jié)。

PHP 層面的 UTF-8

最基本的字符串操作,像是連結(jié)兩個字符串或?qū)⒆址x值給變量,并不需要對 UTF-8 做特別的處理。然而大多數(shù)字符串的函數(shù),像 strpos()strlen(),確實需要特別的處理。這些函數(shù)名中通常包含 mb_*:比如,mb_strpos()mb_strlen()。這些 mb_* 字符串是由 Multibyte String Extension 提供支持的,它專門為操作 Unicode 字符串而特別進行了設(shè)計。

在操作 Unicode 字符串時,請你務(wù)必使用 mb_* 函數(shù)。例如,如果你對一個 UTF-8 字符串使用 substr(),那返回的結(jié)果中有很大可能會包含一些亂碼。正確的方式是使用 mb_substr()。

最難的地方在于每次都要記得使用 mb_* 函數(shù)。如果你哪怕只有一次忘記了使用,你的 Unicode 字符串就有在接下來的過程中變成亂碼的風(fēng)險。

不是所有的字符串函數(shù)都有一個對應(yīng)的 mb_* 函數(shù)。如果你想要的功能沒有對應(yīng)的 mb_* 函數(shù)的話,那只能說你運氣不佳了。

你應(yīng)該在你所有的 PHP 腳本(或全局包含的腳本)的開頭使用 mb_internal_encoding() 函數(shù),然后緊接著在會對瀏覽器進行輸出的腳本中使用 mb_http_output()。在每一個腳本當(dāng)中明確聲明字符串的編碼可以免去很多日后的煩惱。

另外,許多對字符串進行操作的函數(shù)都有一個可選的參數(shù)用來指定字符串編碼。當(dāng)可以設(shè)定這類參數(shù)時,你應(yīng)該始終明確指定使用 UTF-8。例如,htmlentities() 有一個字符編碼的選項,你應(yīng)該始終將其設(shè)為 UTF-8。從 PHP 5.4.0 開始, htmlentities()htmlspecialchars() 的編碼都已經(jīng)被默認設(shè)為了 UTF-8。

最后,如果你所編寫的是分布式的應(yīng)用程序并且不能確定 mbstring 擴展一定開啟的話,可以考慮使用 patchwork/utf8 Composer 包。它會在 mbstring 可用時自動使用,否則自動切換回非 UTF-8 函數(shù)。

數(shù)據(jù)庫層面的 UTF-8

如果你使用 PHP 來操作到 MySQL,有些時候即使你做到了上面的每一點,你的字符串仍可能面臨在數(shù)據(jù)庫中以非 UTF-8 的格式進行存儲的問題。

為了確保你的字符串從 PHP 到 MySQL都使用 UTF-8,請檢查確認你的數(shù)據(jù)庫和數(shù)據(jù)表都設(shè)定為 utf8mb4 字符集和整理,并且確保你的 PDO 連接請求也使用了 utf8mb4 字符集。請看下方的示例代碼,這是 非常重要 的。

請注意為了完整的 UTF-8 支持,你必須使用 utf8mb4 而不是 utf8!你會在進一步閱讀中找到原因。

瀏覽器層面的 UTF-8

使用 mb_http_output() 函數(shù)來確保 PHP 向瀏覽器輸出 UTF-8 格式的字符串。

隨后瀏覽器需要接收 HTTP 應(yīng)答來指定頁面是由 UTF-8 進行編碼的。以前這一步是通過在頁面 <head> 標(biāo)簽下包含字符集 <meta> 標(biāo)簽實現(xiàn)的,這是一種可行的方式。但更好的做法是在 Content-Type 響應(yīng)頭中進行設(shè)置,因為這樣做的速度會更快。

{% highlight php %}
<?php
// Tell PHP that we're using UTF-8 strings until the end of the script
mb_internal_encoding('UTF-8');

// Tell PHP that we'll be outputting UTF-8 to the browser
mb_http_output('UTF-8');

// Our UTF-8 test string
$string = 'êl síla erin l? e-govaned v?n.';

// Transform the string in some way with a multibyte function
// Note how we cut the string at a non-Ascii character for demonstration purposes
$string = mb_substr($string, 0, 15);

// Connect to a database to store the transformed string
// See the PDO example in this document for more information
// Note the `charset=utf8mb4` in the Data Source Name (DSN)
$link = new PDO(
    'mysql:host=your-hostname;dbname=your-db;charset=utf8mb4',
    'your-username',
    'your-password',
    array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_PERSISTENT => false
    )
);

// Store our transformed string as UTF-8 in our database
// Your DB and tables are in the utf8mb4 character set and collation, right?
$handle = $link->prepare('insert into ElvishSentences (Id, Body) values (?, ?)');
$handle->bindValue(1, 1, PDO::PARAM_INT);
$handle->bindValue(2, $string);
$handle->execute();

// Retrieve the string we just stored to prove it was stored correctly
$handle = $link->prepare('select * from ElvishSentences where Id = ?');
$handle->bindValue(1, 1, PDO::PARAM_INT);
$handle->execute();

// Store the result into an object that we'll output later in our HTML
$result = $handle->fetchAll(\PDO::FETCH_OBJ);

header('Content-Type: text/html; charset=UTF-8');
?><!doctype html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>UTF-8 test page</title>
    </head>
    <body>
        <?php
        foreach($result as $row){
            print($row->Body);  // This should correctly output our transformed UTF-8 string to the browser
        }
        ?>
    </body>
</html>
{% endhighlight %}

延伸閱讀

上一篇:入門指南下一篇:安全