PHP不權(quán)威總結(jié)

PHP不權(quán)威總結(jié)

歡迎閱讀

本文目標(biāo)用戶是我自己抑月,系統(tǒng)地持續(xù)集成PHP方方面面的知識(shí),但不會(huì)事無巨細(xì)的一一列舉舆蝴,只會(huì)挑選我認(rèn)為易忘谦絮、易錯(cuò)、重要的內(nèi)容進(jìn)行集成洁仗,而且大多是點(diǎn)到為止层皱,需要更詳細(xì)文檔的話會(huì)額外鏈接。所以赠潦,如果你已經(jīng)靠PHP謀生了幾年的話這篇文檔應(yīng)該會(huì)對你有幫助叫胖,如果你是PHP新手,建議重點(diǎn)閱讀學(xué)習(xí)資料一節(jié)祭椰。

安裝配置 & 環(huán)境搭建

PHP的開發(fā)環(huán)境主要是LA/NMP臭家,即Linux、Apache/Nginx方淤、Mysql钉赁、PHP,關(guān)于這些工具在不同平臺(tái)的安裝有大量優(yōu)秀詳細(xì)的文檔携茂,這里不廢話你踩,重點(diǎn)還是PHP的安裝,不過強(qiáng)烈推薦Laravel的Homestead解決方案讳苦,一套方便管理的带膜、跨平臺(tái)、統(tǒng)一的鸳谜、虛擬化開發(fā)環(huán)境膝藕。

編譯安裝

推薦使用Like Unix操作系統(tǒng)作為開發(fā)、線上環(huán)境咐扭,強(qiáng)大且簡單芭挽。我推薦的PHP安裝方式是編譯安裝滑废,對新手也是,不走彎路袜爪、不踩坑怎么成長蠕趁。源碼安裝你的選擇會(huì)更加自由,能第一時(shí)間嘗試各種Alpha版辛馆,而且在日常工作中俺陋,安裝擴(kuò)展、調(diào)試昙篙、優(yōu)化腊状,都需要對PHP的目錄、文件有一定了解瓢对。

  1. PHP官網(wǎng)下載你需要的版本源碼
  2. 編譯 & 安裝
# --prefix指定目錄寿酌,--with-config-file-path指定php-config目錄,其他為要一同安裝的擴(kuò)展或開啟的功能

./configure --prefix=/usr/local/php \
--with-config-file-path=/etc/php \
--enable-fpm \
--enable-pcntl \
--enable-mysqlnd \
--enable-opcache \
--enable-sockets \
--enable-sysvmsg \
--enable-sysvsem \
--enable-sysvshm \
--enable-shmop \
--enable-zip \
--enable-soap \
--enable-xml \
--enable-mbstring \
--disable-rpath \
--disable-debug \
--disable-fileinfo \
--with-mysql=mysqlnd \
--with-mysqli=mysqlnd \
--with-pdo-mysql=mysqlnd \
--with-pcre-regex \
--with-iconv \
--with-zlib \
--with-mcrypt \
--with-gd \
--with-openssl \
--with-mhash \
--with-xmlrpc \
--with-curl \
--with-imap-ssl

sudo make
sudo make install
  1. 添加配置硕蛹。PHP的源碼包中附帶了一個(gè)開發(fā)環(huán)境使用的配置(開啟了方便調(diào)試醇疼、減少性能消耗的配置),只需要放到默認(rèn)位置就行
sudo mkdir /etc/php
sudo cp php.ini-development /etc/php/php.ini
  1. 擴(kuò)展安裝法焰。如果需要在已安裝的PHP中添加擴(kuò)展的話秧荆,只需下載好擴(kuò)展源碼后進(jìn)入擴(kuò)展的源碼目錄
phpize
./configure --with-php-config=/usr/local/php-config
make
make install

Homestead

團(tuán)隊(duì)作戰(zhàn)中,由于依賴眾多(git埃仪、redis乙濒、memcache、nodejs卵蛉、npm)颁股,每個(gè)人習(xí)慣不同,開發(fā)環(huán)境都會(huì)有所差別傻丝,會(huì)導(dǎo)致許多難以察覺的問題甘有,并大大提高團(tuán)隊(duì)協(xié)調(diào)的難度,我曾遇到過PHP5.2葡缰、5.4兩個(gè)版本的正則執(zhí)行結(jié)果不同而導(dǎo)致上線折騰到凌晨2點(diǎn)的坑事亏掀。所以Laravel團(tuán)隊(duì)提供了一套基于虛擬機(jī)的跨平臺(tái)開發(fā)環(huán)境搭建方案 —— Homestead(譯為家園)。

我來簡單梳理一下泛释。Homestead的做法是滤愕,首先選擇免費(fèi)穩(wěn)定的Virtual Box作為虛擬機(jī),用Vagrant來管理虛擬機(jī)和開發(fā)環(huán)境怜校,然后又配置了一套統(tǒng)一的git间影、redis等常用工具,直接安裝即可茄茁。這里有詳細(xì)的說明文檔宇智。

Mac安裝

直接使用Homebrew安裝蔓搞,類似Linux的yum、apt-get方案随橘。

Windows安裝

推薦使用XAMPP、EasyPHP锦庸、WAMP等一類的套件机蔗,簡單易用,包括了LN/AMP最基本的開發(fā)環(huán)境甘萧,提供了GUI管理界面萝嘁。需要提醒的是千萬不要用這些套件部署線上環(huán)境

基本特性

日期時(shí)間

日期處理非常討厭的地方是每月天數(shù)不同和閏年扬卷、時(shí)區(qū)的差異牙言,以及日期之間的運(yùn)算。早期的phper主要使用date怪得、strtotime等幾個(gè)函數(shù)進(jìn)行轉(zhuǎn)換咱枉,費(fèi)時(shí)費(fèi)力易出錯(cuò)。5.2之后其實(shí)提供了以下幾個(gè)類徒恋,來簡化日期處理蚕断。

// 基本的日期處理對象
DateTime: __construct ([ string $time = "now" [, DateTimeZone $timezone = NULL ]] );

// 時(shí)區(qū)對象
DateTimeZone: __construct ($timezone);

// 時(shí)間段對象
DateInterval: __construct ($interval_spec);

// 時(shí)間迭代器
DatePeriod: __construct (DateTimeInterface $start, DateInterval $interval, DateTimeInterface $end, $options=0);

列舉幾個(gè)使用場景

$datetime = new DateTime();
echo $datetime->format('Y-m-d H:i:s'); // out當(dāng)前時(shí)間

$datetime->setTimezone(new DateTimeZone('Asia/ShangHai'));
echo $datetime->format('Y-m-d H:i:s'); // out上海時(shí)間

$yesterday = new DateTime('-1 day');
echo $datetime->diff($yesterday)->format('%a'); // out日期天數(shù)差,1

// 生成一個(gè)時(shí)間段
$interval = new DateInterval('P2DT2H'); // P開頭, 日期和時(shí)間T隔開 Y M D W H M S
$datetime->add($interval);
$datetime->sub($interval);

