PHP 语言指引
语法特性
命名空间
无命名空间的代码都属于全局命名空间。
<?php
# 声明
namespace online\moha; # 声明命名空间需要在 <?php 后第一行开始。
# 导入命名空间下的类,避免重复输入全名
use Symfony\Component\HttpFoundation\Response;
# 导入并创建别名
use Symfony\Component\HttpFoundation\Response as Res;
# 多重导入
use Symfony\Component\HttpFoundation\Request,
Symfony\Component\HttpFoundation\Cookie;
# 导入函数 PHP 5.6+
use func online\moha\common\moha_start_with;
# 导入常量 PHP 5.6+
use constant online\moha\common\SECONDS_IN_YEAR;
<?php
# 一个 PHP 文件,多个命名空间
namespace online\moha\wx {
}
namespace online\moha\qr {
}
<?php
namespace online\moha;
# 没有指明命名空间的代码在全局命名空间中。
# 在命名空间下引用全局命名空间下的代码,需要用 \ 限定,否则非限定类 Exception 将被解释为 online\moha\Exception。
throw new \Exception();
接口
使用接口抽象处理流程,使代码依赖抽象定义,而非具体对象,方便扩展。
性状
PHP 5.4 开始支持,用于将共通代码注入多个无关的类以实现代码复用。
<?php
# 定义性状
trait TRAIT_NAME {
public function FUNCTION_NAME() {
}
}
# 使用性状,PHP 运行时会复制性状代码到类中。
class CLASS_NAME {
use TRAIT_NAME;
}
$obj = new CLASS_NAME();
$obj->FUNCTION_NAME();
生成器
PHP 5.5 开始支持。返回一系列值/内容,并配合循环使用的函数。返回内容依次通过关键字 yield 向外传递给调用的循环,省去中间变量的内存开销。
<?php
function getRows($file) {
$handle = fopen($file, 'rb');
if ($handle === false) { throw new Exception(); }
while (feof($handle) === false) {
// 文件内容不会全部加载到内存再返回。
// yield 会暂停后续处理,直至外部循环执行 1 次。
yield fgetcsv($handle);
}
fclose($handle);
}
foreach (getRows('data.csv') as $row) {
print_r($row);
}
闭包(匿名函数)
PHP 5.3 开始支持。用关键字 use 传入调用环境的变量,以对象对待,常做为回调函数使用。
array_map(function($value) use ($env){
return $value + $env;
}, [2,3,4]);
PSR
日期
PHP 5.2 后可使用 DateTimeZone、DateTime 和 DateInternal 类处理日期。
时区
PHP 可以设置默认时区,也可以生成 DateTimeZone 对象,在日期时间相关操作中被引用:
; php.ini 设置默认时区
date.timezone = 'Asia/Shanghai';
# 运行时改变默认时区
date_default_timezone_set('UTC');
# 生成时区对象
new DateTimeZone('Asia/Tokyo');
读取
读取日期字符串,使用不同方法可生成 DateTime 对象:
计算
DateTime 对象的 add、sub 方法接收 DateInterval 对象进行日期时间计算:
DateTime 对象的 setTimezone 方法接收 DateTimeZone 对象调整时区供后续处理或显示。
DatePeriod 类可以根据 DateTime 实例及 DateInterval 实例返回指定个数 DateTime 实例。
$datetime = new DateTime();
$interval = new DateInterval('P1W'); # 间隔 1 周
$period = new DatePeriod($datetime, $interval, 5);
foreach ($period as $next) {
echo $next->format('Y-m-d H:i:s'), PHP_EOL;
}
数据库
PHP 常用统一接口 PDO(PHP Data Object)和不同数据库交互。PDO 需用数据库对应的连接字符串(Data Source Name)初始化。
用户输入需通过 PDO 的 prepare 方法返回的预处理对象和数据库交互。输入值可通过预处理对象的 bindValue 绑定到 :占位符。
MySQL
# 根据最佳实践,这些配置需从外部文件导入
$dsn = 'mysql:host=localhost;dbname=testdb';
$username = 'username';
$password = 'password';
$options = array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
);
try {
$pdo = new PDO($dsn, $username, $password, $options);
$sql = 'SELECT id FROM users WHERE email = :email';
$statement = $pdo->prepare($sql);
$email = filter_value(INPUT_GET, 'email');
$statement->bindValue(':email', $email, PDO::PARAM_STR);
$statement->execute();
while (($result = $statement->fetch(PDO::FETCH_ASSOC)) !== false) {
// $result['id']
}
}
catch (PDOException $e) {
}
多字节
PHP 处理多字节字符串需使用 mbstring 扩展提供的函数:
配置
; php.ini 设置默认字符编码
default_charset = "UTF-8";
流
流被用来读写不同的资源或内容。流标识符 <schema>://<target> 标识流的协议和目标。流协议通过函数 stream_wrapper_register() 可自定义。
流过滤器
流在读写过程中可以借助流过滤器对内容进行改变。过滤器可以通过函数 stream_filter_append() 添加到某个打开流;也可以在流标识符里直接添加。通过继承 php_user_filter 类,调用函数 stream_filter_register() 可添加自定义流过滤器。
错误
调用会触发错误的函数前加 @ 符号可忽略错误。
相关配置
开发/测试环境
; 错误显示
display_startup_errors = On
display_errors = On
; 报告错误
error_reporting = E_ALL
; 记录错误
log_errors = On
生产环境
; 错误显示
display_startup_errors = Off
display_errors = Off
; 报告错误
error_reporting = E_ALL & ~E_NOTICE
; 记录错误
log_errors = On
自定义错误处理
PHP 可如下自定义错误处理,自己是现实注意 die() 或 exit() 函数的调用,否则 PHP 继续执行后续处理:
// 原生函数实现
set_error_handler(function ($errno, $errstr, $errfile, $errline, $errcontext){
}, E_ALL | E_STRICT);
restore_error_handler();
# 第三方库 filp/whoops
$whoops = new \Whoops\Run;
$whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler);
$whoops->register();
异常
try {
}
catch (Exception $e) {
}
finally {
// 始终执行
}
PHP 允许自定义全局异常处理函数,处理未捕捉的异常:
set_exception_handler(function (Exception $e) {
});
// 还原之前异常处理
restore_exception_handler();
安全
转义输出
数据在展示前需要过滤攻击代码,可以用原生代码或 PHP Purifier 库实现;不建议使用正则表达式函数:常用模版引擎,比如:twig、smarty 会自动转义输出:
# 转义 HTML 代码
$input = htmlentities($input, ENT_QUOTES, 'UTF-8');
过滤输入
用户输入的数据需要过滤及验证,可以用原生代码或 Aura、Symfony 等库。根据获取的数据操作数据库前要过滤攻击代码,可以借助 PDO 预处理实现。
# 过滤字符串: 只保留电子邮件有效字符
$mail = filter_var($mail, FILTER_SANITIZE_EMAIL);
# 验证字符串:是否为电子邮件
$isMail = filter_var($mail, FILTER_VALIDATE_EMAIL);
# 过滤输入:是否为安全字符串
$isValidPassword = filter_input(INPUT_POST, 'password');
密码
用户密码仅存哈希值,登录验证是否需要重新生成,推荐 bcrypt 算法。PHP 5.5- 版本需要借助 password_compat 库。
# 静态比较哈希值
$hash = password_hash($password, PASSWORD_DEFAULT, ['salt' => 'FIXED_SALT_FIXED_SALT_FIXED_SALT']);
# 动态哈希值匹配
$hash = password_hash($password, PASSWORD_DEFAULT, ['cost' => 12]);
$isMatch = password_verify($password, $hash);
# 存储的哈希值是否需要重新生成
$needRegenerate = password_needs_rehash($hash, PASSWORD_DEFAULT, ['cost' => 20]);
巡查/诊断
内存
# 诊断最大使用内存
echo date('H:i:s') , " Peak memory usage: " , (memory_get_peak_usage(true) / 1024 / 1024) , " MB" , PHP_EOL;
执行时间
# 运行时调整最大执行时间限制
set_time_limit();