Modern PHP 笔记(二):良好实践
系列笔记:
Modern PHP 笔记(一):语言特性
Modern PHP 笔记(二):良好实践
Modern PHP 笔记(三):部署测试和调优
相比于上一篇笔记Modern PHP 笔记(一):语言特性,第二部分侧重讲标准和良好实践,更加实战。
第三章:标准
PHP-FIG(PHP Framework Interop Group)2009年
框架的互操作性
通过接口、自动加载、代码风格,使框架相互合作
PSR标准
PSR: PHP Standard Recommendation(PHP推荐标准)
可以看点击链接看详细的中文版。很多方案在起草中,也有废弃的,比如PSR-4就是替代PSR-0的
第四章:组件
多用组件,从框架的封闭生态中走出来,减少重复造轮子。
组件是什么?
打包的代码,一系列相关的类、接口、性状
组件与框架对比
框架(尤其是较旧的框架)的问题是:需要更多投入,框架提供很多工具,但是可能缺少我们需要的。
把第三方代码集成上去费时费力。框架本身的维护也需要很多人力。
使用正确的工具做正确的事
- 如果是一些PHP组件就能搞定的小项目,用组件。
- 多个团队成员开发,用框架。它能提供约定、准则、结构等,加速开发。
查找组件
组件库Packagist https://packagist.org/
优秀组件可参考Awesome PHP:https://github.com/ziadoz/awesome-php
使用组件
使用composer
下载并自动加载依赖组件
使用composer
composer require vendor/package
composer.lock
文件会列出使用的所有组件和具体版本号,确保组件版本一致。
composer install // 不会更新.lock
composer update // 更新.lock到最新版
自动加载
只需要一句,不用一一引入
require 'vendor/autoload.php'
composer和私有仓库
不方便开源的组件也可以使用composer
创建PHP组件
-
取一个厂商名和包名
-
定一个命名空间
-
文件系统
src/ 源码 tests/ 测试 composer.json composer的配置文件,描述组件、依赖等 README.md CONTRIBUTING.md 说明如何为这个组件做贡献 LISENSE 组件许可证 CHANGELOG.md 每个版本引入的改动
-
composer.json
require 列出需要的最小依赖 require-dev 列出开发组件需要的依赖
-
实现组件
-
发到版本控制
-
提交到 Packagist
-
使用组件 composer require
第五章:良好实践
过滤、验证和转义
不要相信任何人。
包括:
$_GET
$_POST
$_REQUEST
$_COOKIE
$argv
php://stdin
php://input
file_get_contents()
- 远程数据库
- 远程API
- 来自客户端的数据
过滤输入
转义或删除不安全的字符。
HTML
使用htmlentities()
函数,但是它默认不会转义单引号,正确使用方式:第一个参数是输入字符串,
第二额参数设为ENT_QUOTES
常量,转义单引号,第三个参数设为输入字符串的字符集。
$htmlentities($input, ENT_QUOTES, 'UTF-8');
如果需要更多过滤HTML方式,可以使用HTML Purifier库。
SQL查询
使用PDO预处理语句
用户资料信息
filter_var()
和filter_input()
函数
-
过滤电子邮件
filter_var($email, FILTER_SANITIZE_EMAIL);
-
过滤用户资料中的外国字, 删除小于ASCII 32的字符,转义大于ASCII 127的字符
filter_var( $string, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW|FILTER_FLAG_ENCODE_HIGH);
验证数据
区别于过滤数据会删除,验证不会,只是确保符合特定格式。
传递FILTER_VALIDATE_*
标志给filter_var()
函数。验证失败返回false
,否则返回原值。
//验证电子邮件
$input = 'john@example.com'
$isEmail = filter_var($input, FILTER_VALIDATE_EMAIL);
if ($isEmail !== false) {
echo 'success';
} else {
echo "fail";
}
推荐组件:
- aura/filter
- respect/validation
- symsony/validator
转义输出
很多模板引擎(e.g., twig、smarty)会自动转义,否则使用htmlentities($output, ENT_QUOTES, 'UTF-8');
密码
- 绝对不能知道用户的密码
- 绝对不能约束用户的密码
- 绝对不能通过电子邮件发送用户的密码
使用bcrypt计算用户密码的哈希值
应该用哈希,而不是加密。加密是双向算法,可以解密,而哈希是单向算法。
哈希算法很多,一般用bcrypt,与MD5和SHA1不同,bcrypt故意设计的很慢,多次处理数据(工作因子)。
密码哈希API
注册用户
// 验证电子邮件
// 验证密码
// 创建密码哈希值
// 创建用户账户
// 重定向登录账户
登录用户
// 从请求主体获取电子邮件地址
// 从请求主体获取密码
// 使用电子邮件查询账户
password_verify();
// 验证密码和账户哈希值是否匹配
password_needs_rehash();
// 如果需要,重新计算哈希值(最新的算法)
// 把登录状态保存在会话中
// 重定向到个人资料页
PHP5.5.0之前,使用组件ircmaxell/password-compat
日期、时间和时区
设置默认时区
// php.ini
date.timezone = 'America/New_York';
// *.php
date_default_timezone_set('America/New_York');
DateTime类
构造$datetime = new DateTime('2014-09-08 5:03 AM');
自定义格式DateTime::createFromFormat('M j, Y H:i:s', 'Jan 2, 2014 23:09:12')
DateInterval类
表示固定的时间段,DateInterval
用于修改DateTime
实例.
$datetime = new DateTime('2014-09-08 5:03 AM');
$interval = new DateInterval('P2W');
$datetime->add($interval);
echo $datetime->format('Y-m-d H:i:s');
DateTimeZone类
$timezone = new DateTimeZone('America/New_York');
$datetime = new DateTime('2014-09-08 5:03 AM', $timezone);
DatePeriod类
一段时期内反复出现的一系列日期和时间
$period = new DatePeriod($startDateTime, $interval, 3);
nesbot/carbon组件
数据库
PDO扩展,PHP Data Objects,PHP数据对象,是一系列类,抽象了不同数据库的具体实现。
这里实际上现在框架的ORM用的很好,不需要直接PDO了。
// 预处理语句
$sql = 'select id from user where email = :email';
$statement = $pdo->prepare($sql);
$email = filter_input(INPUT_GET, 'email');
$statement->bindValue(':email', $email);
// ':email'是占位符
// 查询结果
$statement->execute();
// 迭代结果
while (($result = $statement->fetch(PDO::FETCH_ASSOC)) !== false) {
echo $result['email'];
}
事务
$pdo->beginTransaction();
// sql
$pdo->commit();
多字节字符串
使用mbstring
扩展,比如用mb_strlen()
代替原生的strlen()
处理字符编码:
-
一定要知道数据的字符编码
-
使用utf-8存储数据
-
使用utf-8输出数据
// php.ini default_charset = 'UTF-8';
流
流的作用是在出发地和目的地之间传输数据。出发地和目的地可以是文件、命令行进程、网络连接、
ZIP或TAR压缩文件、临时内存、标准输入和输出、或者是PHP流封装协议实现的其他资源。就是管道。
流封装协议
流协议和目标格式:
<scheme>://<target>
下面例子里file_get_contents()
的字符串参数是流标识符,只是因为HTTP流协议就是这样,像URL
$json = file_get_contents(
'http://api.flickr.com....'
)
还有其他file://
流封装协议、php://
流封装协议
流上下文
有些流接受的可选参数,成为流上下文
使用stream_context_creat()
函数创建
流过滤器
流真正的强大之处:过滤、转换、添加、删除流中传输的数据。
有string.toupper
等,大多需要自定义stream_filter_append()
自定义流过滤器
自定义流过滤器是个PHP类,扩展内置的php_user_filter
类,实现一些方法,然后用
stream_filter_register()
注册。
错误和异常
错误会导致脚本停止,异常时PHP错误处理面向对象的产物,异常要先实例化,然后抛出,最后再捕获。
异常
异常是Exception
类的对象。
$exception = new Exception('msg', -1);
抛出异常throw new Exception('msg', -1);
,异常抛出后程序会终止。
捕获异常try {...} catch { }
异常处理程序
设置一个全局异常处理程序,捕获所有未捕获的异常。
set_exception_handler(function (Exception $e) {
// 处理并记录异常
});
// 编写其他代码
// 还原之前的异常处理程序
restore_exception_handler();
错误
在开发环境,显示并记录所有错误信息;
在生产环境,记录大多数错误信息,但不显示出来;
set_error_handler(); // 全局错误处理程序
restore_error_handler();
在开发环境中处理错误和异常
使用Whoops
组件
在生产环境处理错误和异常
使用Monolog
组件