## 简介
在使用PHP代码时,您可能经常会遇到`parent::`、`static::`和`self::`。但是当你第一次作为一个开发人员开始的时候,有时候你会很困惑,不知道它们是做什么的,以及它们之间的区别。
在我第一次作为开发人员开始工作后的很长一段时间里,我认为`static::`和`self::`是完全一样的。
## `parent::`是什么?
假设我们有一个`BaseTestCase`类,它有一个`setUp`方法:
```php
class BaseTestCase
{
public function setUp(): void
{
echo 'Run base test case set up here...';
}
}
(new BaseTestCase())->setUp();
// Output is: "Run base test case set up here...';
```
正如我们所看到的,当我们调用 setUp 方法时,它按预期运行并输出文本。
现在,让我们假设我们想要创建一个新的`FeatureTest`类来继承`BaseTestCase`类。如果我们想运行`FeatureTest`类的`setUp`方法,我们可以这样做:
```
class FeatureTest extends BaseTestCase
{
//
}
(new FeatureTest())->setUp();
// Output is: "Run base test case set up here...";
```
正如我们所看到的,我们没有在`FeatureTest`中定义`setUp`方法,所以在`BaseTestCase`中定义的方法将被运行。
现在,假设我们想在运行`FeatureTest`中的`setUp`方法时运行一些额外的逻辑。例如,如果这些类是作为PhpUnit测试的一部分使用的测试用例,那么我们可能需要在数据库中创建模型或设置测试值。
一开始,你可能(错误地)认为你可以在你的`FeatureTest`方法中定义`setUp`方法,然后调用`$this->setUp()`。老实说,当我第一次学习编程的时候,我总是陷入这个陷阱!
所以我们的代码可能看起来像这样:
```php
class FeatureTest extends BaseTestCase
{
public function setUp(): void
{
$this->setUp();
echo 'Run extra feature test set up here...';
}
}
(new FeatureTest())->setUp();
```
但是,您会发现,如果我们运行这段代码,我们最终会陷入一个循环,导致您的应用程序崩溃。这是因为我们递归地要求`setUp`一遍又一遍地调用它自己。你可能会得到类似这样的输出:
```
Fatal error: Out of memory (allocated 31457280 bytes) (tried to allocate 262144 bytes) in /in/1MXtt on line 15
mmap() failed: [12] Cannot allocate memory
mmap() failed: [12] Cannot allocate memory
Process exited with code 255.
```
因此,我们需要告诉PHP在`BaseTestCase`中使用`setUp`方法,而不是使用`$this->setUp()`。为了做到这一点,我们可以像这样用`parent::setUp()`替换`$this->setUp()`:
```
class FeatureTest extends BaseTestCase
{
public function setUp(): void
{
parent::setUp();
echo 'Run extra feature test set up here...';
}
}
(new FeatureTest())->setUp();
// Output is: "Run base test case set up here... Run extra feature test set up here...";
```
现在,正如你所看到的,当我们在`FeatureTest`类中运行`setUp`方法时,我们首先运行`BaseTestCase`中的代码,然后继续运行子类中定义的其余代码。
值得注意的是,您并不总是需要将`parent::`调用放在方法的顶部。实际上,您可以将其放置在方法中任何最适合代码目的的位置。例如,如果你想先在`FeatureTest`类中运行你的代码,然后在`BaseTestCase`类中运行,你可以像这样将`parent::setUp()`调用移动到方法的底部:
## `self::`是什么?
假设我们有一个`Model`类,它有一个静态的`connection`属性和一个`makeConnection`方法。我们还可以想象我们有一个`User`类,它继承了`Model`类并覆盖了`connection`属性。
这两个类可能看起来像这样:
```
class Model
{
public static string $connection = 'mysql';
public function makeConnection(): void
{
echo 'Making connection to: '.self::$connection;
}
}
class User extends Model
{
public static string $connection = 'postgres';
}
```
现在让我们在两个类上运行`makeConnection`方法,看看我们会得到什么输出:
```
(new Model())->makeConnection();
// Output is: "Making connection to mysql"
(new User())->makeConnection();
// Output is: "Making connection to mysql";
```
正如我们所看到的,这两个调用都导致了`Model`类的`connection`属性被使用。这是因为`self`使用了在方法所在的类上定义的属性。在这两种情况下,`makeConnection`方法在`Model`类上是打开的,因为`User`类上不存在一个方法。
为了进一步说明这一点,我们将向`User`类添加`makeConnection`方法,如下所示:
```
class Model
{
public static string $connection = 'mysql';
public function makeConnection(): void
{
echo 'Making connection to: '.self::$connection;
}
}
class User extends Model
{
public static string $connection = 'postgres';
public function makeConnection(): void
{
echo 'Making connection to: '.self::$connection;
}
}
```
现在,如果我们再次调用这两个方法,我们会得到以下输出:
```
(new Model())->makeConnection();
// Output is: "Making connection to mysql"
(new User())->makeConnection();
// Output is: "Making connection to postgres";
```
正如您所看到的,对`makeConnection`的调用现在将使用`User`类上的`connection`字段,因为这是该方法存在的地方。
## `static::`是什么?
现在我们已经知道了`self::`的作用,让我们来看看`static::`。
为了更好地理解它的作用,让我们更新上面的代码示例,使用`static::`而不是`self::`,如下所示:
```
class Model
{
public static $connection = 'mysql';
public function makeConnection()
{
echo 'Making connection to: '.static::$connection;
}
}
class User extends Model
{
public static $connection = 'postgres';
}
```
如果我们在两个类上运行`makeConnection`方法,我们会得到以下输出:
```
(new Model())->makeConnection();
// Output is: "Making connection to mysql"
(new User())->makeConnection();
// Output is: "Making connection to postgres";
```
正如我们所看到的,这个输出与我们之前使用`self::$connection`时不同。对`User`类上的`makeConnection`方法的调用使用了`User`类上的`connection`属性,而不是`Model`类(该方法实际所属的类)。这是由于PHP中一个名为“后期静态绑定”的特性。
如果您有兴趣阅读更多关于后期静态绑定的内容,可以在这里查看PHP文档。https://www.php.net/manual/en/language.oop5.late-static-bindings.php
> 根据PHP文档:*这个特性被命名为“后期静态绑定”,从内部的角度考虑。“后期绑定”来自这样一个事实,即static::将不会使用定义方法的类来解析,而是使用运行时信息来计算。它也被称为“静态绑定”,因为它可以用于(但不限于)静态方法调用。"*
因此,在我们的示例中,使用了`User`类上的`connection`属性,因为我们在同一个类上调用了`makeConnection`方法。
然而,值得注意的是,如果`connection`属性在`User`类上不存在,它将回退到使用`Model`类上的属性。
## 什么时候使用self::或 static::?
现在我们对`self::`和`static::`之间的区别有了一个大致的了解,让我们快速介绍一下如何决定在自己的代码中使用哪一个。
这一切都取决于您正在编写的代码的用例。
一般来说,我通常会使用`static::`而不是`self::`,因为我希望我的类是可扩展的
例如,假设我想写一个类,我完全打算由子类继承(例如上面示例中的`BaseTestCase`类)。除非我真的想防止子类重写属性或方法,否则我想使用`static::`。
这意味着我可以有信心,如果我重写任何静态方法或字段,我的子类将使用我的重写。我无法告诉你有多少次我在代码中遇到了bug,当我在父类中使用`self::`时,然后无法弄清楚为什么我的子类没有使用我的重写!
另一方面,一些开发人员可能会争辩说,你应该坚持使用`self::`,因为你不应该真的从类继承。他们可能会建议你应该遵循“组合优于继承”的原则。我不会深入研究这个话题,因为这是未来的另一篇博客文章。但从广义上说,简单地说,这个原则指出,你应该避免通过将所有逻辑放在父类中来为类添加功能,而是通过用许多更小的类来构建类来添加功能。
这意味着如果你遵循这个原则,你就不需要使用`static::`,因为你永远不会扩展你的父类。如果你想确保类不能被扩展,你甚至可以更进一步,在定义类时使用`final`关键字。使用`final`关键字可以防止类被继承,所以它可以减少您对类可能意外扩展并引入任何潜在错误的担忧。
一般来说,最好在编写代码时根据具体情况决定应该使用`static::`还是`self::`。
- 设计模式系列
- 工厂方法模式
- 序言
- Windows程序注册为服务的工具WinSW
- 基础
- 安装
- 开发规范
- 目录结构
- 配置
- 快速入门
- 架构
- 请求流程
- 架构总览
- URL访问
- 容器和依赖注入
- 中间件
- 事件
- 代码层结构
- 四个层次
- 路由
- 控制器
- 请求
- 响应
- 数据库
- MySQL实时同步数据到ES解决方案
- 阿里云DTS数据MySQL同步至Elasticsearch实战
- PHP中的MySQL连接池
- PHP异步非阻塞MySQL客户端连接池
- 模型
- 视图
- 注解
- @SpringBootApplication(exclude={DataSourceAutoConfiguration.calss})
- @EnableFeignClients(basePackages = "com.wotu.feign")
- @EnableAspectJAutoProxy
- @EnableDiscoveryClient
- 错误和日志
- 异常处理
- 日志处理
- 调试
- 验证
- 验证器
- 验证规则
- 扩展库
- 附录
- Spring框架知识体系详解
- Maven
- Maven和Composer
- 构建Maven项目
- 实操课程
- 01.初识SpringBoot
- 第1章 Java Web发展史与学习Java的方法
- 第2章 环境与常见问题踩坑
- 第3章 springboot的路由与控制器
- 02.Java编程思想深度理论知识
- 第1章 Java编程思想总体
- 第2章 英雄联盟的小案例理解Java中最为抽象的概念
- 第3章 彻底理解IOC、DI与DIP
- 03.Spring与SpringBoot理论篇
- 第1章 Spring与SpringBoot导学
- 第2章 Spring IOC的核心机制:实例化与注入
- 第3章 SpringBoot基本配置原理
- 04.SprinBoot的条件注解与配置
- 第1章 conditonal 条件注解
- 第2章 SpringBoot自动装配解析
- 05.Java异常深度剖析
- 第1章 Java异常分类剖析与自定义异常
- 第2章 自动配置Url前缀
- 06.参数校验机制与LomBok工具集的使用
- 第1章 LomBok工具集的使用
- 第2章 参数校验机制以及自定义校验
- 07.项目分层设计与JPA技术
- 第1章 项目分层原则与层与层的松耦合原则
- 第2章 数据库设计、实体关系与查询方案探讨
- 第3章 JPA的关联关系与规则查询
- 08.ORM的概念与思维
- 第1章 ORM的概念与思维
- 第2章 Banner等相关业务
- 第3章 再谈数据库设计技巧与VO层对象的技巧
- 09.JPA的多种查询规则
- 第1章 DozerBeanMapper的使用
- 第2章 详解SKU的规格设计
- 第3章 通用泛型Converter
- 10.令牌与权限
- 第1章 通用泛型类与java泛型的思考
- 常见问题
- 微服务
- demo
- PHP中Self、Static和parent的区别
- Swoole-Cli
- 为什么要使用现代化PHP框架?
- 公众号
- 一键部署微信公众号Markdown编辑器(支持适配和主题设计)
- Autodesigner 2.0发布
- Luya 一个现代化PHP开发框架
- PHPZip - 创建、读取和管理 ZIP 文件的简单库
- 吊打Golang的PHP界天花板webman压测对比
- 简洁而强大的 YAML 解析库
- 推荐一个革命性的PHP测试框架:Kahlan
- ServBay下一代Web开发环境
- 基于Websocket和Canvas实现多人协作实时共享白板
- Apipost预执行脚本如何调用外部PHP语言
- 认证和授权的安全令牌 Bearer Token
- Laradock PHP 的 Docker 完整本地开发环境
- 高效接口防抖策略,确保数据安全,避免重复提交的终极解决方案!
- TIOBE 6月榜单:PHP稳步前行,编程语言生态的微妙变化
- Aho-Corasick字符串匹配算法的实现
- Redis键空间通知 Keyspace Notification 事件订阅
- ServBay如何启用并运行Webman项目
- 使用mpdf实现导出pdf文件功能
- Medoo 轻量级PHP数据库框架
- 在PHP中编写和运行单元测试
- 9 PHP运行时基准性能测试
- QR码生成器在PHP中的源代码
- 使用Gogs极易搭建的自助Git服务
- Gitea
- webman如何记录SQL到日志?
- Sentry PHP: 实时监测并处理PHP应用程序中的错误
- Swoole v6 Alpha 版本已发布
- Proxypin
- Rust实现的Redis内存数据库发布
- PHP 8.4.0 Alpha 1 测试版本发布
- 121
- Golang + Vue 开发的开源轻量 Linux 服务器运维管理面板
- 内网穿透 FRP VS Tailscale
- 新一代开源代码托管平台Gitea
- 微服务系列
- Nacos云原生配置中心介绍与使用
- 轻量级的开源高性能事件库libevent
- 国密算法
- 国密算法(商用密码)
- GmSSL 支持国密SM2/SM3/SM4/SM9/SSL 密码工具箱
- GmSSL PHP 使用
- 数据库
- SQLite数据库的Web管理工具
- 阿里巴巴MySQL数据库强制规范
- PHP
- PHP安全测试秘密武器 PHPGGC
- 使用declare(strict_types=1)来获得更健壮的PHP代码
- PHP中的魔术常量
- OSS 直传阿里腾讯示例
- PHP源码编译安装APCu扩展实现数据缓存
- BI性能DuckDB数据管理系统
- 为什么别人可以是架构师!而我却不是?
- 密码还在用 MD5 加盐?不如试试 password_hash
- Elasticsearch 在电商领域的应用与实践
- Cron 定时任务入门
- 如何动态设置定时任务!而不是写死在Linux Crontab
- Elasticsearch的四种查询方式,你知道多少?
- Meilisearch vs Elasticsearch
- OpenSearch vs Elasticsearch
- Emlog 轻量级开源博客及建站系统
- 现代化PHP原生协程引擎 PRipple
- 使用Zephir编写C扩展将PHP源代码编译加密
- 如何将PHP源代码编译加密,同时保证代码能正常的运行
- 为什么选择Zephir给PHP编写动态扩展库?
- 使用 PHP + XlsWriter实现百万级数据导入导出
- Rust编写PHP扩展
- 阿里云盘开放平台对接进行文件同步
- 如何构建自己的PHP静态可执行文件
- IM后端架构
- RESTful设计方法和规范
- PHP编译器BPC 7.3 发布,成功编译ThinkPHP8
- 高性能的配置管理扩展 Yaconf
- PHP实现雪花算法库 Snowflake
- PHP官方现代化核心加密库Sodium
- pie
- 现代化、精简、非阻塞PHP标准库PSL
- PHP泛型和集合
- 手把手教你正确使用 Composer包管理
- JWT双令牌认证实现无感Token自动续期
- 最先进PHP大模型深度学习库TransformersPHP
- PHP如何启用 FFI 扩展
- PHP超集语言PXP
- 低延迟双向实时事件通信 Socket.IO
- PHP OOP中的继承和多态
- 强大的现代PHP高级调试工具Kint
- PHP基金会
- 基于webman+vue3高质量中后台框架SaiAdmin
- 开源免费的定时任务管理系统:Gocron
- 简单强大OCR工具EasyOCR在PHP中使用
- PHP代码抽象语法树工具PHP AST Viewer
- MySQL数据库管理工具PHPMyAdmin
- Rust编写的一款高性能多人代码编辑器Zed
- 超高性能PHP框架Workerman v5.0.0-beta.8 发布
- 高并发系列
- 入门介绍及安装
- Lua脚本开发 Hello World
- 执行流程与阶段详解
- Nginx Lua API 接口开发
- Lua模块开发
- OpenResty 高性能的正式原因
- 记一次查找 lua-resty-mysql 库 insert_id 的 bug
- 包管理工具OPM和LuaRocks使用
- 异步非阻塞HTTP客户端库 lua-resty-http
- Nginx 内置绑定变量
- Redis协程网络库 lua-resty-redis
- 动态HTML渲染库 lua-testy-template
- 单独的
- StackBlitz在线开发环境
- AI
- 基础概念
- 12312
- 基础镜像的坑
- 利用phpy实现 PHP 编写 Vision Transformer (ViT) 模型
- 语义化版本 2.0.0