PHP|開發必知的良好實踐

過濾、驗證、轉義

全部這些外部資源都不能徹底相信php

  • $_GEThtml

  • $_POSTmysql

  • $_REQUEST算法

  • $_COOKIEsql

  • $argv數據庫

  • php://stdinapi

  • php://input安全

  • file_get_contents()網絡

  • 遠程數據庫app

  • 遠程API

  • 客戶端的數據

過濾

使用htmlentities()過濾HTML,將特殊字符轉換爲HTML實體,轉義輸出,第二個參數使用ENT_QUOTES

使用PDO預處理語句過濾SQL注入.

使用filter_var() & filter_input()函數來過濾和驗證不一樣類型的輸入。

Eg: email, number, char, 特殊字符

密碼

最安全的哈希算法:bcrypt

使用:

password_hash()
password_get_info()
password_needs_rehash()
password_verify()

函數來生成密碼。

日期,時間,時區

使用PHP5.2.0引入的DateTime & DateInterval & DateTimeZone類來處理時間。

設置默認時區

在php.ini中,設置

date.timezone = 'country/city'

使用date_default_timezone_set()函數來設置默認時區。

DateTime Class

使用DateTime類來管理日期和時間

$datetime = new DateTime();

使用DateInterval類來構造長度固定的時間段。配合上一個類中的方法使用。

DateTimeZone Class

時區的處理選擇。

$timezone = new DateTimeZone('Asia/Shanghai');

$datetime = new DateTime('2016-05-20', $timezone);

$datetime->setTimezone(new DateTimeZone('Asia/Hong_Kong'));

有時,咱們須要迭代處理一段時間內反覆出現的一系列日期和時間,重複在日程表中記事就是個好例子。DatePeriod類能夠解決這種問題

DatePeriod實例就是迭代器,每次迭代會產出一個DateTime實例。

<?php
$start = new DateTime();
$interval = new DateInterval('P2W');
$period = new DatePeriod($start, $interval, 3);

foreach ($period as $nextDateTime) {
    echo $nextDateTime->format('Y-m-d H:i:s'), PHP_EOL;
}

DB

PDO擴展

http://php.net/manual/zh/pdo.drivers.php

<?php
try {
    $pdo = new PDO(
        'mysql:host=127.0.0.1;dbname=books;port=3306;charset=utf8',
        'USERNAME',
        'PASSWORD'
    );
} catch (PDOException $e) {
    echo "Fail";
    exit;
}

預處理語句

爲了防止SQL注入,使用PDO的預處理語句。

預處理語句時PDOStatement的實例。能夠經過PDO實例的prepare()方法獲取與處理語句對象。

$sql = "select id from users where email = :email";
$statement = $pdo->prepare($sql);

$email = filter_input(INPUT_GET, 'email');
$statement->bindValue(':email', $email);

http://php.net/manual/pdo.constants.php

繼續:

$statement->execute();

while (($result = $statement->fetch(PDO::FETCH_ASSOC)) !== false) {
    echo $result['email'];
}

$results = $statement->fetchAll(PDO::FETCH_ASSOC);
foreach ($results as $result) {
    echo $result['email'];
}

PDO的事務

try {
    $pdo = new PDO();
}catch(PDOExcetion $e){
    //
}

