PHP不權(quán)威總結(jié)
歡迎閱讀
本文目標(biāo)用戶是我自己,系統(tǒng)地持續(xù)集成PHP方方面面的知識(shí),但不會(huì)事無(wú)巨細(xì)的一一列舉,只會(huì)挑選我認(rèn)為易忘、易錯(cuò)、重要的內(nèi)容進(jìn)行集成,而且大多是點(diǎn)到為止,需要更詳細(xì)文檔的話會(huì)額外鏈接。所以,如果你已經(jīng)靠PHP謀生了幾年的話這篇文檔應(yīng)該會(huì)對(duì)你有幫助,如果你是PHP新手,建議重點(diǎn)閱讀學(xué)習(xí)資料一節(jié)。
安裝配置 & 環(huán)境搭建
PHP的開(kāi)發(fā)環(huán)境主要是LA/NMP,即Linux、Apache/Nginx、Mysql、PHP,關(guān)于這些工具在不同平臺(tái)的安裝有大量?jī)?yōu)秀詳細(xì)的文檔,這里不廢話,重點(diǎn)還是PHP的安裝,不過(guò)強(qiáng)烈推薦Laravel的Homestead解決方案,一套方便管理的、跨平臺(tái)、統(tǒng)一的、虛擬化開(kāi)發(fā)環(huán)境。
編譯安裝
推薦使用Like Unix操作系統(tǒng)作為開(kāi)發(fā)、線上環(huán)境,強(qiáng)大且簡(jiǎn)單。我推薦的PHP安裝方式是編譯安裝,對(duì)新手也是,不走彎路、不踩坑怎么成長(zhǎng)。源碼安裝你的選擇會(huì)更加自由,能第一時(shí)間嘗試各種Alpha版,而且在日常工作中,安裝擴(kuò)展、調(diào)試、優(yōu)化,都需要對(duì)PHP的目錄、文件有一定了解。
- PHP官網(wǎng)下載你需要的版本源碼
- 編譯 & 安裝
# --prefix指定目錄,--with-config-file-path指定php-config目錄,其他為要一同安裝的擴(kuò)展或開(kāi)啟的功能
./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
- 添加配置。PHP的源碼包中附帶了一個(gè)開(kāi)發(fā)環(huán)境使用的配置(開(kāi)啟了方便調(diào)試、減少性能消耗的配置),只需要放到默認(rèn)位置就行
sudo mkdir /etc/php
sudo cp php.ini-development /etc/php/php.ini
- 擴(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)中,由于依賴(lài)眾多(git、redis、memcache、nodejs、npm),每個(gè)人習(xí)慣不同,開(kāi)發(fā)環(huán)境都會(huì)有所差別,會(huì)導(dǎo)致許多難以察覺(jué)的問(wèn)題,并大大提高團(tuán)隊(duì)協(xié)調(diào)的難度,我曾遇到過(guò)PHP5.2、5.4兩個(gè)版本的正則執(zhí)行結(jié)果不同而導(dǎo)致上線折騰到凌晨2點(diǎn)的坑事。所以Laravel團(tuán)隊(duì)提供了一套基于虛擬機(jī)的跨平臺(tái)開(kāi)發(fā)環(huán)境搭建方案 —— Homestead(譯為家園)。
我來(lái)簡(jiǎn)單梳理一下。Homestead的做法是,首先選擇免費(fèi)穩(wěn)定的Virtual Box作為虛擬機(jī),用Vagrant來(lái)管理虛擬機(jī)和開(kāi)發(fā)環(huán)境,然后又配置了一套統(tǒng)一的git、redis等常用工具,直接安裝即可。這里有詳細(xì)的說(shuō)明文檔。
Mac安裝
直接使用Homebrew安裝,類(lèi)似Linux的yum、apt-get方案。
Windows安裝
推薦使用XAMPP、EasyPHP、WAMP等一類(lèi)的套件,簡(jiǎn)單易用,包括了LN/AMP最基本的開(kāi)發(fā)環(huán)境,提供了GUI管理界面。需要提醒的是千萬(wàn)不要用這些套件部署線上環(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è)類(lèi),來(lái)簡(jiǎn)化日期處理。
// 基本的日期處理對(duì)象
DateTime: __construct ([ string $time = "now" [, DateTimeZone $timezone = NULL ]] );
// 時(shí)區(qū)對(duì)象
DateTimeZone: __construct ($timezone);
// 時(shí)間段對(duì)象
DateInterval: __construct ($interval_spec);
// 時(shí)間迭代器
DatePeriod: __construct (DateTimeInterface $start, DateInterval $interval, DateTimeInterface $end, $options=0);
列舉幾個(gè)使用場(chǎng)景
$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開(kāi)頭, 日期和時(shí)間T隔開(kāi) 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ù)庫(kù)
常用mysqli_* mysql_*函數(shù)簇由于比較底層且繁瑣,加之經(jīng)常使用框架封裝好的方法,經(jīng)常忘記原生的數(shù)據(jù)庫(kù)操作,出于便捷性、通用性與安全性的考慮,數(shù)據(jù)庫(kù)連接推薦使用PDO
// 建立數(shù)據(jù)庫(kù)連接,pdo支持mysql oracle sqlite等多種常用數(shù)據(jù)庫(kù)
$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ò)誤信息能夠幫助我們快速解決問(wèn)題。
// 錯(cuò)誤報(bào)告控制,也可以在php.ini中修改error_reporting
error_reporting(E_ALL & ~E_NOTICE); // 關(guān)閉notice錯(cuò)誤
error_reporting(E_WARNING | E_ERROR); // 只開(kāi)啟warning和error錯(cuò)誤
// 錯(cuò)誤顯示
ini_set('display_errors', 1);
# php中錯(cuò)誤相關(guān)配置
# 錯(cuò)誤日志是否打開(kāi)
log_errors = On (Off)
# 錯(cuò)誤日志記錄的位置
error_log = php_errors.log
# 是否打開(kāi)錯(cuò)誤顯示
display_errors = Off
# 定義錯(cuò)誤顯示的級(jí)別
error_reporting = E_ALL
關(guān)于錯(cuò)誤的控制,建議為:
- 任何環(huán)境都要記錄錯(cuò)誤
- 開(kāi)發(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中嘗試對(duì)此進(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ī)制,可以注冊(cè)一個(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)致程序退出而無(wú)法轉(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)綁定是指類(lè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類(lèi),而self為定義者也就是People類(lèi),所以輸出為
// people
// user
反射
反射是一種面向?qū)ο蟮臋C(jī)制,提供了一套API允許程序在運(yùn)行時(shí)對(duì)類(lèi)進(jìn)行檢查,類(lèi)定義、類(lèi)方法、成員變量、可訪問(wèn)性、源碼,甚至訪問(wèn)私有成員與方法。反射機(jī)制經(jīng)常使用在測(cè)試、構(gòu)建與框架底層代碼中,能夠產(chǎn)生很多“神奇”的效果。
延伸
- 《深入PHP 面向?qū)ο?、模式與實(shí)踐》反射一節(jié)
- 反射在PHP中應(yīng)用
trait
trait,翻譯為性狀,主要用于在類(lèi)之間抽象一些特性。比如超人和小鳥(niǎo),他們都需要實(shí)現(xiàn)一個(gè)fly方法,一般的做法是抽象一個(gè)更高級(jí)的類(lèi)實(shí)現(xiàn)fly方法他們共同繼承,但是這樣強(qiáng)行耦合了兩個(gè)并不強(qiáng)相關(guān)的類(lèi);另一種做法是定義一個(gè)Fly的接口實(shí)現(xiàn)fly方法,這樣雖然避免耦合,但同樣的fly方法需要實(shí)現(xiàn)兩遍,違背了DYR原則;比較好的辦法是has-a,即定義一個(gè)包含fly方法的公共類(lèi),并在超人類(lèi)和小鳥(niǎo)類(lèi)中分別實(shí)例化。其實(shí)trai就類(lèi)似第三種做法,而且使用起來(lái)更方便一些
trait Fly {
public function fly() {
echo 'I can fly';
}
}
class Superman {
use Fly;
}
class Bird {
use Fly;
}
$clark = new Superman();
$clark->fly();
就是這么簡(jiǎn)單,但trait使用時(shí)有幾個(gè)需要注意的地方
- php的方法調(diào)用優(yōu)先級(jí)為class自身方法 > trait > 繼承
- 多個(gè)trait用逗號(hào)分隔
- trait也可以使用trait
- trait也可以定義屬性,但不能與class中屬性沖突,fatal error
高級(jí)特性
命名空間
命名空間主要用來(lái)解決命名沖突。php的命名空間其實(shí)很簡(jiǎn)單,只是由于其只是個(gè)虛擬空間,并不要求與代碼實(shí)際文件相對(duì)應(yīng),雖然會(huì)靈活許多,但導(dǎo)致使用起來(lái)不好理解,這也是我詬病php哲學(xué)的一點(diǎn),過(guò)分追求靈活簡(jiǎn)單,反而導(dǎo)致方案太多而困惑。
先看幾個(gè)命名空間的常見(jiàn)場(chǎng)景
namespace App\A\Route;
class Http {
}
namespace App\B\Route;
class Http {
}
// 直接new,默認(rèn)當(dāng)前命名空間
$routeB = new Http();
// 使用全限定命名空間,\開(kāi)頭
$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必須在文件開(kāi)頭
- function const同樣可以使用命名空間,導(dǎo)入時(shí)關(guān)鍵字為use function, use const
延伸
yield
yield,生成器,主要用于“逐步”的處理大文件、復(fù)雜計(jì)算,減少內(nèi)存消耗,和一定程度的降低代碼復(fù)雜度,類(lèi)似游標(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),這也是生成器名字的由來(lái)
生成器的返回值,和交互
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ì)話,作用是解決無(wú)狀態(tài)的Http協(xié)議實(shí)現(xiàn)用戶登錄的問(wèn)題。用戶第一次訪問(wèn)應(yīng)用時(shí)生成一個(gè)seesion_id并將會(huì)話信息持久化,用戶后續(xù)訪問(wèn)時(shí)均帶上該session_id,應(yīng)用程序籍此區(qū)分用戶并共享會(huì)話信息,用戶在退出時(shí)注銷(xiāo)該session_id。目前直接使用原生的php session機(jī)制并不多,大多數(shù)是使用一個(gè)單點(diǎn)的統(tǒng)一用戶會(huì)話管理服務(wù),不過(guò)php本身的session機(jī)制在一些場(chǎng)景依然簡(jiǎn)單有效。
session_start(); // 開(kāi)啟session,無(wú)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)致分布式集群無(wú)法正常使用,需要覆蓋實(shí)現(xiàn)php的session回調(diào),借助redis、mysql等集中式存儲(chǔ)實(shí)現(xiàn)分布式的session
- php的session過(guò)期有些費(fèi)解
- 由于判斷session過(guò)期是要頻繁的文件檢查,性能原因考慮,配置了一個(gè)檢查概率,默認(rèn)1/100
- 坑爹的以文件最后修改時(shí)間為過(guò)期依舊,而每次更新會(huì)話變量,最后修改時(shí)間都會(huì)刷新
- 由于默認(rèn)目錄是/tmp,假如服務(wù)器部署多個(gè)應(yīng)用而沒(méi)有調(diào)整時(shí),超時(shí)時(shí)間短的應(yīng)用會(huì)導(dǎo)致長(zhǎng)的被清理
延伸
取值范圍
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)大,但由于采集的信息比較多,對(duì)性能影響較大無(wú)法在實(shí)際生產(chǎn)環(huán)境使用,導(dǎo)致一些問(wèn)題難以發(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)用程序中顯示開(kāi)啟、終止分析,會(huì)生成性能分析文件,借助可視化工具,可清晰的看到性能瓶頸
單元測(cè)試
單測(cè)自己的使用經(jīng)驗(yàn)有限,但也嘗到了甜頭。PHP的單元測(cè)試主要借助PHPunit,可以通過(guò)composer安裝。簡(jiǎn)單記錄幾個(gè)使用經(jīng)驗(yàn)
- 最好在調(diào)試階段就開(kāi)始使用phpunit,
既可以第一時(shí)間實(shí)際使用類(lèi)/方法,發(fā)現(xiàn)錯(cuò)誤與難用之處;也可以持續(xù)積累case,方便后續(xù)回歸 - TDD,所謂測(cè)試驅(qū)動(dòng),是先通過(guò)測(cè)試用例確認(rèn)軟件行為,然后再實(shí)現(xiàn)軟件來(lái)通過(guò)測(cè)試用例,達(dá)到明確的需求確認(rèn)和保證軟件質(zhì)量。但由于測(cè)試用例比較耗費(fèi)精力,所以我一般用在小模塊開(kāi)發(fā)中
- phpunit的--filter選項(xiàng)比較有用,可以方便的測(cè)試指定的用例
- 將case放在一個(gè)回滾的事務(wù)中,可以在自動(dòng)測(cè)試完后恢復(fù)現(xiàn)場(chǎng)
- phpunit中的data準(zhǔn)備回調(diào)非常好用,可以自動(dòng)準(zhǔn)備、恢復(fù)測(cè)試數(shù)據(jù),保證每次測(cè)試的現(xiàn)場(chǎng)一致可復(fù)現(xiàn)
延伸
PHPUnit手冊(cè)
如何有效的書(shū)寫(xiě)項(xiàng)目單元測(cè)試
gdb
延伸
安全
加密
加密的一個(gè)原則是絕對(duì)不要明文存儲(chǔ)、傳輸密碼,一般使用一種單向算法對(duì)密碼加密,以前常用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)證,就這么簡(jiǎ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手冊(cè)
組件 & 擴(kuò)展 & 工具
自動(dòng)加載
PHP中所謂的自動(dòng)加載是class的命名空間與類(lèi)名去解析class文件真實(shí)path并require,映射規(guī)則一般使用官方推薦的PSR-4,大概為一個(gè)文件只包含一個(gè)class、trait、interface,同時(shí)namespace要與文件路徑一致。這樣利用PHP內(nèi)置的類(lèi)加載器注冊(cè)機(jī)制便可以自動(dòng)require了。一個(gè)典型的autoloader.php如下:
<?php
/**
* 使用SPL組冊(cè)這個(gè)自動(dòng)加載函數(shù)后,遇到下述代碼時(shí)這個(gè)函數(shù)會(huì)嘗試 從/path/to/project/src/Baz/Qux.php文件中加載\Foo\Bar\Baz\Qux類(lèi):
* new \Foo\Bar\Baz\Qux;
* @param string $class 完全限定的類(lèi)名。
* @return void
**/
spl_autoload_register(function ($class) {
// 項(xiàng)目的命名空間前綴
$prefix = 'Foo\\Bar\\';
// 目錄前綴對(duì)應(yīng)的根目錄
$base_dir = __DIR__ . '/src/';
// 判斷傳入的類(lèi)是否使用了這個(gè)命名空間前綴
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
// 沒(méi)有使用,交給注冊(cè)的下一個(gè)自動(dòng)加載器處理
return;
}
// 獲取去掉前綴后的類(lèi)名
$relative_class = substr($class, $len);
// 把命名空間前綴替換成根目錄,
// 在去掉前綴的類(lèi)名中,把命名空間分隔符替換成目錄分隔符,
// 然后在后面加上.php
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
// 如果該文件存在,就將其導(dǎo)入
if (file_exists($file)) {
require $file;
}
});
composer
類(lèi)似apt-get yum npm等,composer是PHP的類(lèi)管理器,可以自動(dòng)下載更新class、庫(kù)、項(xiàng)目,管理項(xiàng)目依賴(lài),結(jié)合目前最大的PHP包分享社區(qū)packagist可以非常方便的完成項(xiàng)目環(huán)境搭建
延伸
PHP最流行的包分享packagist
Composer使用手冊(cè)
底層原理
生命周期
SPAI
Server Application Programming Interface,服務(wù)應(yīng)用編程接口,php一切的開(kāi)始,簡(jiǎn)單說(shuō)作用是負(fù)責(zé)隔離php程序的執(zhí)行與運(yùn)行環(huán)境,使得無(wú)論是cli、fpm、webserver module,php代碼都可以正常運(yùn)行