// 兩個(gè)時(shí)間點(diǎn)之間進(jìn)行迭代
$start = DateTime('2019-11-01');
$end = DateTime('2019-11-21');
$interval = new DateInterval('P2D');
$period = new DatePeriod($start, $interval, $end, DatePeriod::EXCLUDE_START_DATE);
foreach ($period as $datetime) {
    echo $datetime->format("Y-m-d H:i:s") , PHP_EOL; // out 2019-11-03 2019-11-05...
}

延伸

數(shù)據(jù)庫

常用mysqli_* mysql_*函數(shù)簇由于比較底層且繁瑣入挣,加之經(jīng)常使用框架封裝好的方法亿乳,經(jīng)常忘記原生的數(shù)據(jù)庫操作,出于便捷性径筏、通用性與安全性的考慮葛假,數(shù)據(jù)庫連接推薦使用PDO

// 建立數(shù)據(jù)庫連接,pdo支持mysql oracle sqlite等多種常用數(shù)據(jù)庫
$pdo = new PDO('mysql:host=...;dbname=...;prot=...;charset=...');
$stmt = $pdo->query('select * from user');
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); // fetchAll一次讀取尚未讀取的所有記錄

// 逐條讀取滋恬,減少內(nèi)存消耗聊训;注意查詢結(jié)果以及緩存在本地,只是逐條加載近內(nèi)存
$stmt = $pdo->query('select * from user');
while (false !== ($row = $stmt->fetch()) {
    print_r($row);
}

// 一般為了防止sql注入夷恍,和提高性能魔眨,常使用prepare
$sql = "insert into foo (name, age) values (:name, :age)";
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':name', 'name');
$stmt->bindValue(':age', 18);
$stmt->execute();

define VS const

關(guān)于常量的處理有define和const兩種實(shí)現(xiàn)方法,其中const是在編譯階段執(zhí)行性能酿雪、可讀性都要略好一些遏暴,但由于其不能使用函數(shù)、不能if條件控制指黎,靈活性要比define差朋凉。

// define
define('NAME_1', 1);
define('NAME_2', [1, 2, 3]);
define('NAME_3', time());
if ($flag) {
    define('NAME_4', true);
}
class Foo {
    define('class_const', 1); // compile error
}

// const
const NAME_5 = 1;
const NAME_6 = time(); // compile error
if ($flag) {
    const NAME_7 = 1; // compile error
}
function foo () {
    const A = 1; // compile error
}

OOP特性

錯(cuò)誤與異常

php程序運(yùn)行時(shí)可能會(huì)出現(xiàn)各種錯(cuò)誤,錯(cuò)誤分為notice醋安、warning杂彭、error墓毒、compile_error等,正確處理這些錯(cuò)誤信息能夠幫助我們快速解決問題亲怠。

// 錯(cuò)誤報(bào)告控制所计,也可以在php.ini中修改error_reporting
error_reporting(E_ALL & ~E_NOTICE); // 關(guān)閉notice錯(cuò)誤
error_reporting(E_WARNING | E_ERROR); // 只開啟warning和error錯(cuò)誤

// 錯(cuò)誤顯示
ini_set('display_errors', 1);
# php中錯(cuò)誤相關(guān)配置
# 錯(cuò)誤日志是否打開
log_errors = On (Off)

# 錯(cuò)誤日志記錄的位置
error_log = php_errors.log

# 是否打開錯(cuò)誤顯示
display_errors = Off

# 定義錯(cuò)誤顯示的級(jí)別
error_reporting = E_ALL

關(guān)于錯(cuò)誤的控制,建議為:

  • 任何環(huán)境都要記錄錯(cuò)誤
  • 開發(fā)環(huán)境盡可能多顯示錯(cuò)誤
  • 生產(chǎn)環(huán)境要關(guān)閉錯(cuò)誤顯示

異常OOP的錯(cuò)誤處理機(jī)制团秽,強(qiáng)大且優(yōu)雅主胧。但php在這方面比較混亂,程序出錯(cuò)時(shí)既可能拋異常习勤,也可能只是個(gè)warning或直接中斷運(yùn)行踪栋,甚至繼續(xù)執(zhí)行下去。php7中嘗試對此進(jìn)行一定的統(tǒng)一图毕,部分運(yùn)行時(shí)fatal error不再直接中斷程序夷都,而是改為拋出Error異常,并與Exception統(tǒng)一實(shí)現(xiàn)了Throwable接口

try {
    require_once 'b.php'; // 依然fatal error
    $b = 1 % 0; // Error, DivisionByZeroError
    foo(); // Error
    echo $a; // warning而已

    $b = null;
    $b->test(); // Error
 
} catch (Exception $e) {

} catch (Error $e) {

} catch (Throwable $e) {

}

如果想嚴(yán)格執(zhí)行異常機(jī)制予颤,可以注冊一個(gè)error回調(diào)函數(shù)將所有錯(cuò)誤轉(zhuǎn)換為異常拋出囤官。注意,E_ERROR E_PARSE E_CORE_ERROR E_CORE_WARNING E_COMPILE_ERROR E_COMPILE_WARNING會(huì)直接導(dǎo)致程序退出而無法轉(zhuǎn)換為異常

set_error_handler(function($errno, $errstr, $errfile, $errline) {
    // 屏蔽不報(bào)告的錯(cuò)誤
    if (!(error_reporting() & $errno)) {
        return;
    } else {
        throw new \ErrorException($errstr, $errno);
    }
});

延伸

延遲靜態(tài)綁定

延遲靜態(tài)綁定是指類靜態(tài)方法調(diào)用時(shí)執(zhí)行調(diào)用者的邏輯荣瑟,而非方法定義時(shí)的邏輯治拿,看個(gè)例子

class People {
    public static function out() {
        echo 'people' . PHP_EOL;
    }    
    public function callOut() {
        self::out();
        static::out();
    } 
}   
class User extends People {
    public static function out() {
        echo 'user' . PHP_EOL;
    } 
}   

$user = new User();
$user->callOut();

// static指向運(yùn)行時(shí)的調(diào)用者也就是User類,而self為定義者也就是People類笆焰,所以輸出為
// people
// user

反射

反射是一種面向?qū)ο蟮臋C(jī)制劫谅,提供了一套API允許程序在運(yùn)行時(shí)對類進(jìn)行檢查,類定義嚷掠、類方法捏检、成員變量、可訪問性不皆、源碼贯城,甚至訪問私有成員與方法。反射機(jī)制經(jīng)常使用在測試霹娄、構(gòu)建與框架底層代碼中能犯,能夠產(chǎn)生很多“神奇”的效果。

延伸

trait

