PHP 错误与异常处理:从 die 到优雅的错误处理 PHP
前言
很多 PHP 新手处理错误就是 die() 或者 exit(),页面直接白屏,用户体验极差。本文讲清楚 PHP 的错误和异常处理机制,帮你写出更健壮的代码。
一、错误 vs 异常
很多人搞不清错误和异常的区别:
- 错误:PHP 本身的问题,比如语法错误、调用不存在的函数
- 异常:程序逻辑上的问题,比如数据库连不上、文件不存在
PHP 7 以后,很多错误也变成了异常,可以用 try-catch 捕获。
二、错误级别
PHP 有很多错误级别:
E_ERROR 致命错误,脚本停止运行
E_WARNING 警告,脚本继续运行
E_NOTICE 注意,一般不影响运行
E_PARSE 语法解析错误
E_DEPRECATED 过时提醒
开发环境和生产环境的配置不一样:
# 开发环境:显示所有错误
display_errors = On
error_reporting = E_ALL
# 生产环境:不显示错误,只记录日志
display_errors = Off
log_errors = On
error_reporting = E_ALL
三、异常处理基础
try-catch-finally
<?php
try {
// 可能出错的代码
$db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
} catch (PDOException $e) {
// 捕获异常
echo "数据库连接失败:" . $e->getMessage();
} finally {
// 不管有没有异常都会执行
echo "执行完毕";
}
抛出异常
<?php
function divide($a, $b)
{
if ($b == 0) {
throw new Exception("除数不能为 0");
}
return $a / $b;
}
try {
echo divide(10, 0);
} catch (Exception $e) {
echo "错误:" . $e->getMessage();
}
四、自定义异常类
<?php
class DbException extends Exception
{
public function errorMessage()
{
return "数据库错误:" . $this->getMessage();
}
}
class UserException extends Exception
{
public function errorMessage()
{
return "用户错误:" . $this->getMessage();
}
}
// 使用
try {
if (!$user) {
throw new UserException("用户不存在");
}
} catch (UserException $e) {
echo $e->errorMessage();
} catch (DbException $e) {
echo $e->errorMessage();
} catch (Exception $e) {
echo "其他错误:" . $e->getMessage();
}
五、错误处理器
把 PHP 错误转换成异常,统一处理:
<?php
// 自定义错误处理器
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
// 把错误转换成异常抛出
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});
try {
// 这个本来是 warning,现在会被捕获
$result = 10 / 0;
} catch (ErrorException $e) {
echo "错误:" . $e->getMessage();
}
六、异常处理器
兜底处理,最后一道防线:
<?php
// 自定义异常处理器
set_exception_handler(function ($e) {
echo "未捕获的异常:" . $e->getMessage();
// 记录日志
error_log($e->getMessage());
});
// 没有 try-catch 的异常会走这里
throw new Exception("爆炸了");
七、常见错误处理方式对比
| 方式 | 问题 |
|---|---|
die() / exit() |
页面白屏,用户体验差 |
echo 错误信息 |
代码混在一起,不好维护 |
| 错误码返回 | 调用方需要判断错误码 |
| 异常处理 | 代码清晰,错误统一管理 |
八、最佳实践
- 生产环境不要显示错误:会暴露服务器信息
- 一定要记录日志:方便排查问题
- 用异常处理替代 die():代码更优雅
- 自定义异常类:区分不同类型的错误
- 兜底异常处理器:防止未捕获的异常白屏
九、日志记录
<?php
try {
// 可能出错的代码
} catch (Exception $e) {
// 记录日志
error_log(
date('Y-m-d H:i:s') . " 错误:" . $e->getMessage() . "\n",
3,
'/var/log/php/error.log'
);
// 给用户友好提示
echo "系统繁忙,请稍后再试";
}
总结
PHP 错误处理的核心思路:
- 开发环境显示错误,生产环境隐藏错误
- 用 try-catch 捕获异常,不要用 die()
- 自定义异常类,区分不同错误类型
- 设置错误处理器,把错误转成异常
- 一定要记录日志,方便排查问题
掌握这些,你的 PHP 代码会健壮很多。
PHP 设计模式:单例、工厂、观察者实战详解 PHP
前言
设计模式不是八股文,它是前人踩坑踩出来的最佳实践。PHP 开发中最常用的三个设计模式:单例、工厂、观察者。本文用最直白的方式讲清楚。
一、单例模式(Singleton)
什么时候用
全局只需要一个实例的对象,比如:
- 数据库连接
- 配置管理
- 日志对象
代码实现
<?php
class Database
{
private static $instance = null;
private $conn;
// 构造函数私有化,防止外部 new
private function __construct()
{
$this->conn = mysqli_connect('localhost', 'user', 'pass', 'db');
}
// 禁止克隆
private function __clone() {}
// 禁止反序列化
public function __wakeup()
{
throw new Exception("不能反序列化单例");
}
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function getConnection()
{
return $this->conn;
}
}
// 使用
$db = Database::getInstance();
$conn = $db->getConnection();
为什么要私有化构造函数
如果构造函数是 public 的,外面就能 new Database(),那就不是单例了。
二、工厂模式(Factory)
什么时候用
需要根据不同条件创建不同对象的时候,比如:
- 根据支付类型创建不同的支付对象(微信、支付宝、银行卡)
- 根据数据库类型创建不同的数据库对象(MySQL、Redis)
简单工厂
<?php
interface Payment
{
public function pay($amount);
}
class WechatPay implements Payment
{
public function pay($amount)
{
echo "微信支付 {$amount} 元";
}
}
class Alipay implements Payment
{
public function pay($amount)
{
echo "支付宝支付 {$amount} 元";
}
}
class PaymentFactory
{
public static function make($type)
{
switch ($type) {
case 'wechat':
return new WechatPay();
case 'alipay':
return new Alipay();
default:
throw new Exception("不支持的支付方式");
}
}
}
// 使用
$pay = PaymentFactory::make('wechat');
$pay->pay(100);
工厂模式的好处
- 解耦:客户端不需要知道具体怎么创建对象
- 易扩展:新增支付方式只需要加一个类和工厂里的一个 case
- 统一管理:所有对象的创建都在工厂里,方便维护
三、观察者模式(Observer)
什么时候用
一个对象状态改变时,需要通知多个其他对象,比如:
- 用户注册后:发邮件、发短信、送积分
- 订单状态变更后:通知仓库、通知物流、通知客服
代码实现
<?php
// 事件接口
interface Observer
{
public function update($eventData);
}
// 事件主体
class Event
{
private $observers = [];
public function attach(Observer $observer)
{
$this->observers[] = $observer;
}
public function trigger($eventData)
{
foreach ($this->observers as $observer) {
$observer->update($eventData);
}
}
}
// 具体观察者
class SendEmail implements Observer
{
public function update($eventData)
{
echo "发送邮件给 {$eventData['email']}\n";
}
}
class SendSms implements Observer
{
public function update($eventData)
{
echo "发送短信给 {$eventData['phone']}\n";
}
}
class GivePoints implements Observer
{
public function update($eventData)
{
echo "给用户送 100 积分\n";
}
}
// 使用
$event = new Event();
$event->attach(new SendEmail());
$event->attach(new SendSms());
$event->attach(new GivePoints());
// 用户注册,触发事件
$event->trigger([
'email' => 'user@example.com',
'phone' => '13800138000'
]);
观察者模式的好处
- 解耦:事件主体不需要知道谁在监听
- 易扩展:新增监听者只需要加一个类,不需要改原来的代码
- 符合开闭原则:对扩展开放,对修改关闭
四、什么时候用什么模式
| 场景 | 用什么模式 |
|---|---|
| 全局只需要一个实例 | 单例 |
| 根据条件创建不同对象 | 工厂 |
| 一个动作触发多个后续操作 | 观察者 |
五、常见坑
- 单例用太多:单例用多了会变成全局变量,代码难维护
- 工厂过度设计:简单逻辑就别硬上工厂,会增加复杂度
- 观察者滥用:所有操作都做成观察者,调试会很痛苦
总结
设计模式是工具,不是目的。
- 单例:保证全局只有一个实例
- 工厂:统一管理对象创建
- 观察者:一个事件,多个响应
用对了让代码更清晰,用错了让代码更复杂。记住:先解决问题,再考虑模式。
PHP 性能优化:从代码到部署的完整指南 PHP
前言
PHP 性能优化是每个 PHP 开发者都绕不开的话题。很多人觉得 PHP 性能差,但实际上通过合理的优化,PHP 应用完全可以达到很高的性能水平。本文从代码层面到部署层面,带你完整梳理 PHP 性能优化的思路。
一、代码层面优化
1. 避免在循环中做数据库查询
// 慢:循环中查询数据库
foreach ($users as $user) {
$order = $db->query("SELECT * FROM orders WHERE user_id = {$user['id']}");
}
// 快:一次性查询,然后用数组映射
$userIds = array_column($users, 'id');
$orders = $db->query("SELECT * FROM orders WHERE user_id IN (" . implode(',', $userIds) . ")")->fetchAll();
$orderMap = array_column($orders, null, 'user_id');
2. 用生成器处理大数据
// 慢:一次性加载所有数据到内存
$users = $db->query("SELECT * FROM big_table")->fetchAll();
// 快:用生成器逐行处理
function getUsers() {
$stmt = $pdo->query("SELECT * FROM big_table");
while ($row = $stmt->fetch()) {
yield $row;
}
}
3. 字符串拼接 vs implode
// 慢:频繁拼接字符串
$str = '';
foreach ($items as $item) {
$str .= $item . ',';
}
// 快:用数组收集最后拼接
$parts = [];
foreach ($items as $item) {
$parts[] = $item;
}
$str = implode(',', $parts);
二、PHP 配置优化
1. OPcache 必开
OPcache 是 PHP 性能提升最大的一个扩展,没有之一。
; php.ini
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
开启 OPcache 后,PHP 脚本会被编译成字节码缓存起来,避免每次请求都重新编译。性能提升通常在 2-5 倍。
2. 合理设置内存限制
memory_limit = 256M
不要设置太大,也不要太小。根据你的应用实际情况调整。
三、数据库优化
1. 开启慢查询日志
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
找出慢查询,然后针对性优化。
2. 使用持久化连接要谨慎
// 不一定更快,可能反而更慢
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass', [
PDO::ATTR_PERSISTENT => true
]);
持久化连接在高并发场景下可能导致连接耗尽,要根据实际情况测试。
四、部署层面优化
1. 使用 PHP-FPM 优化
; php-fpm/www.conf
pm = dynamic
pm.max_children = 50
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30
根据服务器内存调整 max_children,每个 PHP-FPM 进程大约占用 20-40MB 内存。
2. Nginx 开启 gzip
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1000;
3. 静态文件 CDN 加速
把图片、CSS、JS 等静态资源放到 CDN 上,减轻服务器压力。
五、性能监控工具
- Xdebug:代码性能分析,找出瓶颈函数
- Blackfire:更专业的性能分析工具
- New Relic:APM 应用性能监控
- ab / wrk:压力测试工具
总结
PHP 性能优化的核心思路:
- 先找出瓶颈在哪里,不要盲目优化
- 代码层面:减少数据库查询、减少内存占用
- 配置层面:OPcache 必开
- 部署层面:PHP-FPM、Nginx、数据库都要调优
- 用工具量化优化效果
性能优化是一个持续迭代的过程,不要指望一次性优化到位。每次做一点,慢慢积累。
PHP 8.3 新特性详解:类型类获取、动态常量获取和更多实用功能 PHP
前言
PHP 8.3 于 2023 年 11 月正式发布,作为 PHP 8.x 系列的第四个版本,它带来了一系列实用的语法增强和性能优化。本文将深入介绍 PHP 8.3 中最值得关注的几个新特性。
1. 类型类获取(Typed class constants)
在 PHP 8.3 之前,类常量无法声明类型。从 PHP 8.3 开始,类常量可以指定类型,这有助于提高代码的类型安全性。
class Foo
{
const string BAR = 'bar';
const int MAX_COUNT = 100;
}
声明了类型的常量必须与声明的类型匹配,否则会抛出 TypeError。支持的类型包括:标量类型、array、object、static、self、parent、类名/接口名以及 iterable。
2. 动态类常量获取
PHP 8.3 新增了 constant() 函数的增强,允许通过表达式动态获取类常量:
class Status
{
const DRAFT = 'draft';
const PUBLISHED = 'published';
const ARCHIVED = 'archived';
}
$status = 'PUBLISHED';
echo constant(Status::class . "::$status"); // 输出: published
这在需要根据变量动态选择常量时非常有用,避免了大量的 switch 或 match 分支。
3. #[\Override] 注解
PHP 8.3 引入了 #[\Override] 注解,用于显式标记一个方法是重写父类的方法:
abstract class Controller
{
abstract public function handle(): void;
}
class UserController extends Controller
{
#[\Override]
public function handle(): void
{
echo "Handling user request...";
}
}
如果使用了 #[\Override] 注解,但方法实际上并没有重写任何父类方法(比如方法名拼错了),PHP 会在编译时报错。这在大型项目中非常有价值,可以防止因为方法名不一致导致的隐蔽 bug。
4. Readonly 深拷贝
PHP 8.3 改进了 readonly 属性的处理。在 PHP 8.2 中,readonly 属性不能通过克隆来修改。PHP 8.3 允许在克隆时修改 readonly 属性:
class Point
{
public function __construct(
public readonly int $x,
public readonly int $y
) {}
}
$p1 = new Point(10, 20);
$p2 = clone $p1;
$p2->x = 30; // PHP 8.3 中允许,PHP 8.2 会报错
这个特性在值对象模式中特别有用,可以创建修改后的副本而不影响原始对象。
5. 其他实用改进
json_validate() 函数
PHP 8.3 新增了 json_validate() 函数,用于验证 JSON 字符串是否有效,而不需要解码:
$json = '{"name": "Alice", "age": 30}';
if (json_validate($json)) {
echo "Valid JSON";
}
这比 json_decode() 更高效,因为它不需要分配内存来存储解码后的结果。
随机浮点数
PHP 8.3 的 Random\Randomizer 类新增了 getFloat() 方法:
$r = new Random\Randomizer();
$float = $r->getFloat(0, 10, \Random\IntervalBoundary::ClosedOpen);
总结
PHP 8.3 虽然不是一个大版本更新,但它带来的每一个新特性都直击开发者的日常痛点。类型类常量和 #[\Override] 注解增强了代码的可维护性,动态常量获取简化了代码逻辑,readonly 深拷贝则让值对象模式更加实用。
建议正在使用 PHP 8.x 的项目尽快升级到 8.3,既能获得这些新特性的好处,也能享受性能上的小幅提升。
命令行模式下 PHP Redis 为什么会提示 Class Redis not found? PHP
命令行模式下PHPRedis无法使用在命令行模式下运行PHP程序时,遇到ClassRedisnotfound...
如何将 WordPress 评论功能集成到自定义 PHP 页面? PHP
WordPress评论功能的提取提取WordPress评论功能并将其集成到自定义PHP...
如何使用 PHP 去除字符串中的 HTML 标记? PHP
PHP中清除字符串中的HTML标记对于以下需求:给定一个字符串$str='ttttgg....
为什么在 PHP OOP 中,使用 $this 关键字时,静态调用实例方法会出现 "Using $this when not in object context" 错误? PHP
PHPOOP$this问题在PHP中使用OOP时,有时可能会遇到涉及$this关键字的问题。问题:在以下代码中,执行...
PHP 中如何创建指定长度的数组? PHP
PHP中能否像C#一样定义指定长度的数组?在C#中,在初始化int类型数组时可以指定长度,并且默认值全部为...
PHP 中如何定义指定长度的数组? PHP
PHP定义指定长度数组PHP中的数组是一种动态数据结构,不需要指定固定的长度。与C#中使用int[]arr=new...
为什么 Go 语言底层实现解析资源丰富,而 PHP 却匮乏? PHP
PHP源码解析内容匮乏的原因探讨尽管Go语言的底层实现解析内容丰富,PHP...
PHP-WebDriver 如何获取渲染后的页面代码? PHP
如何在PHP-WebDriver中获取渲染后的页面代码问题:在使用PHP-WebDriver...
PHP 返回数组,如何动态输出到 HTML 的 ul 列表中? PHP
php返回数组,如何输出到html的ul当我们从php返回一个数组,可以使用jQuery的ajax函数将其发送到目标url,并通过da...
如何使用 jQuery 从 PHP 获取 MySQL 数据并显示为列表? PHP
从PHP获得MySQL数据这个示例使用$.ajax()函数来向名为test1.php的服务器端脚本发送POST请求,该脚本获取...
如何在PHP中为特定位置的日期字符串插入空格? PHP
PHP字符串空格问题:为特定位置插入空格用户需要将日期格式字符串中的某些位置添加空格。具体地说,用户希...