php的生命周期如圖所示,fpm中主要是啟動(dòng)請(qǐng)求周期中循環(huán),cli中則僅僅是一次完整的流程。簡(jiǎn)要說(shuō)明一下各個(gè)環(huán)節(jié)的主要作用:
- MINIT,模塊初始化階段,只執(zhí)行一次
- 激活SAPI
- 初始化垃圾回收器
- 啟動(dòng)zend引擎
- 注冊(cè)PHP內(nèi)置常量
- 解析php.ini
- 注冊(cè)$_GET、$_POST等超全局變量及處理回調(diào)
- 動(dòng)態(tài)加載 .so
- 回調(diào)擴(kuò)展minit方法
- RINIT,請(qǐng)求初始化階段,每次請(qǐng)求到達(dá)時(shí)都會(huì)執(zhí)行
- 重置zend引擎,重置編譯器、符號(hào)表等
- 重置垃圾回收器
- 回調(diào)擴(kuò)展rinit
- EXECUTE,腳本執(zhí)行階段
- 加載php腳本
- 預(yù)編譯、詞法解析、語(yǔ)法解析,生成抽象語(yǔ)法樹(shù)(AST)
- 編譯生成中間碼,op_code
- ZendVM執(zhí)行op_code
- RSHUTDOWN,請(qǐng)求關(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
- 清理資源
延伸
- TIPI深入理解PHP內(nèi)核-生命周期
- 《PHP7內(nèi)核剖析》第1章基礎(chǔ)架構(gòu)
zval zend_value zend_reference 和 CoW
PHP7中一個(gè)重要改動(dòng)是變量數(shù)據(jù)結(jié)構(gòu)的優(yōu)化,將原來(lái)的變量的數(shù)據(jù)結(jié)構(gòu)從一個(gè)zval拆為zval、zend_value,并將原zval中引用計(jì)數(shù)移入zend_value中,可以簡(jiǎn)單理解為zval是變量名,zend_value是實(shí)際的變量值,減少了內(nèi)存占用,結(jié)構(gòu)更清晰。另一個(gè)變化是 PHP7中整形,浮點(diǎn)型,布爾型,NULL是直接保存在zval中,無(wú)zend_value,所以無(wú)引用計(jì)數(shù);另外字符串雖然有zend_value,但由于是程序結(jié)束后統(tǒng)一回收,所以也沒(méi)引用計(jì)數(shù)
zend_reference則是原來(lái)的是否為引用變量標(biāo)記位,在PHP7中擴(kuò)展為一種數(shù)據(jù)類(lèi)型。生成引用類(lèi)型的唯一方法就是使用&$val,直接復(fù)制一個(gè)引用類(lèi)型的變量是無(wú)法復(fù)制引用的,而對(duì)象和資源復(fù)制則默認(rèn)為引用復(fù)制
struct _zval_struct {
zend_value value; // 變量實(shí)際值
……
};
struct _zend_value {
zend_refcounted *counted; // gc頭部
zend_reference *ref; // 引用類(lèi)型
……
}
實(shí)際測(cè)試一下
<?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 ()
/* 引用類(lèi)型 */
$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 (),寫(xiě)時(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,寫(xiě)時(shí)復(fù)制,變量復(fù)制時(shí),PHP解釋器并不會(huì)真的復(fù)制內(nèi)存,而是當(dāng)變量的值實(shí)際發(fā)生變化時(shí)才真正復(fù)制內(nèi)存,從而減少內(nèi)存消耗,提高性能
但引用類(lèi)型的加入導(dǎo)致CoW有一點(diǎn)疑惑,在PHP5中由于只有zval,所以即便變量沒(méi)有修改值,但由于&操作變?yōu)橐妙?lèi)型,則會(huì)導(dǎo)致一次隱性的變量分離,潛在內(nèi)存溢出風(fēng)險(xiǎn),不易排查。不過(guò)PHP7中由于zval分離,zend_value沒(méi)有變化時(shí),便不會(huì)發(fā)生CoW,算是修復(fù)了這個(gè)缺陷
最后看一個(gè)引用類(lèi)型導(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)如下:

插入一個(gè)新key,先經(jīng)過(guò)散列函數(shù)映射到中間映射表里,其中記錄著元素?cái)?shù)組的位置,中間映射表與元素?cái)?shù)組為內(nèi)存中連續(xù)空間。當(dāng)直接訪問(wèn)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)引用 無(wú)法使用該方法回收,比如某個(gè)數(shù)組中元素又引用了數(shù)組本身。針對(duì)這種情況,PHP會(huì)在引用計(jì)數(shù)減少時(shí)收集可能的垃圾變量,然后定期對(duì)收集的變量進(jìn)行深度遍歷,在遍歷中引用次數(shù)依次減一,若存在循環(huán)引用則最終引用計(jì)數(shù)將變?yōu)?,變量回收
JIT
JIT,just in time,即時(shí)編譯,是解釋型語(yǔ)言的一種性能優(yōu)化手段。雖然編譯型語(yǔ)言與解釋型語(yǔ)言都有編譯階段,但不同的是編譯型語(yǔ)言是直接將源碼編譯為機(jī)器碼,而解釋型語(yǔ)言則是編譯為字節(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)行稱(chēng)為事前編譯AOT(ahead of time),php也是支持這種做法的,但這樣做有幾個(gè)缺點(diǎn),首先背離php堅(jiān)持的一貫簡(jiǎn)單原則,項(xiàng)目的發(fā)布上線需要提前編譯,對(duì)研發(fā)者也不夠友好,而且JIT由于可以收集大量運(yùn)行時(shí)信息,編譯的優(yōu)化效果更好,綜合來(lái)說(shuō),JIT更適合一些
JIT也并非沒(méi)有缺點(diǎn) 首先是JIT是需要額外消耗性能的,如果程序中無(wú)明顯熱點(diǎn)代碼,或者運(yùn)行周期比較短,則JIT反而會(huì)導(dǎo)致性能下降
php引入JIT后的性能變化