trait踩晶,翻譯為性狀,主要用于在類之間抽象一些特性。比如超人和小鳥,他們都需要實(shí)現(xiàn)一個(gè)fly方法夭拌,一般的做法是抽象一個(gè)更高級(jí)的類實(shí)現(xiàn)fly方法他們共同繼承突倍,但是這樣強(qiáng)行耦合了兩個(gè)并不強(qiáng)相關(guān)的類;另一種做法是定義一個(gè)Fly的接口實(shí)現(xiàn)fly方法唆垃,這樣雖然避免耦合肛真,但同樣的fly方法需要實(shí)現(xiàn)兩遍袁铐,違背了DYR原則淘衙;比較好的辦法是has-a,即定義一個(gè)包含fly方法的公共類幔翰,并在超人類和小鳥類中分別實(shí)例化西壮。其實(shí)trai就類似第三種做法,而且使用起來更方便一些

trait Fly {
    public function fly() {
        echo 'I can fly';
    }
}
class Superman {
    use Fly;
}
class Bird {
    use Fly;
}
$clark = new Superman();
$clark->fly();

就是這么簡單霍狰,但trait使用時(shí)有幾個(gè)需要注意的地方

  • php的方法調(diào)用優(yōu)先級(jí)為class自身方法 > trait > 繼承
  • 多個(gè)trait用逗號(hào)分隔
  • trait也可以使用trait
  • trait也可以定義屬性蔗坯,但不能與class中屬性沖突康震,fatal error

高級(jí)特性

命名空間

命名空間主要用來解決命名沖突。php的命名空間其實(shí)很簡單宾濒,只是由于其只是個(gè)虛擬空間腿短,并不要求與代碼實(shí)際文件相對應(yīng),雖然會(huì)靈活許多绘梦,但導(dǎo)致使用起來不好理解橘忱,這也是我詬病php哲學(xué)的一點(diǎn),過分追求靈活簡單卸奉,反而導(dǎo)致方案太多而困惑钝诚。

先看幾個(gè)命名空間的常見場景

namespace App\A\Route;
class Http {
}

namespace App\B\Route;
class Http {
}

// 直接new,默認(rèn)當(dāng)前命名空間
$routeB = new Http();

// 使用全限定命名空間榄棵,\開頭
$routeA = new \App\A\Route();

// use導(dǎo)入凝颇,同時(shí)重命名
use App\A\Route\Http as HttpA;
$routeA = new HttpA();

// use導(dǎo)入半限定
use App\A;
$routeA = new A\Route\Http();

php的命名空間只是個(gè)虛擬概念,use并不能自動(dòng)加載文件疹鳄,還需要依靠其他辦法加載文件拧略,php標(biāo)準(zhǔn)psr要求每個(gè)php文件只能定義一個(gè)namespace,且與其文件路徑保持一致尚辑,并利用spl_autoload自動(dòng)加載辑鲤,這樣就大大優(yōu)化了命名空間

  • namespace必須在文件開頭
  • function const同樣可以使用命名空間,導(dǎo)入時(shí)關(guān)鍵字為use function, use const

延伸

yield

yield杠茬,生成器月褥,主要用于“逐步”的處理大文件弛随、復(fù)雜計(jì)算,減少內(nèi)存消耗宁赤,和一定程度的降低代碼復(fù)雜度舀透,類似游標(biāo)。生成器是基于協(xié)程實(shí)現(xiàn)的决左,其基本特點(diǎn)是函數(shù)不再是調(diào)用-返回的模式愕够,而是調(diào)用-運(yùn)行-暫停……-返回佛猛,即函數(shù)可以讓出惑芭,和重入。

生成器的基本使用继找,定義遂跟、生成、執(zhí)行

function foo () {
    yield 'first record';
    yield 'second record';
}
$gen = foo();
$gen->current(); // out: first record
$gen->current(); // out: second record

// or
foreach($gen as $v) {
    echo $v;
}

// or
while ($gen->valid()) {
    $gen->current();
}

生成器婴渡,就是一個(gè)包含yield關(guān)鍵字的函數(shù)幻锁,比較特殊,調(diào)用這個(gè)函數(shù)時(shí)边臼,會(huì)返回一個(gè)迭代器(即實(shí)現(xiàn)了Iterator哄尔,next valid current),這也是生成器名字的由來
生成器的返回值柠并,和交互

function foo () {
    yield; // null
    yield 'string'; // string
    yield 'key' => 10; // key-val foreach($gen as $k => $v)

    return 'retval'; // php7以后支持岭接,$foo->getReturn();
}

function foo () {
    while ('end' != ($msg = yield)) {
        echo $msg;
        sleep(1);
    }
}
$gen->send('go');
$gen->send('go ahead');
$gen->send('end');

需要注意的是直接send可能導(dǎo)致數(shù)據(jù)丟失

function foo() {
    yield 1;
    yield 2;
}
$gen = foo();
$gen->send('msg');
echo $gen->current(); // 2

// 正確做法
$gen->current(); // 1
$gen->send('msg');
$gen->current(); // 2

延伸

session

session意為會(huì)話,作用是解決無狀態(tài)的Http協(xié)議實(shí)現(xiàn)用戶登錄的問題堂鲤。用戶第一次訪問應(yīng)用時(shí)生成一個(gè)seesion_id并將會(huì)話信息持久化亿傅,用戶后續(xù)訪問時(shí)均帶上該session_id,應(yīng)用程序籍此區(qū)分用戶并共享會(huì)話信息瘟栖,用戶在退出時(shí)注銷該session_id葵擎。目前直接使用原生的php session機(jī)制并不多,大多數(shù)是使用一個(gè)單點(diǎn)的統(tǒng)一用戶會(huì)話管理服務(wù)半哟,不過php本身的session機(jī)制在一些場景依然簡單有效酬滤。

session_start(); // 開啟session,無session_id時(shí)創(chuàng)建寓涨,并生成文件
$_SESSION; // 超全局?jǐn)?shù)組盯串,保存著session信息
session_unset(); // 清空session信息,并非unset($_SESSION)
session_commit(); // 保存session信息戒良,并結(jié)束session
session_destroy(); // 清空session体捏,刪除session文件

php的session在使用時(shí)有這么幾個(gè)缺陷和注意點(diǎn):

  • session_id默認(rèn)是使用cookie傳遞,一旦cookie被禁用,需要其他辦法兼容几缭,比如session_id應(yīng)用自己維護(hù)傳輸河泳,或者自動(dòng)加在url中
  • session信息默認(rèn)是保存在單機(jī)文件中的,這就導(dǎo)致分布式集群無法正常使用年栓,需要覆蓋實(shí)現(xiàn)php的session回調(diào)拆挥,借助redis、mysql等集中式存儲(chǔ)實(shí)現(xiàn)分布式的session
  • php的session過期有些費(fèi)解
    • 由于判斷session過期是要頻繁的文件檢查某抓,性能原因考慮纸兔,配置了一個(gè)檢查概率,默認(rèn)1/100
    • 坑爹的以文件最后修改時(shí)間為過期依舊否副,而每次更新會(huì)話變量汉矿,最后修改時(shí)間都會(huì)刷新
    • 由于默認(rèn)目錄是/tmp,假如服務(wù)器部署多個(gè)應(yīng)用而沒有調(diào)整時(shí)备禀,超時(shí)時(shí)間短的應(yīng)用會(huì)導(dǎo)致長的被清理

