PHP
基础
PHP 的生命周期/启动流程
完整的生命周期为
- 模块初始化
- 请求初始化
- 请求处理
- 请求关闭
- 模块关闭。
- cli 模式下,每个脚本都会完整的执行上面的五大阶段;
- 对于 fastcgi 模式而言,只在启动时会执行模块初始化,之后的请求都走了请求初始化、处理请求、请求关闭三大阶段,在 fastcgi 关闭时执行模块关闭阶段。各个扩展的加载也是在模块初始化阶段完成的。
PHP 8+新特性
- 联合类型:允许变量、参数或返回值声明多种可能类型(如string|int)。
- 命名参数:调用函数时可指定参数名,提升代码可读性。
- 属性(Attributes):替代PHPDoc注释的元数据声明方式。
- match表达式:更强大的switch替代方案,可直接返回值。
- nullsafe运算符:?->在链式调用中遇到null时停止执行而不报错。
- 构造函数属性提升:简化类属性声明和构造函数赋值。
- JIT编译器:显著提升计算密集型任务性能。
Opcache设置问题
- 缓存不更新:开发环境未关闭opcache.validate_timestamps,导致代码更改不生效。
- 内存不足:opcache.memory_consumption设置过小,导致缓存频繁失效。
- 脚本限制:opcache.max_accelerated_files不足,导致部分文件未被缓存。
- 权限问题:Opcache缓存目录不可写。
- 符号链接问题:使用符号链接时路径解析错误。
- 预加载冲突:opcache.preload配置错误,导致类加载问题。
- 与Xdebug冲突:同时启用可能导致性能下降或调试问题。
PHP 的(内存)垃圾回收机制
每一个变量对应一个 zval 数据结构,在该结构内还有一个 val 结构体,该结构体内有一个引用计数(php7 而言,对于 php5,这个引用计数是保存在 zval 结构中的),标识该对象的引用数,当对象的引用计数为 0 时代表这个对象可被回收。
对象的 refcount 减少的时机: 修改变量、函数返回(释放局部变量)、unset 变量
对于数组和对象而言,可能存在变量中的成员引用变量本身的情况,也就是循环引用,这样会造成这个变量永远不会被内存回收,而成为垃圾。
PHP 里对于这种情况给出了垃圾回收机制 如果数组、对象的引用计数减少而且不为零,则认为他们可能是垃圾,把他们放到垃圾收集器里。
等垃圾收集器到了一定的数量之后,进行垃圾处理:对所有可能的垃圾 refcount 减 1,如果为 1,说明是垃圾,则进行内存回收;如果不为 1,说明还有其他变量在使用,refcount 重新加 1;
这种对象复用以及垃圾回收机制在其他语言中也有体现:redis 中也使用了引用计数表示每个对象的引用数量。
如果我的网站用的utf-8编码,为防止乱码出现,都需要注意哪些地方?
从以下几个方面考虑:
- 数据库中库和表都用utf8编码php连接mysql,指定数据库编码为utf8 mysql_query("set names utf8");
- php文件指定头部编码为utf-8 header("content-type:text/html;charset=utf-8");
- 网站下所有文件的编码为utf8,html文件指定编码为utf-8;
什么是单点入口呢?
所谓单点入口就是整个应用程序只有一个入口,所有的实现都通过这个入口来转发。比如Thinkphp使用index.php作为程序的单点入口,当然这个是可以由你自己任意控制的。
作用:
- 定义框架路径、项目路径和项目名称(可选)
- 定义调试模式和运行模式的相关常量(可选)
- 载入框架入口文件(必须)
单点入口有几大好处:
- 一些系统全局处理的变量,类,方法都可以在这里进行处理。比如说你要对数据进行初步的过滤,你要模拟session处理,你要定义一些全局变量,甚至你要注册一些对象或者变量到注册器里面。
- 程序的架构更加清晰明了。
PHP 的控制反转
一种设计思想,用于降低代码之间的耦合度,增强代码的灵活性和可维护性。将组件间的依赖关系从程序内部提到外部来管理。在PHP中,控制反转通常通过依赖注入(Dependency Injection,DI)来实现。
- 构造器注入
- Setter方法注入
- 接口注入(最建议)
合并数组
功能 | array_merge | array_merge_recursive | array_combine | + |
---|---|---|---|---|
合并方式 | 合并数组,覆盖重复键名 | 递归合并数组,合并重复键名的值 | 组合两个数组,第一个数组的值作为键名 | 合并数组,保留第一个数组的重复键名 |
适用场景 | 一般数组合并 | 多维数组合并 | 创建键值对数组 | 保留第一个数组的键值 |
键名重复处理 | 覆盖 | 合并为数组 | 无重复键名,否则报错 | 保留第一个数组的值 |
常用的魔术方法有哪些?
这些方法在特定的情况下会被自动调用,从而提供一些强大的功能,例如重载、序列化、自动加载等。
- __construct() 实例化类时自动调用。
- __destruct() 类对象使用结束时自动调用。
- __set() 在给未定义的属性赋值的时候调用。
- __get() 调用未定义的属性时候调用。
- __isset() 使用isset()或empty()函数时候会调用。
- __unset() 使用unset()时候会调用。
- __sleep() 使用serialize序列化时候调用。
- __wakeup() 使用unserialize反序列化的时候调用。
- __call() 调用一个不存在的方法的时候调用。
- __callStatic()调用一个不存在的静态方法调用。
- __toString() 把对象转换成字符串的时候会调用。比如 echo。
- __invoke() 当尝试把对象当方法调用时调用。
- __set_state() 当使用var_export()函数时候调用。接受一个数组参数。
- __clone() 当使用clone复制一个对象时候调用。
$this和self、parent这三个关键词
分别代表什么?在哪些场合下使用?
- $this 当前对象:$this在当前类中使用,使用->调用属性和方法。
- self 当前类:self也在当前类中使用,不过需要使用::调用。
- parent 当前类的父类:parent在类中使用。
常量
类中如何定义常量、如何类中调用常量、如何在类外调用常量。
类中的常量也就是成员常量,常量就是不会改变的量,是一个恒值。 定义常量使用关键字const.例如:const PI = 3.1415326; 无论是类内还是类外,常量的访问和变量是不一样的,常量不需要实例化对象,访问常量的格式都是类名加作用域操作符号(双冒号)来调用。即:类名 :: 类常量名;
__autoload()方法的工作原理是什么?
使用这个魔术函数的基本条件是类文件的文件名要和类的名字保持一致。
- 当程序执行到实例化某个类的时候,如果在实例化前没有引入这个类文件,那么就自动执行__autoload()函数。
- 这个函数会根据实例化的类的名称来查找这个类文件的路径,当判断这个类文件路径下确实存在这个类文件后就执行include或者require来载入该类,然后程序继续执行,如果这个路径下不存在该文件时就提示错误。
使用自动载入的魔术函数可以不必要写很多个include或者require函数。
trait 和 interface
trait 特质是一种代码复用机制,允许在类中插入一段预定义的代码块。特质可以包含方法和属性。
trait Greeting {
public function sayHello() {
echo "Hello!\n";
}
}
class User {
use Greeting;
}
$user = new User();
$user->sayHello(); // 输出:Hello!
interface 接口中的方法不能有具体实现,只能定义方法的签名(方法名、参数和返回值类型)。
interface Animal {
public function makeSound(): string;
}
class Dog implements Animal {
public function makeSound(): string {
return "Bark";
}
}
class Cat implements Animal {
public function makeSound(): string {
return "Meow";
}
}
PHP超全局变量的列表
- $_GET:用于获取通过URL参数传递的GET请求数据。
- $_POST:用于获取通过表单提交的POST请求数据。
- $_REQUEST:包含通过GET、POST或COOKIE传递的请求数据。
- $_COOKIE:用于获取客户端发送的Cookie数据。
- $_SESSION:用于存储会话数据,这些数据在用户访问期间会持续存在。
- $_SERVER:包含服务器环境信息,例如请求方法、脚本路径、客户端IP等。
- $_ENV:用于访问环境变量。
- $_FILES:用于处理文件上传。
- $GLOBALS:包含当前脚本中所有全局变量的数组。
PHP如何实现页面跳转
使用header()函数 通过header()函数发送HTTP头信息,实现页面跳转。
header("Location: https://www.example.com");
exit; // 跳转后停止脚本执行
使用HTML的<meta>标签 在HTML中使用<meta>标签实现页面跳转。
echo '<meta http-equiv="refresh" content="0;url=https://www.example.com">';
防止脚本攻击函数
htmlspecialchars 主要用于处理用户输入或动态生成的HTML内容,防止XSS攻击。
htmlentities 适用于需要将整个字符串完全转换为HTML实体的场景,例如显示用户输入的HTML代码。
is_writeable()函数存在Bug
无法准确判断一个目录/文件是否可写,请写一个函数来判断目录/文件是否绝对可写
在windows中,当文件只有只读属性时,is_writeable()函数才返回false,当返回true时,该文件不一定是可写的。 如果是目录,在目录中新建文件并通过打开文件来判断; 如果是文件,可以通过打开文件(fopen),来测试文件是否可写。
在Unix中,当php配置文件中开启safe_mode时(safe_mode=on),is_writeable()同样不可用。 读取配置文件是否safe_mode是否开启。
创建Composer包
- 初始化项目:
mkdir my-package && cd my-package
,然后运行composer init。 - 配置composer.json:指定name、description、type、license、autoload等信息。
- 设置命名空间和自动加载:如"psr-4":
{"MyVendor\\MyPackage\\": "src/"}
。 - 编写包代码:在src/目录下创建PHP类,遵循PSR-4标准。
- 添加测试:使用PHPUnit或其他测试框架编写测试用例。
- 发布到Packagist:将代码推送到GitHub/GitLab等平台,在Packagist.org提交仓库URL,设置GitHub服务钩子自动更新。
- 版本控制:使用语义化版本控制(SemVer),通过git tag标记版本。
- 文档编写:添加README.md,说明包用途、安装方法和使用示例。
ThinkPHP
核心架构
核心层
核心层是 ThinkPHP 的基础部分,提供了框架运行所需的基本功能和工具。
- 请求处理:接收和解析用户请求,提取请求参数。
- 路由分发:根据请求的 URL 和配置的路由规则,将请求分发到对应的控制器和方法。
- 模板引擎:支持多种模板引擎,用于视图层的渲染。
- 自动加载:支持 PSR-4 和 PSR-0 标准的自动加载机制,方便类的加载。
- 配置管理:提供灵活的配置文件管理,支持多种配置方式。
- 日志记录:记录运行日志,便于调试和问题排查。
行为层
行为层提供了一些常用的行为(Behavior),这些行为可以在框架运行的各个阶段被触发和执行。行为层的主要功能包括:
- 日志记录:记录系统运行日志。
- 性能监控:监控系统性能,记录请求的执行时间等信息。
- 安全防护:提供安全防护机制,如防止 SQL 注入、XSS 攻击等。
- 图片处理:提供图片上传、裁剪、缩放等操作。
- 验证码生成:生成验证码,用于用户验证。
服务层
服务层是业务逻辑的具体实现部分,通常由开发者根据具体业务需求开发。
- 模型层(Model):封装数据库操作,提供数据的增删改查等操作。
- 控制器层(Controller):处理用户请求,调用模型层获取数据,并将数据传递给视图层。
- 视图层(View):负责将数据渲染成用户界面,通常使用模板引擎来实现。
- 服务类(Service):封装复杂的业务逻辑,提供复用的业务方法。
运行机制
- 入口(Entry):接收用户请求的部分,通常是 index.php 文件。入口文件负责初始化框架环境,加载配置文件,启动框架。
- 调度(Dispatch):决定如何处理请求的部分。调度器根据配置文件解析 URL,找到对应的控制器和动作,并将其交给控制器处理。
- 响应(Response):向客户端发送结果的部分。控制器处理完请求后,将结果传递给响应器,响应器负责将结果发送给客户端。
核心组件
路由组件
- 功能:解析 URL,根据路由规则将请求分发到对应的控制器和方法。
- 实现:通过 Route 类实现,支持多种路由规则,如正则表达式、动态路由等。
模型组件
- 功能:封装数据库操作,提供数据的增删改查等操作。
- 实现:通过 Model 类实现,支持多种数据库驱动,如 MySQL、SQLite 等。
视图组件
- 功能:负责将数据渲染成用户界面。
- 实现:支持多种模板引擎,如 Smarty、Twig 等,默认使用内置的模板引擎。
日志组件
- 功能:记录系统运行日志,便于调试和问题排查。
- 实现:通过 Log 类实现,支持多种日志存储方式,如文件、数据库等。
配置组件
- 功能:提供灵活的配置文件管理,支持多种配置方式。
- 实现:通过 Config 类实现,支持从文件、环境变量等加载配置。
扩展机制
ThinkPHP 提供了丰富的扩展机制,允许开发者根据需要扩展框架的功能:
- 行为扩展:通过定义行为类,可以在框架运行的各个阶段插入自定义逻辑。
- 插件扩展:通过定义插件类,可以在控制器中调用插件提供的功能。
- 服务提供者:通过定义服务提供者类,可以注册和管理应用服务。
- 中间件:通过定义中间件类,可以在请求处理过程中插入自定义逻辑。
自定义路由解析和调度器
- 实现自定义路由解析器:首先,需要继承think\Route类并实现自己的路由解析器。在这个解析器中,可以实现自己的路由规则和匹配逻辑。
- 注册自定义路由解析器:然后,在route.php配置文件中注册自己的路由解析器。例如,可以通过register方法注册自己的路由解析器。
- 实现自定义调度器:接下来,可以继承think\App类并实现自己的调度器。在这个调度器中,可以实现自己的调度规则和分发逻辑。
- 调用自定义调度器:最后,在入口文件index.php中调用自己的调度器。例如,可以通过app()->run方法启动自己的调度器。
ThinkPHP应用的性能
- 数据库优化:在开发过程中,需要注意数据库的设计和优化,包括合理设计表结构、优化查询语句、使用索引等。此外,也可以利用缓存技术来降低数据库的访问压力,提高查询效率。
- 响应时间优化:可以利用ThinkPHP提供的缓存技术,减少不必要的计算和数据库访问。此外,还可以采用异步任务和队列等方式来分散请求的压力,降低服务器响应时间。
- 内存消耗优化:可以采用分页技术,限制一次查询返回的数据量,降低内存占用;也可以利用静态代理,将频繁访问的数据存储在内存中,减少对数据库的直接访问。
- 代码优化:保持代码简洁和高效,避免冗余代码和无效循环等。
- 合理利用框架提供的特性:利用ThinkPHP提供的特性,如路由调度、缓存、自动加载等,可以提高程序的整体性能。
- 及时发布新版本:及时更新ThinkPHP的新版本,可以修复已知问题,优化性能。
Laravel
Container 容器
Laravel 容器是一个强大的依赖注入容器,用于管理类的依赖关系和实例化过程。
- 依赖注入:自动解析类的依赖关系。
- 单例管理:支持单例模式,确保某些类的实例在整个应用中只有一个。
- 接口绑定:将接口绑定到具体的实现类。
- 闭包绑定:支持通过闭包动态生成对象。
容器的源码位于 vendor/laravel/framework/src/Illuminate/Container/Container.php
IOC 控制反转
控制反转(Inversion of Control, IoC)是一种设计原则,用于将类的依赖关系从类的内部反转到类的外部。
通过控制反转,类的依赖关系由外部注入,而不是在类的内部直接实例化。这有助于降低代码的耦合度,提高代码的可维护性和可测试性。
class UserController {
protected $userService;
public function __construct(UserService $userService) {
$this->userService = $userService;
}
}
UserController 的依赖 UserService 是通过构造函数注入的,而不是在 UserController 内部直接实例化。
DI 依赖注入
依赖注入(Dependency Injection, DI)是控制反转的一种实现方式,通过函数参数或构造函数参数将依赖关系注入到类中。
依赖注入有助于提高代码的模块化、可测试性和灵活性。
构造函数注入
class UserService {
private $userRepository;
public function __construct(UserRepository $userRepository) {
$this->userRepository = $userRepository;
}
public function getUser($id) {
return $this->userRepository->find($id);
}
}
方法注入
class ReportGenerator {
public function generate(ExportFormatter $formatter, $data) {
return $formatter->format($data);
}
}
// 使用示例
$generator = new ReportGenerator();
$generator->generate(app(ExcelFormatter::class), $data);
接口/契约注入
interface LoggerInterface {
public function log($message);
}
class FileLogger implements LoggerInterface {
public function log($message) {
file_put_contents('app.log', $message, FILE_APPEND);
}
}
// 绑定接口到实现
$this->app->bind(LoggerInterface::class, FileLogger::class);
// 使用
class OrderProcessor {
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
}
Contract 契约
契约(Contract)是 Laravel 中的一组接口,用于定义类的行为和方法。
契约提供了一种标准化的方式来定义类的功能,使得不同的实现类可以互换使用。
契约通常位于 vendor/laravel/framework/src/Illuminate/Contracts
命名空间下。
namespace Illuminate\Contracts\Auth;
interface Guard {
public function user();
public function check();
public function guest();
}
为什么使用契约?
- 低耦合:依赖接口而非具体实现
- 可替换性:轻松切换实现(如从文件缓存切换到Redis)
- 可测试性:更容易模拟依赖
Provider 服务提供者
服务提供者(Service Provider)是 Laravel 中用于注册服务和绑定依赖关系的类。
服务提供者通常位于 app/Providers 目录下,它们通过 register 和 boot 方法来注册服务和执行初始化操作。
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider {
public function register() {
// 注册服务
$this->app->bind('App\Contracts\UserRepository', 'App\Repositories\DbUserRepository');
}
public function boot() {
// 初始化操作
}
}
依赖解析过程
当Laravel解析一个类时,其过程如下:
- 检查是否已注册显式绑定
- 如果没有,使用反射自动解析
- 递归解析所有依赖
- 返回完全构建的对象
On this page
- PHP
- 基础
- PHP 的生命周期/启动流程
- PHP 8+新特性
- Opcache设置问题
- PHP 的(内存)垃圾回收机制
- 如果我的网站用的utf-8编码,为防止乱码出现,都需要注意哪些地方?
- 什么是单点入口呢?
- PHP 的控制反转
- 合并数组
- 常用的魔术方法有哪些?
- $this和self、parent这三个关键词
- 常量
- __autoload()方法的工作原理是什么?
- trait 和 interface
- PHP超全局变量的列表
- PHP如何实现页面跳转
- 防止脚本攻击函数
- is_writeable()函数存在Bug
- 创建Composer包
- ThinkPHP
- 核心架构
- 运行机制
- 核心组件
- 扩展机制
- 自定义路由解析和调度器
- ThinkPHP应用的性能
- Laravel
- Container 容器
- IOC 控制反转
- DI 依赖注入
- 构造函数注入
- 方法注入
- 接口/契约注入
- Contract 契约
- Provider 服务提供者