社區(qū)
FIG & PSR
PHP令人詬病的問(wèn)題之一是框架太多,ThinkPHP、CI、Yii、Zend、Laravel……,雖然有各自的適合場(chǎng)景,但由于風(fēng)格設(shè)計(jì)不統(tǒng)一,一些公共組件如日志、緩存、http請(qǐng)求無(wú)法公用,反復(fù)造輪子,浪費(fèi)精力。PHP-FIG PHP Framework Interop Group 目標(biāo)便是解決這個(gè)問(wèn)題,它從代碼規(guī)范、接口標(biāo)準(zhǔn)、自動(dòng)加載幾個(gè)角度持續(xù)推薦了一批規(guī)范 這些推薦標(biāo)準(zhǔn)便是 PSR PHP Standards Recommendation,并不強(qiáng)制,各框架開(kāi)發(fā)者自行決定是否支持,任何人都可以向FIG反饋意見(jiàn)
目前已經(jīng)初見(jiàn)成效,目前最流行的Laravel框架便大量使用了公共組件,提高開(kāi)發(fā)效率
延伸
架構(gòu)
實(shí)踐
PHP7比較常用的變化
PHP7發(fā)布后除了性能上的巨大變化外,在語(yǔ)言的規(guī)范、語(yǔ)法糖和許多細(xì)節(jié)地方都做了許多改進(jìn),這里只記錄一些我認(rèn)為經(jīng)常涉及的改動(dòng)
// 新增??三元操作符,類(lèi)似?:
$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è)語(yǔ)法糖
$factor = 10;
$nums = array_map(fn($n)=>$n * $factor,[1,2,3]); // [10,20,30]
// 之前的寫(xiě)法
$nums = array_map(function($num)use($factor){
return $num * $factor;
},[1,2,3]);
// group use
use app\model\service\{User, Task, Log};
正則
正則能夠替代大部分字符串相關(guān)工作,而且非常高效方便,只是性能問(wèn)題不適合密集計(jì)算場(chǎng)景。PHP7中廢棄了ereg_,保留preg_。此處記錄使用正則過(guò)程中的經(jīng)驗(yàn)
// 常用的三個(gè)為
preg_replace($pattern, $replace, $subject);
preg_match($pattern, $subject, &$match); // 只匹配第一個(gè)
preg_match_all($pattern, $subject, &$match); // 可匹配多個(gè)
常見(jiàn)的正則網(wǎng)上一堆,這里只積累一些易錯(cuò)的地方:
- [家|(春秋)] 與 [家|春秋]有區(qū)別嗎?有的,中括號(hào)中'|'就是匹配豎線,并非‘或’的意思,所以第一個(gè)意思為:匹配包含家或豎線或春秋;第二個(gè)意思為:匹配包含家或豎線或春或秋
- 處理Unicode時(shí)別忘了加u,/pattern/u
延伸
輸入/輸出過(guò)濾、轉(zhuǎn)義
一般場(chǎng)景使用urlencode(url中特殊字符轉(zhuǎn)義,如漢字、空格),http_build_query(urlencode的封裝),htmlspecialchars和html_entity_decode是對(duì)html實(shí)體轉(zhuǎn)義(如< >)
如果應(yīng)對(duì)復(fù)雜安全性要求更高的場(chǎng)景,建議學(xué)習(xí)并使用htmlpurifier
文件操作
常用的文件讀寫(xiě)為feof fread fwrite fgets fget file_get_contents file_put_contents fgetcsv fputcsv
延伸
- 建議學(xué)習(xí)并使用Symfony中Finder類(lèi)庫(kù)
執(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執(zhí)行外部命令
- 建議學(xué)習(xí)并使用Symfony中Process類(lèi)庫(kù)
字符串操作
PHP提供了很多方便的字符串函數(shù),常用的有:
-
strstr ( string $haystack , mixed $needle [, bool $before_needle = false ] )。返回 haystack 字符串從 needle 第一次出現(xiàn)的位置開(kāi)始到 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ù)的起始位置開(kāi)始統(tǒng)計(jì)。 如果是負(fù)數(shù),搜索會(huì)從字符串結(jié)尾指定字符數(shù)開(kāi)始。 -
ltrim()、rtrim()、trim()。這仨都是刪除字符串中的空白符。ltrim()刪除字符串開(kāi)頭的空白字符;rtrim()刪除字符串末端的空白字符;trim()去除字符串首尾處的空白字符。
數(shù)組操作
這篇文章總結(jié)的非常好,不做贅述。PHP數(shù)組使用之道
非阻塞與并行操作
在遇到慢速操作導(dǎo)致fpm進(jìn)程積壓時(shí),可以使用fastcgi_finish_request()方法觸發(fā)請(qǐng)求結(jié)束,但fpm進(jìn)程繼續(xù)執(zhí)行慢速操作
另一個(gè)有用的場(chǎng)景是批量網(wǎng)絡(luò)請(qǐng)求或io,若不使用非阻塞方法,則需要依次進(jìn)行,效率極其低下,可以使用curl_muti_*和非阻塞socket,詳見(jiàn)PHP的非阻塞或并行請(qǐng)求實(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寫(xiě)分支的方式是否足矣支持復(fù)雜編程,“沒(méi)有什么計(jì)算機(jī)問(wèn)題是一層抽象解決不了,如果不能解決,就再抽象一層”,確實(shí)通過(guò)簡(jiǎn)單抽象,再結(jié)合exit()便能很清晰的控制邏輯邊界
子進(jìn)程會(huì)繼承父進(jìn)程打開(kāi)的資源(文件描述符)
以前對(duì)這句話理解不深,這次數(shù)據(jù)庫(kù)連接的子進(jìn)程關(guān)閉算是結(jié)實(shí)的上了一課,也學(xué)到了php運(yùn)行模式在多進(jìn)程方面的一個(gè)缺陷(進(jìn)程結(jié)束后釋放所有資源)。為了避免混亂,資源的申請(qǐng)若不需要共享,一定要控制好申請(qǐng)的時(shí)機(jī)。
另外一個(gè)關(guān)于該點(diǎn)的坑是,標(biāo)準(zhǔn)輸入、輸出、錯(cuò)誤,也是父進(jìn)程打開(kāi)的資源,同樣會(huì)被繼承(導(dǎo)致exec不能退出)
進(jìn)程守護(hù)化
實(shí)現(xiàn)進(jìn)程的守護(hù)化需要進(jìn)行以下幾個(gè)步驟:
- fork一次,父進(jìn)程退出,子進(jìn)程認(rèn)1作父,自成一個(gè)進(jìn)程組組長(zhǎng)
- setsid,重新創(chuàng)建一個(gè)會(huì)話,成為一個(gè)會(huì)話組(包含多個(gè)進(jìn)程組)的組長(zhǎng)
- 改變工作目錄為/,與當(dāng)前啟動(dòng)目錄解耦
- 關(guān)閉標(biāo)準(zhǔn)輸入、輸出,重新定向到文件或者/dev/null,避免資源泄露
- umask(0),避免父進(jìn)程繼承權(quán)限掩碼
實(shí)用代碼片段
php管理
# 查看配置文件位置
php --ini
# 指定加載php.ini的絕對(duì)路徑
php -c another.ini
# 查看phpinfo
php -i
# 查看擴(kuò)展目錄
php-config --extension-dir
# 查看擴(kuò)展模塊,注意擴(kuò)展模塊是可以通過(guò)修改ini不啟用的,內(nèi)置的不行
php -m
# 查看摸個(gè)擴(kuò)展的信息
php --ri swoole
# 查看某個(gè)擴(kuò)展提供了哪些類(lèi)和函數(shù)
php --re swoole
# 啟動(dòng)一個(gè)內(nèi)置的Web服務(wù)器,用于開(kāi)發(fā)環(huán)境內(nèi)進(jìn)行程序的調(diào)試
php -S 0.0.0.0:9000 [-t /data/webroot/]
# 檢測(cè)一個(gè)php代碼文件是否有語(yǔ)法錯(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之后則已加入源碼中,只需要編譯中開(kāi)啟即可
# 關(guān)于php-fpm的編譯參數(shù)有
–enable-fpm –with-fpm-user=www –with-fpm-group=www –with-libevent-dir=libevent_path
# 還有個(gè)變化是,必須通過(guò)信號(hào)管理fpm
# SIGINT, SIGTERM 立刻終止
# SIGQUIT 平滑終止
# SIGUSR1 重新打開(kāi)日志文件
# 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è)問(wèn)題就是,若是索引數(shù)組,則索引亂序,可以直接使用array_keys
$array = array_flip($array);
$array = array_keys($array);
輸出所有已定義的常量
print_r(get_defined_constants());
curl
# 以前我們通過(guò) PHP 的 cURL 上傳文件是,是使用“@+文件全路徑”的來(lái)實(shí)現(xiàn)的:
curl_setopt(ch, CURLOPT_POSTFIELDS, array(
'file' => '@'.realpath('image.png'),
));
# PHP 從 5.5 開(kāi)始引入了新的 CURLFile 類(lèi)用來(lái)指向文件,CURLFile 類(lèi)也可以詳細(xì)定義 MIME 類(lèi)型、文件名等可能出現(xiàn)在multipart/form-data 數(shù)據(jù)中的附加信息,PHP 推薦使用 CURLFile 替代舊的@語(yǔ)法
# 而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跳過(guò)無(wú)法編碼
$T = iconv("utf8","big5//ignore", $T);
$T = mb_convert_encoding($T, "big5", "utf8");
令人迷惑
PHP由于初期的野蠻生長(zhǎng),即便進(jìn)入PHP7時(shí)代,語(yǔ)言中依然保留了大量令人迷惑的地方:
- php://output和php://stdout的區(qū)別
- 升PHP7后isset不太對(duì)了
- isset和is_null區(qū)別
- 0.58 * 100 == 57?
- utf8中文截?cái)嘣硪约皃hp的實(shí)現(xiàn)
面試常見(jiàn)
有些知識(shí)在實(shí)際開(kāi)發(fā)中很少遇到,或者遇到就要把始作俑者叉出去,但在面試中卻喜聞樂(lè)見(jiàn):