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 协议》,转载必须注明作者和本文链接
推荐文章: