它的本质是:**类成员(Properties 和 Methods)是封装在类作用域 (Class Scope)内的数据 (State)和行为 (Behavior)。
- 属性 (Properties):是对象的静态状态容器,存储在堆内存中,伴随对象实例存在。
- 方法 (Methods):是对象的动态行为指令,存储在代码段中,通过
this指针绑定到具体实例。 - 可见性 (Visibility):是封装的防火墙,决定了谁有权读写状态或调用行为。
- 静态成员 (Static):是类级别的共享资源,不属于任何实例,存在于全局符号表中。
如果把类比作一家公司:
- 类 (Class):是公司章程 + 办公室蓝图。
- 实例 (Object):是实际运营的公司实体。
- 属性 (Properties):是公司的资产和档案(现金、员工名单、客户数据)。
public:前台展示柜,任何人都能看。private:CEO 保险箱,只有内部高层能开。protected:部门经理文件夹,子公司(子类)也能看。
- 方法 (Methods):是员工的岗位职责(销售、研发、财务)。
$this:是当前正在执行任务的员工 ID。
- 静态成员 (Static):是公司品牌 Logo 和注册号码。
- 不管开多少家分公司(实例),Logo 只有一个,存在总部(类级别)。
- 核心逻辑:别把家底(属性)随便露在外面,也别让外人(外部代码)直接指挥员工(方法)。通过接口(Public Methods)进行受控交互。
一、属性 (Properties):状态的持久化
1. 定义与初始化
- 声明:必须使用关键字
var(PHP4遗留),public,protected,private。 - 默认值:可以是标量、数组、
null。不能是表达式或资源(如public $db = new PDO(...)❌)。 - 初始化时机:
- 声明时:编译期确定默认值。
- 构造函数中:运行时动态赋值。
- PHP 8.0+ 构造函数提升:
public function __construct(private string $name) {}。
2. 动态属性 (Dynamic Properties) -PHP 8.2+ 已废弃
- 现象:
$obj->newProp = 'value'; - 本质:在实例的哈希表中动态添加键值对。
- 风险:
- 拼写错误隐蔽:
$user->nmae不会报错,而是创建新属性,导致 Bug。 - 性能差:无法优化内存布局。
- 对策:使用
#[AllowDynamicProperties](仅用于兼容) 或改用数组/DTO。
- 拼写错误隐蔽:
3. 类型声明 (Typed Properties) -PHP 7.4+
- 语法:
public int $id; - 优势:
- 早期错误检测:赋值类型不符立即报错。
- OPcache 优化:引擎知道类型,减少 ZVal 转换开销。
- 未初始化状态:声明了但未赋值的 typed property 处于 “Uninitialized” 状态,访问它会报错。这是防止使用 null 默认值的安全机制。
💡 核心洞察:属性是对象的记忆。类型声明是给记忆加锁,防止存错东西。
二、方法 (Methods):行为的执行
1.$this指针
- 本质:指向当前调用该方法的对象实例。
- 作用:在方法内部访问其他属性和方法。
- 静态方法中:没有
$this。因为静态方法不属于任何实例。
2. 魔术方法 (Magic Methods)
__construct/__destruct:生与死。__get/__set:拦截不可访问属性的读写(实现重载 Overloading)。__call/__callStatic:拦截不可访问方法的调用。__invoke:让对象可像函数一样调用。- 价值:提供钩子 (Hooks),允许框架(如 Laravel/Hyperf)实现 ORM、代理、AOP 等高级功能。
3. 引用传递 vs. 值传递
- 对象参数:默认通过对象标识符 (Object Identifier)传递。
- 修改对象内部状态会影响原对象。
- 但重新赋值变量 (
$obj = new Other()) 不会影响原变量。
- 标量参数:默认值传递。需
&$param才能引用传递。
三、可见性 (Visibility):封装的防火墙
| 关键字 | 访问权限 | 隐喻 | 适用场景 |
|---|---|---|---|
public | ** everywhere** | 公司官网 | API 接口、对外服务方法。 |
protected | 自身 + 子类 | 内部内网 | 模板方法模式、供子类扩展的逻辑。 |
private | 仅自身 | CEO 日记 | 内部辅助函数、敏感数据、实现细节。 |
1. 封装原则
- 最小暴露原则:默认
private,除非必要才protected,最后才public。 - Getter/Setter:通过方法控制访问,而非直接暴露属性。
- 优势:可以在 Setter 中加入验证逻辑(如年龄不能为负)。
2. 继承中的可见性
- 规则:子类可以访问父类的
public和protected成员,但不能访问private。 - 重写 (Override):子类可以重写父类的
public/protected方法,但不能降低可见性(如不能把public改为private)。
四、静态成员 (Static Members):类级别的共享
1. 静态属性 (static $count)
- 存储:存在于类结构 (zend_class_entry)中,而非对象实例中。
- 生命周期:脚本启动时初始化,脚本结束时销毁。
- 用途:计数器、单例实例、配置缓存。
- 陷阱:在 Swoole/Hyperf 常驻内存环境中,静态属性会永久保留,导致内存泄漏或数据污染(不同请求共享同一静态变量)。
- 对策:在协程环境中慎用静态属性,或使用Context隔离。
2. 静态方法 (static function)
- 特点:不依赖
$this,不能访问非静态属性。 - 用途:工具函数、工厂方法 (
create())。 - 后期静态绑定 (
static::):self:::指向定义该方法的类。static:::指向调用该方法的类(运行时决定)。- 价值:解决继承中的静态方法多态问题。
五、认知牢笼:常见误区
1. 误区:“私有属性不能被外部读取,所以绝对安全。”
- 真相:
- 反射 (Reflection)可以强制访问私有成员。
- 序列化/反序列化可能绕过构造函数。
- 对策:不要依赖可见性做安全隔离,它只是代码规范。
2. 误区:“静态方法比实例方法快。”
- 真相:
- 差异微乎其微。
- 静态方法难以 Mock,不利于单元测试。
- 对策:优先使用实例方法,除非确实是纯工具函数。
3. 误区:“属性越多,对象越强大。”
- 真相:
- 属性多意味着状态复杂,难以维护。
- 对策:遵循单一职责原则 (SRP)。如果一个类有超过 5-7 个属性,考虑拆分类或引入 Value Object。
4. 误区:“$this可以在静态方法中使用。”
- 真相:
- Fatal Error。静态方法没有上下文实例。
- 对策:如果需要操作实例,请传入实例作为参数,或使用非静态方法。
5. 误区:“PHP 8.2 废弃动态属性是因为它没用。”
- 真相:
- 是因为它有害(隐藏 Bug、性能差)。
- 对策:使用关联数组或stdClass代替动态属性需求。
🚀 总结:原子化“PHP 类成员”全景图
| 维度 | 关键点 |
|---|---|
| 本质 | 封装在类作用域内的状态与行为 |
| 属性 | 堆内存存储、类型声明、避免动态属性 |
| 方法 | 代码段存储、$this 绑定、魔术方法钩子 |
| 可见性 | Public(公开)/Protected(继承)/Private(内部) |
| 静态 | 类级别共享、常驻内存风险、后期静态绑定 |
| PHP 隐喻 | Company Assets (Props) & Job Roles (Methods) |
| 公式 | Object = State (Props) + Behavior (Methods) ^ Encapsulation |
终极心法:
类成员的本质,是“秩序与边界”。
属性是私密的记忆,方法是公开的承诺。
守住边界,才能构建复杂的系统。
于封装中见安全,于静态见共享;以可见性为尺,解混乱之牛,于对象设计中,求严谨之真。
行动指令:
- 审计属性:检查项目中是否有
public属性,尝试改为private并提供 Getter。 - 启用严格类型:为所有属性添加类型声明 (
string,int,?User)。 - 清理动态属性:搜索
$obj->xyz = ...,替换为正式属性或数组。 - 慎用静态:在 Hyperf/Swoole 项目中,审查所有
static变量,确保不会造成请求间污染。 - 思维升级:记住,好的类设计,是让使用者只需关心 Public 接口,而无需窥探内部实现。