ReflectionProperty
概述
ReflectionProperty 是 PHP 反射 API 中用于检查类属性(成员变量)的类。它提供了属性的名称、类型、修饰符、默认值、是否已初始化等信息,并且可以在运行时读取和修改 private/protected 属性的值。
PHP 8.1+
PHP 8.1+ 中,所有 ReflectionProperty 默认可以访问 private 和 protected 属性,无需调用 setAccessible(true)。
基础概念
获取属性信息
通过 ReflectionClass::getProperty() 或 ReflectionClass::getProperties() 获取 ReflectionProperty 实例。
修饰符检查
检查属性的访问控制级别(public/protected/private)、是否静态、是否只读等。
访问和修改属性值
通过 getValue() 和 setValue() 在运行时读取和修改属性值。
语法与代码
获取属性的基本信息
<?php
declare(strict_types=1);
class UserProfile
{
public string $name = '';
protected int $age = 0;
private string $email = '';
public static int $totalUsers = 0;
public readonly string $role = 'user';
}
$refClass = new \ReflectionClass(UserProfile::class);
foreach ($refClass->getProperties() as $prop) {
$modifiers = \Reflection::getModifierNames($prop->getModifiers());
$type = $prop->getType()?->getName() ?? 'mixed';
echo implode(' ', $modifiers) . " {$type} \${$prop->getName()}\n";
}获取特定属性
<?php
declare(strict_types=1);
$prop = new \ReflectionProperty(UserProfile::class, 'name');
echo "属性名: " . $prop->getName() . "\n";
echo "类型: " . $prop->getType()?->getName() . "\n";
echo "是否 public: " . ($prop->isPublic() ? 'yes' : 'no') . "\n";
echo "是否 static: " . ($prop->isStatic() ? 'yes' : 'no') . "\n";
echo "是否只读: " . ($prop->isReadOnly() ? 'yes' : 'no') . "\n";访问和修改属性值
<?php
declare(strict_types=1);
class Secret
{
private string $password = 'hidden';
}
$secret = new Secret();
$refProp = new \ReflectionProperty(Secret::class, 'password');
echo $refProp->getValue($secret) . "\n"; // hidden
$refProp->setValue($secret, 'new_password');
echo $refProp->getValue($secret) . "\n"; // new_password获取默认值
<?php
declare(strict_types=1);
$prop = new \ReflectionProperty(UserProfile::class, 'age');
echo "默认值: " . var_export($prop->getDefaultValue(), true) . "\n"; // 0
echo $prop->hasDefaultValue() ? 'yes' : 'no'; // yes详细说明
ReflectionProperty 常用方法
| 方法 | 说明 |
|---|---|
getName() | 属性名 |
getType() | 类型(ReflectionType) |
getModifiers() | 修饰符位掩码 |
getDefaultValue() | 默认值 |
getValue($object) | 获取属性值 |
setValue($object, $value) | 设置属性值 |
isPublic() / isProtected() / isPrivate() | 访问级别 |
isStatic() | 是否静态 |
isReadOnly() | 是否只读(PHP 8.1+) |
isInitialized($object) | 是否已初始化(PHP 8.0+) |
只读属性不能被 setValue
PHP 8.1+ 的 readonly 属性只能在构造函数中初始化,setValue() 会抛出异常。
实战示例
实战:对象属性复制器
<?php
declare(strict_types=1);
class ObjectCloner
{
public static function cloneProperties(object $source, object $target): void
{
$refSource = new \ReflectionClass($source);
$refTarget = new \ReflectionClass($target);
foreach ($refSource->getProperties() as $prop) {
$propName = $prop->getName();
if ($refTarget->hasProperty($propName)) {
$targetProp = $refTarget->getProperty($propName);
$targetProp->setValue($target, $prop->getValue($source));
}
}
}
}注意事项
静态属性的 getValue
静态属性不需要传入对象实例,传入 null 即可。
<?php
declare(strict_types=1);
$prop = new \ReflectionProperty(UserProfile::class, 'totalUsers');
echo $prop->getValue(null); // 0
$prop->setValue(null, 100);最佳实践
- 缓存 ReflectionProperty 实例。
- 谨慎修改私有属性:仅在测试或框架代码中使用。
<?php
declare(strict_types=1);
function getProperty(object $object, string $name): mixed
{
return (new \ReflectionClass($object))
->getProperty($name)
->getValue($object);
}ReflectionProperty 高级用法
批量获取和设置属性
<?php
declare(strict_types=1);
class Product
{
public string $name = '';
public float $price = 0.0;
public int $stock = 0;
private string $internalCode = '';
protected bool $isActive = true;
}
$product = new Product();
// 批量设置公共属性
$refClass = new \ReflectionClass(Product::class);
foreach ($refClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) {
$name = $prop->getName();
$prop->setValue($product, match ($name) {
'name' => 'Widget Pro',
'price' => 29.99,
'stock' => 100,
default => null,
});
}
// 批量读取所有属性
$allValues = [];
foreach ($refClass->getProperties() as $prop) {
$allValues[$prop->getName()] = $prop->getValue($product);
}
print_r($allValues);属性类型分析
<?php
declare(strict_types=1);
class TypedProperties
{
public string $name = '';
public ?int $age = null;
public array $tags = [];
public readonly float $score = 0.0;
public static string $global = 'value';
}
function analyzeProperties(string $className): array
{
$refClass = new \ReflectionClass($className);
$analysis = [];
foreach ($refClass->getProperties() as $prop) {
$type = $prop->getType();
$typeName = $type?->getName() ?? 'mixed';
$analysis[$prop->getName()] = [
'type' => $typeName,
'allowsNull' => $type?->allowsNull() ?? true,
'isBuiltin' => ($type instanceof \ReflectionNamedType) ? $type->isBuiltin() : false,
'isReadOnly' => $prop->isReadOnly(),
'isStatic' => $prop->isStatic(),
'hasDefault' => $prop->hasDefaultValue(),
'defaultValue' => $prop->hasDefaultValue()
? var_export($prop->getDefaultValue(), true)
: null,
'modifiers' => \Reflection::getModifierNames($prop->getModifiers()),
'isInitialized' => $prop->isInitialized(new $className()),
];
}
return $analysis;
}
print_r(analyzeProperties(TypedProperties::class));反射属性的 HasDefault 陷阱
<?php
declare(strict_types=1);
class DefaultValueTest
{
public string $a = 'default'; // hasDefaultValue: true
public string $b; // hasDefaultValue: false(未初始化的 typed property)
public $c; // hasDefaultValue: true(值为 null)
}
$refClass = new \ReflectionClass(DefaultValueTest::class);
foreach ($refClass->getProperties() as $prop) {
echo "\${$prop->getName()}: ";
echo $prop->hasDefaultValue() ? 'has default' : 'no default';
echo " (value: " . var_export($prop->getDefaultValue() ?? 'no value', true) . ")\n";
}PHP 8.0 Typed Properties
PHP 8.0 引入了类型属性。未初始化的 typed property 的 hasDefaultValue() 返回 false。访问未初始化的 typed property 会抛出 Error。
实战:ORM 风格的实体映射器
<?php
declare(strict_types=1);
use Attribute;
#[Attribute(Attribute::TARGET_PROPERTY)]
readonly class ColumnMapping
{
public function __construct(
public string $columnName,
public string $type = 'string',
) {}
}
class UserEntity
{
#[ColumnMapping(columnName: 'user_id', type: 'integer')]
private int $id = 0;
#[ColumnMapping(columnName: 'user_name', type: 'string')]
private string $username = '';
#[ColumnMapping(columnName: 'email_addr', type: 'string')]
private string $email = '';
#[ColumnMapping(columnName: 'is_active', type: 'boolean')]
private bool $active = true;
}
function entityToRow(object $entity): array
{
$refClass = new \ReflectionClass($entity);
$row = [];
foreach ($refClass->getProperties() as $prop) {
$attrs = $prop->getAttributes(ColumnMapping::class);
if (!empty($attrs)) {
$mapping = $attrs[0]->newInstance();
$row[$mapping->columnName] = $prop->getValue($entity);
}
}
return $row;
}
function rowToEntity(string $className, array $row): object
{
$entity = new $className();
$refClass = new \ReflectionClass($className);
foreach ($refClass->getProperties() as $prop) {
$attrs = $prop->getAttributes(ColumnMapping::class);
if (!empty($attrs)) {
$mapping = $attrs[0]->newInstance();
if (array_key_exists($mapping->columnName, $row)) {
$prop->setValue($entity, $row[$mapping->columnName]);
}
}
}
return $entity;
}
// 测试
$user = new UserEntity();
$user->id = 1;
$user->username = 'alice';
$user->email = 'alice@example.com';
$row = entityToRow($user);
print_r($row);
// ['user_id' => 1, 'user_name' => 'alice', 'email_addr' => 'alice@example.com', 'is_active' => true]
$restored = rowToEntity(UserEntity::class, $row);常见误区与 FAQ
可以通过反射给 readonly 属性赋值吗?
PHP 8.1+ 的 readonly 属性只能在构造函数中初始化一次。通过反射的 setValue() 可以绕过这个限制(仅限未初始化时),但这是不推荐的做法。
isInitialized 对未声明类型的属性
未声明类型的属性(如 public $x;)始终被视为已初始化(值为 null)。只有 typed properties 才有"未初始化"状态。
如何判断属性是否被声明为 promoted constructor parameter?
<?php
declare(strict_types=1);
class Example
{
public function __construct(public readonly string $name = '') {}
}
$prop = new \ReflectionProperty(Example::class, 'name');
echo $prop->isPromoted() ? 'promoted' : 'not promoted'; // promoted (PHP 8.1+)属性反射的高级模式
属性分组与过滤
<?php
declare(strict_types=1);
class Configuration
{
// 系统配置
private string $systemName = 'Default';
private int $systemTimeout = 30;
private bool $systemDebug = false;
// 数据库配置
private string $dbHost = 'localhost';
private int $dbPort = 3306;
private string $dbName = 'app';
// 缓存配置
private string $cacheDriver = 'redis';
private int $cacheTtl = 3600;
}
function getPropertiesByPrefix(string $className, string $prefix): array
{
$refClass = new \ReflectionClass($className);
$result = [];
foreach ($refClass->getProperties(\ReflectionProperty::IS_PRIVATE) as $prop) {
if (str_starts_with($prop->getName(), $prefix)) {
$result[$prop->getName()] = $prop;
}
}
return $result;
}
// 获取所有 db 开头的属性
$dbProps = getPropertiesByPrefix(Configuration::class, 'db');
echo "数据库相关属性:\n";
foreach ($dbProps as $name => $prop) {
echo " - \${$name}: " . ($prop->getType()?->getName() ?? 'mixed') . "\n";
}属性可见性过滤
<?php
declare(strict_types=1);
$refClass = new \ReflectionClass(Configuration::class);
$publicProps = $refClass->getProperties(\ReflectionProperty::IS_PUBLIC);
$protectedProps = $refClass->getProperties(\ReflectionProperty::IS_PROTECTED);
$privateProps = $refClass->getProperties(\ReflectionProperty::IS_PRIVATE);
$staticProps = $refClass->getProperties(\ReflectionProperty::IS_STATIC);
echo "Public 属性: " . count($publicProps) . "\n";
echo "Protected 属性: " . count($protectedProps) . "\n";
echo "Private 属性: " . count($privateProps) . "\n";
echo "Static 属性: " . count($staticProps) . "\n";
// 组合过滤:获取所有 public static 属性
$publicStatic = $refClass->getProperties(
\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_STATIC
);属性初始化状态检查
<?php
declare(strict_types=1);
class PartialUser
{
public string $name; // 未初始化 typed property
public string $email = ''; // 有默认值
public int $age; // 未初始化
}
$user = new PartialUser();
$user->email = 'test@test.com';
$refClass = new \ReflectionClass(PartialUser::class);
foreach ($refClass->getProperties() as $prop) {
echo "\${$prop->getName()}: ";
echo "初始化=" . ($prop->isInitialized($user) ? 'yes' : 'no') . " ";
echo "有默认值=" . ($prop->hasDefaultValue() ? 'yes' : 'no') . "\n";
}
// $name: 初始化=no 有默认值=no
// $email: 初始化=yes 有默认值=yes
// $age: 初始化=no 有默认值=no未初始化的 typed property
访问未初始化的 typed property 会抛出 Error。务必在访问前用 isInitialized() 检查。
常见误区
clone 对象时属性反射的行为
<?php
declare(strict_types=1);
$refProp = new \ReflectionProperty(PartialUser::class, 'name');
$original = new PartialUser();
$original->name = 'Alice';
$cloned = clone $original;
echo $refProp->getValue($cloned); // 'Alice' — clone 会复制属性值反射属性与 __debugInfo 的关系
ReflectionProperty 不受 __debugInfo() 魔术方法影响。反射直接访问对象的真实属性,不会调用任何魔术方法。