延伸

取值范圍

php中整型不區(qū)分int负甸、short、long痹届,統(tǒng)一全是long,且始終為有符號(hào)打月,位數(shù)與平臺(tái)有關(guān)队腐,32位系統(tǒng)取值范圍為-2147483648到2147483647(正負(fù)21億,共10位奏篙,-232~232-1)柴淘,64位系統(tǒng)上為-9223372036854775808到9223372036854775807(正負(fù)19位)

浮點(diǎn)型不區(qū)分float和double,統(tǒng)一全是double秘通,且始終有符號(hào)为严,位數(shù)與平臺(tái)有關(guān),具體表示數(shù)值的范圍需要根據(jù)精度而定肺稀,php默認(rèn)配置的精度是14位有效數(shù)字第股。32位系統(tǒng)中1位符號(hào)8位精度23位尾數(shù),64位系統(tǒng)中1位符號(hào)11位精度52位尾數(shù)

調(diào)試

xdebug

xhprof

xdebug雖然強(qiáng)大话原,但由于采集的信息比較多夕吻,對性能影響較大無法在實(shí)際生產(chǎn)環(huán)境使用,導(dǎo)致一些問題難以發(fā)現(xiàn)繁仁。xhprof是Facebook發(fā)布的一款輕量級(jí)性能分析器涉馅,性能影響小,能夠用于生產(chǎn)環(huán)境黄虱,同時(shí)收集的信息也能滿足大部分分析需求稚矿,主要是function級(jí)別的,包括運(yùn)行時(shí)間、運(yùn)行次數(shù)晤揣、cpu占用桥爽、內(nèi)存占用等。需要注意由于Facebook不再維護(hù)碉渡,官方版并不支持php7聚谁,github另外一個(gè)分支維護(hù)了php7版本

xhprof需要額外安裝擴(kuò)展,需要在應(yīng)用程序中顯示開啟滞诺、終止分析形导,會(huì)生成性能分析文件,借助可視化工具习霹,可清晰的看到性能瓶頸

延伸
github項(xiàng)目地址

單元測試

單測自己的使用經(jīng)驗(yàn)有限朵耕,但也嘗到了甜頭。PHP的單元測試主要借助PHPunit淋叶,可以通過composer安裝阎曹。簡單記錄幾個(gè)使用經(jīng)驗(yàn)

  • 最好在調(diào)試階段就開始使用phpunit,既可以第一時(shí)間實(shí)際使用類/方法煞檩,發(fā)現(xiàn)錯(cuò)誤與難用之處处嫌;也可以持續(xù)積累case,方便后續(xù)回歸
  • TDD斟湃,所謂測試驅(qū)動(dòng)熏迹,是先通過測試用例確認(rèn)軟件行為,然后再實(shí)現(xiàn)軟件來通過測試用例凝赛,達(dá)到明確的需求確認(rèn)和保證軟件質(zhì)量注暗。但由于測試用例比較耗費(fèi)精力,所以我一般用在小模塊開發(fā)中
  • phpunit的--filter選項(xiàng)比較有用墓猎,可以方便的測試指定的用例
  • 將case放在一個(gè)回滾的事務(wù)中捆昏,可以在自動(dòng)測試完后恢復(fù)現(xiàn)場
  • phpunit中的data準(zhǔn)備回調(diào)非常好用,可以自動(dòng)準(zhǔn)備毙沾、恢復(fù)測試數(shù)據(jù)骗卜,保證每次測試的現(xiàn)場一致可復(fù)現(xiàn)

延伸
PHPUnit手冊
如何有效的書寫項(xiàng)目單元測試

gdb

延伸

安全

加密

加密的一個(gè)原則是絕對不要明文存儲(chǔ)、傳輸密碼左胞,一般使用一種單向算法對密碼加密膨俐,以前常用md5+salt的方式,但由于彩虹表的出現(xiàn)罩句,md5不再安全焚刺,目前PHP中比較安全的是使用bcrypt算法加密。這個(gè)算法故意被設(shè)計(jì)的比較耗時(shí)(單次大約毫秒級(jí))门烂,從而增加破解成本乳愉。

// 計(jì)算hash兄淫,可以指定算法、計(jì)算因子蔓姚,會(huì)自動(dòng)補(bǔ)充salt
$pwd = 'password';
$hash = password_hash($pwd, PASSWORD_DEFAULT, ['cost' => 10]);

// 調(diào)試捕虽,可以看到hash值中同時(shí)保存了salt,和配置信息(算法坡脐、因子等)
$pwdInfo = password_get_info($hash);

// 驗(yàn)證泄私,就這么簡單
$bolEqual = password_verify($pwd, $hash);

// 維護(hù),可以自動(dòng)判斷hash是否滿足當(dāng)前配置备闲,籍此可判斷是否需要重新生成hash
$bolNeedRehash = password_needs_rehash($hash, PASSWORD_DEFAULT, ['cost' => 15]);

延伸
password_hash晌端、password_verify、password_needs_rehash手冊

組件 & 擴(kuò)展 & 工具

自動(dòng)加載

PHP中所謂的自動(dòng)加載是class的命名空間與類名去解析class文件真實(shí)path并require恬砂,映射規(guī)則一般使用官方推薦的PSR-4咧纠,大概為一個(gè)文件只包含一個(gè)class、trait泻骤、interface漆羔,同時(shí)namespace要與文件路徑一致。這樣利用PHP內(nèi)置的類加載器注冊機(jī)制便可以自動(dòng)require了狱掂。一個(gè)典型的autoloader.php如下:

