PHP最佳实践
前言
密码存储
使用内置的哈希函数进行加密和比较
随着计算机算力的增加,md5甚至是sha1已经不再安全,黑客可以轻松破解大部分md5/sha1生成的密码
<?php
# 使用 bcrypt 算法,返回60个字符的哈希值:$2y$10$tiHudceUYpxWK56MqVGQuOXTZ.fCmkLYcX3dAHg/KXjXjg2tUzzci
$hashedPassword = password_hash('123456', PASSWORD_DEFAULT);
# 验证错误返回false
password_verify('1234567', $hashedPassword);
# 验证正确返回true
password_verify('123456', $hashedPassword);
?>
password_hash会自动为密码加salt,官方也不建议使用自定义的salt
连接和查询 MySQL 数据库
PDO(PHP Data Ojects),PDO 在许多不同类型的数据库中具有一致的接口,面向对象,并支持新数据库提供的更多功能,同时也可以防止SQL注入攻击
$link = new PDO('mysql:host=your-hostname;dbname=your-db;charset=utf8mb4',
'your-username',
'your-password',
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_PERSISTENT => false
]
);
$handle = $link->prepare('select Username from Users where UserId = ? or Username = ? limit ?');
$handle->bindValue(1, 100);
$handle->bindValue(2, 'Bilbo Baggins');
$handle->bindValue(3, 5);
$handle->execute();
标签
PHP支持标签:
<?php ?>, <?= ?>, <? ?>,<% %>
使用短标签需要修改php.ini配置short_open_tag=on,默认是off,如果你无办法改变全部环境的配置档,建议还是使用<?php ?>,另外<?= ?>不受short_open_tag影响
自动加载
__autoload()
是PHP 5引入的,spl_autoload_register()
是PHP 5.1.2引入的。spl_autoload_register()
是__autoload()
的替代和改进。您一次只能定义一个 __autoload()
函数,因此如果您包含一个也使用 __autoload()
函数的库,那么就会发生冲突。正确方法是给你的自动加载函数起一个独特的名字,然后用 spl_autoload_register() 函数注册它。
function myLoader($class_name)
{
require $class_name . '.php';
}
spl_autoload_register('myLoader');
单引号 vs 双引号
对于一个普通的应用程序,你选择哪个并不重要。对于负载极高的应用程序,也只是有一点影响。
常量 define() vs const
除非考虑可读性、类常量或微优化,否则使用 define(),因为 define() 最终更灵活, 除非你特别需要类常量。
- define() 在运行时定义常量,而 const 在编译时定义常量。
- define() 允许您在常量名称和常量值中都使用表达式,const不允许
- define() 可以在 if() 块中调用,而 const 不能
- define() 不能定义类常量。
const GIMLI_ID = 1; // 成功 define('TRANSPORT_METHOD_SNEAKING', 1 << 0); // 成功 const TRANSPORT_METHOD_WALKING = 1 << 1; // 编译失败,const不能使用表达式 if (1) { define('TRANSPORT_METHOD', TRANSPORT_METHOD_SNEAKING); // 成功 const PARTY_LEADER_ID = 23; // 编译失败,const不能写在if块里 } class OneRing { const MELTING_POINT_CELSIUS = 1000000; // 成功 define('MELTING_POINT_ELVISH_DEGREES', 200); // define() 不能定义类常量 }
缓存PHP opcode
在旧版本的 PHP 中,每次执行脚本时都必须从头开始编译。 Opcode 可以保存以前编译的 PHP 版本,从而加快速度。您可以选择各种风格的缓存。
PHP 和 Memcached
如果你需要一个分布式的缓存,使用Memcached(现在一般用redis),单机且内存足够的情况下使用APCu,APCu原理是操作系统的进程间共享内存,只适合php-fpm模式的进程组,因为php-fpm模式都有一个共同的父进程,cli模式每次都是单独启动一个进程,所以不适合
- Memcache
您有两种不同且命名非常愚蠢的客户端库选择:Memcache 和 Memcached,事实证明 Memcached 更好用sudo apt-get install php-memcached
- APCu
在 Ubuntu 14.04 之前,APC 项目既是一个opcache,也是一个类似 Memcached 的键值存储。 Ubuntu 14.04 之后opcache独立出来,现在只剩下键值存储功能,需要安装apcu扩展sudo apt-get install php-apcu
其实想想,那么多年都没用到过这个东西是有道理的,生产环境不建议使用这个APCuapcu_store('username-6389', 'Gandalf'); //string apcu_store('creatures', array('ent', 'dwarf', 'elf')); //数组 apcu_store('saruman', new Wizard()); //对象 apcu_fetch('username-958', $success); //查询 apcu_delete('username-958'); //删除
正则表达式
在 PHP 7 出现之前, PHP 有两种使用正则表达式的不同方式:preg_* 函数和 ereg_* 函数。 ereg_* 函数已在 PHP 7 中被删除,无论什么版本首选preg_*
$string = 'April 15, 2003';
$pattern = '/(\w+) (\d+), (\d+)/i';
$replacement = '${1}1,$3';
echo preg_replace($pattern, $replacement, $string);
为Web 服务器提供 PHP
apache 的 mod_php早已过时,首选nginx php-fpm
发送邮件
PHP 内置 mail 函数看起来挺好用,实际非常不好用,无法设置合适的header被认为是spam导致投递失败,无法添加附件,无法获得发送结果等,所以不推荐使用
PHPMailer 是一个流行且成熟的开源库
# 安装
composer require phpmailer/phpmailer
以下代码亲测可用
<?php
use PHPMailer\PHPMailer\PHPMailer;
require "../vendor/autoload.php";
$mailer = new PHPMailer(true);
# 发件人
$mailer->Sender = 'no-reply@metaprisebanking.com';
# 抄送?
//$mailer->AddReplyTo('bbaggins@example.com', 'Bilbo Baggins');
# 发件人
$mailer->SetFrom('no-reply@metaprisebanking.com', 'Bilbo Baggins');
# 收件人
$mailer->AddAddress('kun@qq.com');
# 标题
$mailer->Subject = "Let's dance";
# 内容
$mailer->MsgHTML('<p>You are really nice</p>');
$mailer->IsSMTP();
$mailer->SMTPAuth = true;
# @todo 抽空了解一下email协议
//$mailer->SMTPSecure = 'ssl';
$mailer->Port = 587;
$mailer->Host = 'email-smtp.us-west-2.amazonaws.com';
$mailer->Username = 'username';
$mailer->Password = 'password';
$mailer->Send();
验证邮箱地址
使用内置函数 filter_var()
filter_var('test@example.com', FILTER_VALIDATE_EMAIL); // 返回 test@example.com
filter_var('sauron@mordor', FILTER_VALIDATE_EMAIL); // 返回 false
还可以验证 IP , URL , 整形, 使用回调函数验证等
清理 HTML 输入与输出
不要信任任何来自不受自己控制的数据源中的数据,例如以下这些
$_GET
$_POST
$_REQUEST
$_COOKIE
$argv
php://stdin
php://input
file_get_contents()
远程数据库
远程API
来自客户端的数据
如果输入以下内容,渲染页面的时候不过滤,直接跳转第三方网站
<p>可以可以,一键三连了![狗头]</p>
<script>windows.location.href="https://www.threebody.com";</script>
一般渲染的时候,使用htmlentities
函数过滤以下就可以了
当用户输入富文本的时候,比如图片、链接这些的时候,因为htmlentities
没有验证html的功能,使用HTML Purifier
组件实现更专业地,更定制化过滤
在过滤html这方面,用上面两个就可以了,不考虑使用htmlspecialchars
,strip_tags
,filter_var
等函数
PHP和UTF-8
UTF-8 in PHP sucks.
处理多字节字符串时需要用到mb_*函数,需要安装扩展
sudo apt install php-mbstring
处理utf8字符串时必须用mb_*函数
# 告诉 PHP 我们使用 UTF-8 字符串直到脚本结束
mb_internal_encoding('UTF-8');
# 告诉 PHP 我们将向浏览器输出 UTF-8
mb_http_output('UTF-8');
# 使用mb_substr截取utf8字符串
$string = 'Êl síla erin lû e-govaned vîn.';
$string = mb_substr($string, 0, 15);
时间管理大师
以前处理时间只能使用下面这些函数
date()
gmdate()
date_timezone_set()
strtotime()
现在我们用 DateTime 类
在 32 位系统DateTime::getTimestamp() 将不会表示超过 2038 年的日期。64 位系统可以。
# 构造一个新的 UTC 日期。除非您真的知道自己在做什么,否则请始终指定 UTC!
$date = new DateTime('2011-05-04 05:00:00', new DateTimeZone('UTC'));
# 将我们的初始日期增加十天
$date->add(new DateInterval('P10D'));
# 格式化输出日期
print($date->format('Y-m-d h:i:s')); // 2011-05-14 05:00:00
# 设置时区为美国西部时间
$date->setTimezone(new DateTimeZone('America/Los_Angeles'));
# 打印时间戳
$date->getTimestamp();
# 构造一个新的 UTC 日期
$later = new DateTime('2012-05-20', new DateTimeZone('UTC'));
# 直接比较
if($date < $later){
print('嗯,确实能比较!');
}
# 比较日期差异
$difference = $date->diff($later);
# 打印差异多少天
print('两个时间相隔' . $difference->days . '天!'); // 两个时间相隔371天!
$datetime = new DateTime();
$interval = new DateInterval('P2D');
# 返回一个迭代器 参数分别是:开始事件,间隔,迭代次数,排除开始的时间,从下一个周期开始
$period = new DatePeriod($datetime, $interval, 3, DatePeriod::EXCLUDE_START_DATE);
foreach ($period as $date) {
echo $date->format('Y-m-d H:i:s'), PHP_EOL;
}
如果你觉得内置类还是不好用,建议使用这个组件,更多处理时间的函数,更全面
composer require nesbot/carbon
检查一个变量是否为 null 或 false
使用 === , 它也比 is_null() 和 is_bool() 稍微快一点,而且看起来比使用函数进行比较要好,特别是使用strpos函数时一定要使用===
- ==
如果值是空字符串或 0,则使用 == 检查值是否为 null 或 false 可能会返回误报。 - isset()
检查变量是否具有不为 null 的值,但不检查布尔值 false - is_null()
只能准确地检查一个值是否为 null - is_bool()
只能准确地检查一个值是否为布尔值
移除重音符号
这个国外用得比较多,使用php-intl扩展替换iconv函数
$transliterator = Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: NFC;', Transliterator::FORWARD);
print($transliterator->transliterate('Êl síla erin lû e-govaned vîn.'));
?>
代码风格指南
PHP-FIG 已经提出并批准了一系列风格建议。并不是所有的都与代码风格有关,但是确实与代码风格有关的是 PSR-1, PSR-12 and PSR-4。
你可以使用以下工具之一自动修复代码布局:
- PHP Coding Standards Fixer
- PHP Code Beautifier and Fixer 可
phpcs -sw --standard=PSR1 file.php
phpcbf -w --standard=PSR4 file.php
php-cs-fixer fix -v --rules=@PSR1 file.php
代码可读性
github.com/php-cpm/clean-code-php
错误与异常
线程的框架CURD的时候一般只需要捕获异常即可,特别是那些需要建立连接的代码需要捕获异常,包括不限于(curl,第三方软件sdk,数据库,composer组件),只有在自己写框架,composer组件,一些公用类,公用方法的时候需要定义异常,抛出异常
日志级别
时间管理
单元测试
Opcache & JIT
持续更新 不好意思没写完 一直都在草稿不小心点了发布
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐看 PHP: The Right Way