$stmtSubtract = $pdo->prepare('
    UPDATE accounts
    SET amount = amount - :amount
    WHERE name = :name
');
$stmtAdd = $pdo->prepare('
    UPDATE accounts
    SET amount = amount + :amount
    WHERE name = :name
');

$pdo->beginTransaction();

$fromAccount = 'Checking';
$withdrawal = 50;
$stmtSubtract->bindParam(':name', $fromAccount);
$stmtSubtract->bindParam(':amount', $withDrawal, PDO::PARAM_INT);
$stmtSubtract->execute();

$toAccount = 'Savings';
$deposit = 50;
$stmtSubtract->bindParam(':name', $$toAccount);
$stmtSubtract->bindParam(':amount', $deposit, PDO::PARAM_INT);
$stmtSubtract->execute();

$pdo->commit();

多字節字符串

PHP假設字符串中的每一個自負都是八位字符,佔一個字節的內存。然而考慮到國家化的時候,一個字符就不僅佔用一個字節了。

爲了不處理多字節字符串出錯,能夠安裝mbstring擴展。替換PHP原生的函數。

關於字符編碼

  • 必定要知道數據的字符編碼

  • 使用UTF8存儲數據

  • 使用UTF8輸出數據

php.ini中設置,告訴PHP使用UTF8:

default_charset = "UTF-8";

不少PHP函數都使用這個默認的字符集:

htmlentities()
html_entity_decode()
htmlspecialchars()
以及mbstring中的擴展函數

流在PHP4.3.0中引入,做用是使用統一的方式處理文件,網絡和數據壓縮等共用同一套函數和用法的操做。簡單而言,流是具備流式行爲的資源對象。所以,流能夠線性讀寫,或許還能使用fseek()函數定位到流中的任何位置。

流的做用其實是在出發地和目的地之間傳輸數據。出發地和目的地能夠是

文件,命令行進程,網絡鏈接,zip, TAR壓縮, 臨時內存,標準輸入輸出,或者是經過php流封裝協議實現的資源(http://php.net/manual/wrappers.php)

流封裝協議

http://php.net/manual/wrappers.php

流封裝協議的做用是使用通用的接口封裝讀寫文件系統的差別。

每一個流都有一個協議和目標

<scheme>://<target>

file:// 流封裝協議

  • file_get_contents

  • fopen

  • fwrite

  • fclose

<?php
// 隱式
$handle = fopen('/etc/hosts', 'rb');
while (feof($handle) !== true) {
    echo fgets($handle);
}
fclose($handle);

// 顯式
$handle = fopen('file:///etc/hosts', 'rb');
while (feof($handle) !== true) {
    //
}

fclose($handle);

php://流封裝協議

  • php://stdin

  • php://stdout

  • php://memory

  • php://temp

本身編寫流封裝協議

PHP提供了一個示例streamWrapper類,編寫自定義的流封裝協議。

流上下文

有些PHP流能接受一系列可選的參數,這些參數叫流上下文,用於定製流的行爲。

使用

stream_context_create()

函數建立。

<?php
$requestBody = '{"username": "josh"}';
$context = stream_context_create([
    'http' => [
        'method' => 'POST',
        'header' => "",
        'content' => $requestBody,
    ],
]);

$response = file_get_contents('http://x/xapi', false, $context);

流過濾器

把過濾器附加到現有的流上,使用

stream_filter_append()
<?php
$handle = fopen('file', 'rb');
stream_filter_append($handle, 'string.toupper');
while (feof($handle) !== true) {
    echo fgets($handle);
}

fclose($handle);

還可使用php://filter流協議把過濾器附加到流上。

$handle = fopen('php://filter/read=string.toupper/resource=data.txt', 'rb');
while () {

}
filter/read=<filter_name>/resource=<scheme>://<target>

咱們還可使用php_user_filter類來自定義流過濾器

http://php.net/manual/en/class.php-user-filter.php

class DirtyWordsFilter extends php_user_filter
{
    public function filter($in, $out, &$consumed, $closing)
    {
        $words = ['grime', 'dirt', 'grease'];
        $wordData = [];
        foreach ($words as $word) {
            $replacement = array_fill(0, mb_strlen($word), '*');
            $wordData[$word] = implode('', $replacement);
        }
        
        $bad = array_keys($wordData);
        $good = array_values($wordData);
        
        while ($bucket = stream_bucket_make_writeable($in)) {
            $bucket->data = str_replace($bad, $good, $bucket->data);
            
            $consumed += $bucket->datalen;
            
            stream_bucket_append($out, $bucket);
        }
        
        return PSFS_PASS_ON;
    }
}

而後,咱們使用stream_filter_register()函數註冊這個自定義的流過濾器

stream_filter_register('dirty_words_filter', 'DirtyWordsFilter');

異常處理示例

try {
    //
} catch (PDOException $e) {

} catch (Exception $e) {
    // 捕獲除了PDOException以外的全部異常
} finally {
    // 最終執行
}

異常處理程序

PHP容許咱們註冊一個全局異常處理程序,捕獲全部未被捕獲的異常。

set_exception_handler(function (Exception $e) {
    //
});

在某些狀況下,代碼執行完畢後,還須要還原成前一個異常處理程序。

restore_exception_handler()
<?php
// register
set_exception_handler(function (Exception $e) {
    // Process exception
});

// Coding

// restore
restore_exception_handler();

錯誤

PHP因爲某種緣由致使沒法運行,一般會觸發錯誤。

咱們可使用

trigger_error()

本身觸發錯誤。

咱們可使用

error_reporting()

或者php.ini中使用error_reporting指令,告訴PHP如何處理錯誤。

必定要遵照下述四個原則

  • 必定要讓PHP報告錯誤

  • 在開發環境中顯示錯誤

  • 在生產環境中不能顯示錯誤

  • 都要記錄錯誤

php.ini中:

//Dev
display_startup_errors = On
display_errors = On

error_reporting = -1
log_errors = On

//Prod
display_startup_errors = Off
display_errors = Off

error_reporting = E_ALL & ~E_NOTICE
log_errors = On

錯誤處理的函數

set_error_handler(function ($error, $errstr, $errfile, $errline) {
    // Process error
})

咱們也能夠把PHP的錯誤轉換爲異常

只能轉換知足php.ini中error_reporting指令設置的錯誤

set_error_handler(function ($errno, $errstr, $errfile, $errline) {
    if (!(error_reporting() & $errno)) {
        return
    }
    
    throw new \ErrorException($errstr, $errno, 0, $errfile, $errline);
});

參考

  1. Modern PHP

相關文章
相關標籤/搜索