<?php
    /**
     * 使用SPL組冊這個(gè)自動(dòng)加載函數(shù)后演痒,遇到下述代碼時(shí)這個(gè)函數(shù)會(huì)嘗試   從/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) {
        // 項(xiàng)目的命名空間前綴
        $prefix = 'Foo\\Bar\\';
    
        // 目錄前綴對應(yīng)的根目錄
        $base_dir = __DIR__ . '/src/';
    
        // 判斷傳入的類是否使用了這個(gè)命名空間前綴
        $len = strlen($prefix);
        if (strncmp($prefix, $class, $len) !== 0) {
            // 沒有使用趋惨,交給注冊的下一個(gè)自動(dòng)加載器處理
            return;
        }
    
        // 獲取去掉前綴后的類名
        $relative_class = substr($class, $len);
    
        // 把命名空間前綴替換成根目錄嫡霞,
        // 在去掉前綴的類名中,把命名空間分隔符替換成目錄分隔符希柿,
        // 然后在后面加上.php
        $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
    
        // 如果該文件存在,就將其導(dǎo)入
        if (file_exists($file)) {
            require $file;
        }
    });

composer

類似apt-get yum npm等养筒,composer是PHP的類管理器曾撤,可以自動(dòng)下載更新class、庫晕粪、項(xiàng)目挤悉,管理項(xiàng)目依賴,結(jié)合目前最大的PHP包分享社區(qū)packagist可以非常方便的完成項(xiàng)目環(huán)境搭建

延伸
PHP最流行的包分享packagist
Composer使用手冊

底層原理

生命周期

SPAI
Server Application Programming Interface巫湘,服務(wù)應(yīng)用編程接口装悲,php一切的開始,簡單說作用是負(fù)責(zé)隔離php程序的執(zhí)行與運(yùn)行環(huán)境尚氛,使得無論是cli诀诊、fpm、webserver module阅嘶,php代碼都可以正常運(yùn)行

php生命周期

php的生命周期如圖所示属瓣,fpm中主要是啟動(dòng)請求周期中循環(huán)载迄,cli中則僅僅是一次完整的流程。簡要說明一下各個(gè)環(huán)節(jié)的主要作用:

  • MINIT抡蛙,模塊初始化階段护昧,只執(zhí)行一次
    • 激活SAPI
    • 初始化垃圾回收器
    • 啟動(dòng)zend引擎
    • 注冊PHP內(nèi)置常量
    • 解析php.ini
    • 注冊$_GET、$_POST等超全局變量及處理回調(diào)
    • 動(dòng)態(tài)加載 .so
    • 回調(diào)擴(kuò)展minit方法
  • RINIT粗截,請求初始化階段惋耙,每次請求到達(dá)時(shí)都會(huì)執(zhí)行
    • 重置zend引擎,重置編譯器熊昌、符號(hào)表等
    • 重置垃圾回收器
    • 回調(diào)擴(kuò)展rinit
  • EXECUTE绽榛,腳本執(zhí)行階段
    • 加載php腳本
    • 預(yù)編譯、詞法解析浴捆、語法解析蒜田,生成抽象語法樹(AST)
    • 編譯生成中間碼,op_code
    • ZendVM執(zhí)行op_code
  • RSHUTDOWN选泻,請求關(guān)閉階段
    • flush輸出內(nèi)容
    • 發(fā)送http response header
    • 清理全局變量冲粤、關(guān)閉編譯器
    • 關(guān)閉內(nèi)存管理器
    • 回調(diào)擴(kuò)展rshutdown
  • MSHUTDOWN,模塊關(guān)閉階段
    • 關(guān)閉zend引擎
    • 回調(diào)擴(kuò)展mshutdown
    • 清理資源

延伸

zval zend_value zend_reference 和 CoW

PHP7中一個(gè)重要改動(dòng)是變量數(shù)據(jù)結(jié)構(gòu)的優(yōu)化页眯,將原來的變量的數(shù)據(jù)結(jié)構(gòu)從一個(gè)zval拆為zval梯捕、zend_value,并將原zval中引用計(jì)數(shù)移入zend_value中窝撵,可以簡單理解為zval是變量名傀顾,zend_value是實(shí)際的變量值,減少了內(nèi)存占用碌奉,結(jié)構(gòu)更清晰短曾。另一個(gè)變化是 PHP7中整形,浮點(diǎn)型赐劣,布爾型嫉拐,NULL是直接保存在zval中,無zend_value魁兼,所以無引用計(jì)數(shù)婉徘;另外字符串雖然有zend_value,但由于是程序結(jié)束后統(tǒng)一回收咐汞,所以也沒引用計(jì)數(shù)

zend_reference則是原來的是否為引用變量標(biāo)記位盖呼,在PHP7中擴(kuò)展為一種數(shù)據(jù)類型。生成引用類型的唯一方法就是使用&$val化撕,直接復(fù)制一個(gè)引用類型的變量是無法復(fù)制引用的几晤,而對象和資源復(fù)制則默認(rèn)為引用復(fù)制

struct _zval_struct {
    zend_value  value; // 變量實(shí)際值
    ……
};

struct _zend_value {
    zend_refcounted *counted; // gc頭部
    zend_reference  *ref;   // 引用類型
    ……
}

實(shí)際測試一下

<?php
    /* 引用計(jì)數(shù) */
    $a = 1; // a[zval]
    xdebug_debug_zval('a'); // a: (refcount=0, is_ref=0)=1

    $b = 'string'; // b[zval] -> 'string'[zend_value]
    xdebug_debug_zval('b'); // b: (refcount=1, is_ref=0)='string'

    $c = []; // c[zval] -> Array[zend_value]
    xdebug_debug_zval('c'); // c: (refcount=2, is_ref=0)=array ()
    
    $b_1 = $b; // b_1[zval] b[zval] -> 'string'[zend_value]
    xdebug_debug_zval('b'); // b: (refcount=1, is_ref=0)='string'
    xdebug_debug_zval('b_1'); // b_1: (refcount=1, is_ref=0)='string'

    $c_1 = $c; // c_1[zval] c[zval] -> Array[zend_value]
    xdebug_debug_zval('c'); // c: (refcount=3, is_ref=0)=array ()
    xdebug_debug_zval('c_1'); // c_1: (refcount=3, is_ref=0)='array ()

    /* 引用類型 */
    $r_c = &$c; // r_c[zval] c[zval] -> [zend_reference] -> Array[zend_value]
    xdebug_debug_zval('c'); // c: (refcount=2, is_ref=1)=array ()
    xdebug_debug_zval('r_c'); // r_c: (refcount=2, is_ref=1)=array ()

    $d = $r_c; // c[zval] -> Array[zend_value]
    xdebug_debug_zval('d'); // d: (refcount=4, is_ref=0)=array (),d并不是引用變量植阴,但他們?nèi)匀恢赶蛲粋€(gè)zend_value

    /* CoW */
    $d = [];
    xdebug_debug_zval('d'); // d: (refcount=2, is_ref=0)=array ()锌仅,寫時(shí)復(fù)制章钾,d指向了新的zend_value

    $a = [];
    xdebug_debug_zval('a'); // a: (refcount=2, is_ref=0)=array ()

    $b = $a; // 此時(shí)公用zend_value
    xdebug_debug_zval('a'); // a: (refcount=3, is_ref=0)=array ()
    xdebug_debug_zval('b'); // b: (refcount=3, is_ref=0)=array ()

    $c = &$a; // a、c為引用變量热芹,b單獨(dú)贱傀,但他們依舊指向同一zend_value
    xdebug_debug_zval('a'); // a: (refcount=2, is_ref=1)=array ()
    xdebug_debug_zval('b'); // b: (refcount=3, is_ref=0)=array ()
    xdebug_debug_zval('c'); // c: (refcount=2, is_ref=1)=array ()

    $c = 'new'; // 發(fā)生變量分離
    xdebug_debug_zval('a'); // a: (refcount=2, is_ref=1)='new'
    xdebug_debug_zval('b'); // b: (refcount=2, is_ref=0)=array ()
    xdebug_debug_zval('c'); // c: (refcount=2, is_ref=1)='new'

在前面的代碼中一并演示了Cow,寫時(shí)復(fù)制伊脓,變量復(fù)制時(shí)府寒,PHP解釋器并不會(huì)真的復(fù)制內(nèi)存,而是當(dāng)變量的值實(shí)際發(fā)生變化時(shí)才真正復(fù)制內(nèi)存报腔,從而減少內(nèi)存消耗株搔,提高性能

但引用類型的加入導(dǎo)致CoW有一點(diǎn)疑惑,在PHP5中由于只有zval纯蛾,所以即便變量沒有修改值纤房,但由于&操作變?yōu)橐妙愋停瑒t會(huì)導(dǎo)致一次隱性的變量分離翻诉,潛在內(nèi)存溢出風(fēng)險(xiǎn)炮姨,不易排查。不過PHP7中由于zval分離碰煌,zend_value沒有變化時(shí)舒岸,便不會(huì)發(fā)生CoW,算是修復(fù)了這個(gè)缺陷

最后看一個(gè)引用類型導(dǎo)致的詭異現(xiàn)象

<?php
    $foo['love'] = 1;
    $bar = &$foo['love'];
    $tipi = $foo;
    $tipi['love'] = '2';
    echo $foo['love']; // 1 or 2?

延伸

Hash Table

HashTable芦圾,散列表蛾派,PHP的殺手锏array便使用的該數(shù)據(jù)結(jié)構(gòu),既可以O(shè)(1)讀个少,也可以按序遍歷數(shù)據(jù)洪乍,其大致結(jié)構(gòu)如下:

hashtable.jpg

插入一個(gè)新key,先經(jīng)過散列函數(shù)映射到中間映射表里夜焦,其中記錄著元素?cái)?shù)組的位置壳澳,中間映射表與元素?cái)?shù)組為內(nèi)存中連續(xù)空間。當(dāng)直接訪問key值時(shí)與插入步驟一樣糊探,當(dāng)遍歷數(shù)組時(shí)則直接依次讀元素?cái)?shù)組

gc回收

PHP提供了自動(dòng)內(nèi)存管理功能,zend_value中記錄了變量值的引用次數(shù)河闰,當(dāng)我們使用unset時(shí)便會(huì)減少引用次數(shù)科平,當(dāng)引用次數(shù)減少為0時(shí)便會(huì)觸發(fā)內(nèi)存回收

循環(huán)引用 無法使用該方法回收,比如某個(gè)數(shù)組中元素又引用了數(shù)組本身姜性。針對這種情況瞪慧,PHP會(huì)在引用計(jì)數(shù)減少時(shí)收集可能的垃圾變量,然后定期對收集的變量進(jìn)行深度遍歷部念,在遍歷中引用次數(shù)依次減一弃酌,若存在循環(huán)引用則最終引用計(jì)數(shù)將變?yōu)?氨菇,變量回收

延伸
PHP垃圾回收機(jī)制

JIT

JIT,just in time妓湘,即時(shí)編譯查蓉,是解釋型語言的一種性能優(yōu)化手段。雖然編譯型語言與解釋型語言都有編譯階段榜贴,但不同的是編譯型語言是直接將源碼編譯為機(jī)器碼豌研,而解釋型語言則是編譯為字節(jié)碼供虛擬機(jī)執(zhí)行,導(dǎo)致運(yùn)行性能折損唬党。而JIT技術(shù)鹃共,則是實(shí)時(shí)在程序運(yùn)行期間,將熱點(diǎn)代碼直接編譯為機(jī)器碼驶拱,從而提高性能霜浴。PHP已經(jīng)確定將在PHP8中引入JIT技術(shù),將PHP的運(yùn)行性能提高到一個(gè)新的臺(tái)階

那為什么不直接編譯為機(jī)器碼運(yùn)行呢蓝纲? 直接編譯運(yùn)行稱為事前編譯AOT(ahead of time)阴孟,php也是支持這種做法的,但這樣做有幾個(gè)缺點(diǎn)驻龟,首先背離php堅(jiān)持的一貫簡單原則温眉,項(xiàng)目的發(fā)布上線需要提前編譯,對研發(fā)者也不夠友好翁狐,而且JIT由于可以收集大量運(yùn)行時(shí)信息类溢,編譯的優(yōu)化效果更好,綜合來說露懒,JIT更適合一些

JIT也并非沒有缺點(diǎn) 首先是JIT是需要額外消耗性能的闯冷,如果程序中無明顯熱點(diǎn)代碼,或者運(yùn)行周期比較短懈词,則JIT反而會(huì)導(dǎo)致性能下降

php引入JIT后的性能變化


jit.png

社區(qū)

FIG & PSR

PHP令人詬病的問題之一是框架太多蛇耀,ThinkPHP、CI坎弯、Yii纺涤、Zend、Laravel……抠忘,雖然有各自的適合場景撩炊,但由于風(fēng)格設(shè)計(jì)不統(tǒng)一,一些公共組件如日志崎脉、緩存拧咳、http請求無法公用,反復(fù)造輪子囚灼,浪費(fèi)精力骆膝。PHP-FIG PHP Framework Interop Group 目標(biāo)便是解決這個(gè)問題祭衩,它從代碼規(guī)范、接口標(biāo)準(zhǔn)阅签、自動(dòng)加載幾個(gè)角度持續(xù)推薦了一批規(guī)范 這些推薦標(biāo)準(zhǔn)便是 PSR PHP Standards Recommendation掐暮,并不強(qiáng)制,各框架開發(fā)者自行決定是否支持愉择,任何人都可以向FIG反饋意見

目前已經(jīng)初見成效劫乱,目前最流行的Laravel框架便大量使用了公共組件,提高開發(fā)效率

延伸

架構(gòu)

實(shí)踐

PHP7比較常用的變化

PHP7發(fā)布后除了性能上的巨大變化外锥涕,在語言的規(guī)范衷戈、語法糖和許多細(xì)節(jié)地方都做了許多改進(jìn),這里只記錄一些我認(rèn)為經(jīng)常涉及的改動(dòng)

// 新增??三元操作符层坠,類似?:
$foo = $a ?? 'nothing';
$foo = isset($a) ? $a : 'nothing';

// 常量數(shù)組
define('OPTION', [1, 2, 3,]);

// json_encode支持unicode不轉(zhuǎn)碼
json_encode(['一', '二',], JSON_UNESCAPED_UNICODE);

// 數(shù)組解包
$a = [1, 2, 3];
$b = [1, 2, ...$a, 3,]; // $b為[1, 2, 1, 2, 3, 3,]

// 箭頭函數(shù)殖妇,一個(gè)語法糖
$factor = 10;
$nums = array_map(fn($n)=>$n * $factor,[1,2,3]); // [10,20,30]
// 之前的寫法
$nums = array_map(function($num)use($factor){
  return $num * $factor;
},[1,2,3]);

// group use
use app\model\service\{User, Task, Log};

正則

正則能夠替代大部分字符串相關(guān)工作,而且非常高效方便破花,只是性能問題不適合密集計(jì)算場景谦趣。PHP7中廢棄了ereg_,保留preg_座每。此處記錄使用正則過程中的經(jīng)驗(yàn)

// 常用的三個(gè)為
preg_replace($pattern, $replace, $subject);
preg_match($pattern, $subject, &$match); // 只匹配第一個(gè)
preg_match_all($pattern, $subject, &$match); // 可匹配多個(gè)

常見的正則網(wǎng)上一堆前鹅,這里只積累一些易錯(cuò)的地方:

  • [家|(春秋)] 與 [家|春秋]有區(qū)別嗎?有的峭梳,中括號(hào)中'|'就是匹配豎線舰绘,并非‘或’的意思,所以第一個(gè)意思為:匹配包含家或豎線或春秋葱椭;第二個(gè)意思為:匹配包含家或豎線或春或秋
  • 處理Unicode時(shí)別忘了加u捂寿,/pattern/u

延伸

輸入/輸出過濾、轉(zhuǎn)義

一般場景使用urlencode(url中特殊字符轉(zhuǎn)義孵运,如漢字秦陋、空格),http_build_query(urlencode的封裝)治笨,htmlspecialchars和html_entity_decode是對html實(shí)體轉(zhuǎn)義(如< >)

如果應(yīng)對復(fù)雜安全性要求更高的場景驳概,建議學(xué)習(xí)并使用htmlpurifier

文件操作

常用的文件讀寫為feof fread fwrite fgets fget file_get_contents file_put_contents fgetcsv fputcsv

延伸

執(zhí)行外部命令

PHP執(zhí)行外部命令并不友好,提供了多種方式旷赖,令人迷惑顺又,自己目前并不清楚底層差別,只是從函數(shù)行為角度加以區(qū)分

// exec最常用
$last_line = exec(string $cmd, array &$out, int &$retval);

// system會(huì)在方法中輸出命令內(nèi)容
$last_line = system(string $cmd, int &$retval);

// 返回值為cmd執(zhí)行結(jié)果的字符串
$out = shell_exec(string $cmd, int &$retval);
$out = `$cmd`;

// 命令與system的最大區(qū)別是杠愧,直接將執(zhí)行結(jié)果輸出到瀏覽器待榔,支持二進(jìn)制
passthru($cmd, int &$retval);

延伸

字符串操作

PHP提供了很多方便的字符串函數(shù)逞壁,常用的有:

  • strstr ( string $haystack , mixed $needle [, bool $before_needle = false ] )流济。返回 haystack 字符串從 needle 第一次出現(xiàn)的位置開始到 haystack 結(jié)尾的字符串锐锣。若為before_needle為 TRUE,strstr() 將返回 needle 在 haystack 中的位置之前的部分绳瘟。
  • substr( string $string , int $start [, int $length ] )雕憔。返回字符串 string 由 start 和 length 參數(shù)指定的子字符串。
  • substr_replace ( mixed $string , mixed $replacement , mixed $start [, mixed $length ] )糖声。substr_replace() 在字符串 string 的副本中將由 start 和可選的 length 參數(shù)限定的子字符串使用 replacement 進(jìn)行替換斤彼。
  • strrev ( string $string )。返回 string 反轉(zhuǎn)后的字符串蘸泻。
  • str_replace ( mixed $search , mixed $replace , mixed $subject [, int &$count ] )琉苇。該函數(shù)返回一個(gè)字符串或者數(shù)組。該字符串或數(shù)組是將 subject 中全部的 search 都被 replace 替換之后的結(jié)果悦施。subject為執(zhí)行替換的數(shù)組或者字符串并扇。也就是 haystack。如果 subject 是一個(gè)數(shù)組抡诞,替換操作將遍歷整個(gè) subject穷蛹,返回值也將是一個(gè)數(shù)組。如果count被指定昼汗,它的值將被設(shè)置為替換發(fā)生的次數(shù)肴熏。
  • strpos ( string $haystack , mixed $needle [, int $offset = 0 ] )。返回 needle 在 haystack 中首次出現(xiàn)的數(shù)字位置顷窒;如果提供了offset參數(shù)蛙吏,搜索會(huì)從字符串該字符數(shù)的起始位置開始統(tǒng)計(jì)。 如果是負(fù)數(shù)蹋肮,搜索會(huì)從字符串結(jié)尾指定字符數(shù)開始出刷。
  • ltrim()rtrim()坯辩、trim()馁龟。這仨都是刪除字符串中的空白符。ltrim()刪除字符串開頭的空白字符;rtrim()刪除字符串末端的空白字符漆魔;trim()去除字符串首尾處的空白字符坷檩。

數(shù)組操作

這篇文章總結(jié)的非常好,不做贅述改抡。PHP數(shù)組使用之道

非阻塞與并行操作

在遇到慢速操作導(dǎo)致fpm進(jìn)程積壓時(shí)矢炼,可以使用fastcgi_finish_request()方法觸發(fā)請求結(jié)束,但fpm進(jìn)程繼續(xù)執(zhí)行慢速操作

另一個(gè)有用的場景是批量網(wǎng)絡(luò)請求或io阿纤,若不使用非阻塞方法句灌,則需要依次進(jìn)行,效率極其低下,可以使用curl_muti_*和非阻塞socket胰锌,詳見PHP的非阻塞或并行請求實(shí)現(xiàn)方式

除此之外其他常用的辦法可以直接借助系統(tǒng)命令nohup,或者使用多進(jìn)程編程

多進(jìn)程編程

PHP多進(jìn)程編程中幾點(diǎn)心得

fork子進(jìn)程和父進(jìn)程的分道揚(yáng)鑣
以前總是好奇那種if pid>0寫分支的方式是否足矣支持復(fù)雜編程资昧,“沒有什么計(jì)算機(jī)問題是一層抽象解決不了酬土,如果不能解決,就再抽象一層”格带,確實(shí)通過簡單抽象撤缴,再結(jié)合exit()便能很清晰的控制邏輯邊界

子進(jìn)程會(huì)繼承父進(jìn)程打開的資源(文件描述符)
以前對這句話理解不深,這次數(shù)據(jù)庫連接的子進(jìn)程關(guān)閉算是結(jié)實(shí)的上了一課叽唱,也學(xué)到了php運(yùn)行模式在多進(jìn)程方面的一個(gè)缺陷(進(jìn)程結(jié)束后釋放所有資源)屈呕。為了避免混亂,資源的申請若不需要共享棺亭,一定要控制好申請的時(shí)機(jī)凉袱。

另外一個(gè)關(guān)于該點(diǎn)的坑是,標(biāo)準(zhǔn)輸入侦铜、輸出专甩、錯(cuò)誤,也是父進(jìn)程打開的資源钉稍,同樣會(huì)被繼承(導(dǎo)致exec不能退出)

進(jìn)程守護(hù)化
實(shí)現(xiàn)進(jìn)程的守護(hù)化需要進(jìn)行以下幾個(gè)步驟:

  1. fork一次涤躲,父進(jìn)程退出,子進(jìn)程認(rèn)1作父贡未,自成一個(gè)進(jìn)程組組長
  2. setsid种樱,重新創(chuàng)建一個(gè)會(huì)話,成為一個(gè)會(huì)話組(包含多個(gè)進(jìn)程組)的組長
  3. 改變工作目錄為/俊卤,與當(dāng)前啟動(dòng)目錄解耦
  4. 關(guān)閉標(biāo)準(zhǔn)輸入嫩挤、輸出,重新定向到文件或者/dev/null消恍,避免資源泄露
  5. umask(0)岂昭,避免父進(jìn)程繼承權(quán)限掩碼

實(shí)用代碼片段

php管理

# 查看配置文件位置
php --ini
# 指定加載php.ini的絕對路徑
php -c another.ini

# 查看phpinfo
php -i

# 查看擴(kuò)展目錄
php-config --extension-dir
# 查看擴(kuò)展模塊,注意擴(kuò)展模塊是可以通過修改ini不啟用的狠怨,內(nèi)置的不行
php -m
# 查看摸個(gè)擴(kuò)展的信息
php --ri swoole
# 查看某個(gè)擴(kuò)展提供了哪些類和函數(shù)
php --re swoole

# 啟動(dòng)一個(gè)內(nèi)置的Web服務(wù)器约啊,用于開發(fā)環(huán)境內(nèi)進(jìn)行程序的調(diào)試
php -S 0.0.0.0:9000 [-t /data/webroot/]

# 檢測一個(gè)php代碼文件是否有語法錯(cuò)誤
php -l file

# 執(zhí)行一段php代碼
php -r "echo 'hello world';"

php-fpm管理

# php在5.3.3之前fpm是需要自己打補(bǔ)丁的
# 然后在管理時(shí)
php-fpm [start|stop|reload]

# 5.3.3之后則已加入源碼中,只需要編譯中開啟即可
# 關(guān)于php-fpm的編譯參數(shù)有
–enable-fpm –with-fpm-user=www –with-fpm-group=www –with-libevent-dir=libevent_path

# 還有個(gè)變化是佣赖,必須通過信號(hào)管理fpm
# SIGINT, SIGTERM 立刻終止  
# SIGQUIT 平滑終止  
# SIGUSR1 重新打開日志文件  
# SIGUSR2 平滑重載所有worker進(jìn)程并重新載入配置和二進(jìn)制模塊
kill -SIGINT `cat fpm.pid`

配置修改

ini_set('memory_limit', '200M');

數(shù)組去重

# 正常方法
$array = array_unique($array);

# 快速方法恰矩,key與val連續(xù)翻轉(zhuǎn)
$array = array_flip($array);
$array = array_flip($array);

# 但這樣還有個(gè)問題就是,若是索引數(shù)組憎蛤,則索引亂序外傅,可以直接使用array_keys
$array = array_flip($array);
$array = array_keys($array);

輸出所有已定義的常量

print_r(get_defined_constants());

curl

# 以前我們通過 PHP 的 cURL 上傳文件是,是使用“@+文件全路徑”的來實(shí)現(xiàn)的:
curl_setopt(ch, CURLOPT_POSTFIELDS, array(
    'file' => '@'.realpath('image.png'),
));

# PHP 從 5.5 開始引入了新的 CURLFile 類用來指向文件,CURLFile 類也可以詳細(xì)定義 MIME 類型萎胰、文件名等可能出現(xiàn)在multipart/form-data 數(shù)據(jù)中的附加信息彬碱,PHP 推薦使用 CURLFile 替代舊的@語法
# 而PHP 5.6 直接只支持 CURLFile 方法
curl_setopt(ch, CURLOPT_POSTFIELDS, [
    'file' => new CURLFile(realpath('image.png')),
]);

時(shí)間處理

// 獲取上個(gè)月第一天及最后一天,下個(gè)月同理
date('Y-m-01', strtotime('-1 month'));
date('Y-m-t', strtotime('-1 month'));

// 獲取當(dāng)月第一天及最后一天.
date('Y-m-01', time());
date('Y-m-t', time());

// 當(dāng)前年份
date('Y');
// 當(dāng)前月份
date('m');
// 當(dāng)前幾號(hào)
date('d');
// 本月天數(shù)奥洼,因?yàn)閠為最后一天的號(hào)
date("t");

文件操作

// 遍歷文件夾,加載文件
foreach ($arrRequireDir as $requireDir) {
  $objDir = dir($requireDir);

  while ($file = $objDir->read()) {
      $filePath = $requireDir . $file;

      if (is_file($filePath) && ($filePath != __FILE__)) {
          var_dump($filePath);
          include_once($filePath);
      }
   }
}

編碼轉(zhuǎn)換

// utf8轉(zhuǎn)big5晚胡,ignore跳過無法編碼
$T = iconv("utf8","big5//ignore", $T);
$T = mb_convert_encoding($T, "big5", "utf8");

令人迷惑

PHP由于初期的野蠻生長灵奖,即便進(jìn)入PHP7時(shí)代,語言中依然保留了大量令人迷惑的地方:

面試常見

有些知識(shí)在實(shí)際開發(fā)中很少遇到估盘,或者遇到就要把始作俑者叉出去瓷患,但在面試中卻喜聞樂見:

學(xué)習(xí)資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市遣妥,隨后出現(xiàn)的幾起案子擅编,更是在濱河造成了極大的恐慌,老刑警劉巖箫踩,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爱态,死亡現(xiàn)場離奇詭異,居然都是意外死亡境钟,警方通過查閱死者的電腦和手機(jī)锦担,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來慨削,“玉大人洞渔,你說我怎么就攤上這事「刻” “怎么了磁椒?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長玫芦。 經(jīng)常有香客問我浆熔,道長,這世上最難降的妖魔是什么桥帆? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任蘸拔,我火速辦了婚禮,結(jié)果婚禮上环葵,老公的妹妹穿的比我還像新娘调窍。我一直安慰自己,他們只是感情好张遭,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布邓萨。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缔恳。 梳的紋絲不亂的頭發(fā)上宝剖,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音歉甚,去河邊找鬼万细。 笑死,一個(gè)胖子當(dāng)著我的面吹牛纸泄,可吹牛的內(nèi)容都是我干的赖钞。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼聘裁,長吁一口氣:“原來是場噩夢啊……” “哼雪营!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起衡便,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對情侶失蹤献起,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后镣陕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谴餐,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年呆抑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了总寒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡理肺,死狀恐怖摄闸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情妹萨,我是刑警寧澤年枕,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站乎完,受9級(jí)特大地震影響熏兄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜树姨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一摩桶、第九天 我趴在偏房一處隱蔽的房頂上張望膏萧。 院中可真熱鬧息尺,春花似錦、人聲如沸脚囊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽士飒。三九已至,卻和暖如春蔗崎,著一層夾襖步出監(jiān)牢的瞬間酵幕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工缓苛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留芳撒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓未桥,卻偏偏與公主長得像笔刹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子钢属,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345