强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

PHP 完全指南 / 第 13 章 — 属性 (Attributes)

第 13 章 — 属性 (Attributes):内置属性、自定义属性与 PHP 8 特性

13.1 什么是 Attributes

Attributes(属性注解)是 PHP 8.0 引入的元数据机制,替代了传统的注释式注解(如 Doctrine 的 @ORM\Entity),使用 #[...] 语法。

<?php
// 传统注解(旧方式)
/**
 * @Route("/users", methods={"GET"})
 * @IsGranted("ROLE_ADMIN")
 */

// Attributes(新方式)
#[Route('/users', methods: ['GET'])]
#[IsGranted('ROLE_ADMIN')]
class UserController {}

13.2 内置 Attributes

13.2.1 #[Deprecated] — PHP 8.4+

<?php
class OldService
{
    #[\Deprecated(
        message: 'Use NewService::process() instead',
        since: '2.0.0',
    )]
    public function oldMethod(): void
    {
        // ...
    }
}

$service = new OldService();
$service->oldMethod(); // PHP Deprecated: Method OldService::oldMethod() is deprecated since 2.0.0

13.2.2 #[Override] — PHP 8.3+

<?php
class ParentClass
{
    public function doSomething(): void {}
}

class ChildClass extends ParentClass
{
    #[\Override]  // 如果父类没有此方法,编译时报错
    public function doSomething(): void
    {
        parent::doSomething();
    }

    // #[\Override]
    // public function typoMethd(): void {}  // Error: ChildClass::typoMethd() does not override
}

13.2.3 #[Attribute] — 定义自定义 Attribute

<?php
// 标记一个类可以作为 Attribute 使用
#[\Attribute(\Attribute::TARGET_METHOD)]  // 只能用于方法
class Cache
{
    public function __construct(
        public readonly int $ttl = 3600,
        public readonly string $key = '',
    ) {}
}

13.2.4 #[AllowDynamicProperties]

<?php
#[\AllowDynamicProperties]
class LegacyClass
{
    // 允许动态设置属性
    // PHP 8.2+ 默认禁止此行为
}

$obj = new LegacyClass();
$obj->dynamicProp = 'value';  // 允许

13.2.5 其他内置属性

属性PHP 版本说明
#[\Attribute]8.0标记自定义 Attribute 类
#[\ReturnTypeWillChange]8.1标记返回类型将在未来更改
#[\SensitiveParameter]8.2在堆栈跟踪中隐藏敏感参数
#[\Override]8.3确认方法重写了父类方法
#[\Deprecated]8.4标记弃用的方法/函数
#[\AllowDynamicProperties]8.2允许动态属性

13.3 自定义 Attributes

13.3.1 定义 Attribute 类

<?php
declare(strict_types=1);

namespace App\Attributes;

use Attribute;

// 限制 Attribute 可使用的位置
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION)]
class Route
{
    public function __construct(
        public readonly string $path,
        public readonly array $methods = ['GET'],
        public readonly string $name = '',
    ) {}
}

#[Attribute(Attribute::TARGET_PROPERTY)]
class Required
{
}

#[Attribute(Attribute::TARGET_PROPERTY)]
class MaxLength
{
    public function __construct(
        public readonly int $length,
    ) {}
}

#[Attribute(Attribute::TARGET_CLASS)]
class Entity
{
    public function __construct(
        public readonly string $table = '',
    ) {}
}

// 多次使用(需要声明 Attribute::IS_REPEATABLE)
#[Attribute(Attribute::TARGET_METHOD | Attribute::Attribute::IS_REPEATABLE)]
class Middleware
{
    public function __construct(
        public readonly string $class,
    ) {}
}

13.3.2 Attribute 使用位置

常量说明
TARGET_CLASS
TARGET_METHOD方法
TARGET_FUNCTION函数
TARGET_PROPERTY属性
TARGET_CLASS_CONSTANT类常量
TARGET_PARAMETER参数
TARGET_ALL所有位置
IS_REPEATABLE可重复使用

13.3.3 使用自定义 Attribute

<?php
namespace App\Controllers;

use App\Attributes\Route;
use App\Attributes\Middleware;

class UserController
{
    #[Route('/users', methods: ['GET'], name: 'user.index')]
    #[Middleware(AuthMiddleware::class)]
    #[Middleware(CorsMiddleware::class)]
    public function index(): void
    {
        // ...
    }

    #[Route('/users/{id}', methods: ['GET'], name: 'user.show')]
    public function show(int $id): void
    {
        // ...
    }

    #[Route('/users', methods: ['POST'], name: 'user.create')]
    #[Middleware(AuthMiddleware::class)]
    #[Middleware(ValidateMiddleware::class)]
    public function create(): void
    {
        // ...
    }
}

13.4 反射读取 Attributes

<?php
declare(strict_types=1);

class AttributeReader
{
    /**
     * 获取类上的指定 Attribute
     */
    public static function getClassAttribute(
        string $class,
        string $attributeClass,
    ): ?object {
        $reflection = new \ReflectionClass($class);
        $attributes = $reflection->getAttributes($attributeClass);
        return $attributes[0]?->newInstance();
    }

    /**
     * 获取方法上的所有指定 Attributes
     */
    public static function getMethodAttributes(
        string $class,
        string $method,
        string $attributeClass,
    ): array {
        $reflection = new \ReflectionMethod($class, $method);
        return array_map(
            fn(\ReflectionAttribute $attr) => $attr->newInstance(),
            $reflection->getAttributes($attributeClass)
        );
    }

    /**
     * 获取属性上的 Attribute
     */
    public static function getPropertyAttribute(
        string $class,
        string $property,
        string $attributeClass,
    ): ?object {
        $reflection = new \ReflectionProperty($class, $property);
        $attributes = $reflection->getAttributes($attributeClass);
        return $attributes[0]?->newInstance();
    }
}

// 使用示例
$routes = [];
$reflection = new \ReflectionClass(UserController::class);

foreach ($reflection->getMethods() as $method) {
    foreach ($method->getAttributes(Route::class) as $routeAttr) {
        $route = $routeAttr->newInstance();
        $routes[] = [
            'path'    => $route->path,
            'methods' => $route->methods,
            'name'    => $route->name,
            'handler' => [$reflection->getName(), $method->getName()],
        ];
    }
}

print_r($routes);

13.5 属性验证系统

<?php
declare(strict_types=1);

namespace App\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_PROPERTY)]
class Validate
{
    public function __construct(
        public readonly ?int $minLength = null,
        public readonly ?int $maxLength = null,
        public readonly ?string $pattern = null,
        public readonly ?string $email = null,
        public readonly ?int $min = null,
        public readonly ?int $max = null,
    ) {}
}

// 使用验证属性
class UserDTO
{
    #[Validate(minLength: 2, maxLength: 50)]
    public string $name = '';

    #[Validate(email: true)]
    public string $email = '';

    #[Validate(min: 0, max: 150)]
    public int $age = 0;

    #[Validate(pattern: '/^\+?[0-9]{10,15}$/')]
    public string $phone = '';
}

// 验证器
class Validator
{
    public static function validate(object $dto): array
    {
        $errors = [];
        $reflection = new \ReflectionClass($dto);

        foreach ($reflection->getProperties() as $property) {
            $attrs = $property->getAttributes(Validate::class);
            if (empty($attrs)) continue;

            $validate = $attrs[0]->newInstance();
            $value = $property->getValue($dto);
            $name = $property->getName();

            if ($validate->minLength !== null && mb_strlen($value) < $validate->minLength) {
                $errors[$name][] = "{$name} 至少 {$validate->minLength} 个字符";
            }
            if ($validate->maxLength !== null && mb_strlen($value) > $validate->maxLength) {
                $errors[$name][] = "{$name} 最多 {$validate->maxLength} 个字符";
            }
            if ($validate->email !== null && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
                $errors[$name][] = "{$name} 必须是有效的邮箱地址";
            }
            if ($validate->min !== null && $value < $validate->min) {
                $errors[$name][] = "{$name} 不能小于 {$validate->min}";
            }
            if ($validate->max !== null && $value > $validate->max) {
                $errors[$name][] = "{$name} 不能大于 {$validate->max}";
            }
            if ($validate->pattern !== null && !preg_match($validate->pattern, $value)) {
                $errors[$name][] = "{$name} 格式不正确";
            }
        }

        return $errors;
    }
}

// 使用
$user = new UserDTO();
$user->name = 'A';
$user->email = 'invalid';
$user->age = 200;

$errors = Validator::validate($user);
print_r($errors);
// [name] => Array ([0] => name 至少 2 个字符)
// [email] => Array ([0] => email 必须是有效的邮箱地址)
// [age] => Array ([0] => age 不能大于 150)

13.6 常见框架 Attributes

Laravel

<?php
// 路由
#[Route('/api/users')]
class UserController extends Controller
{
    #[Get('/')]
    public function index() {}

    #[Post('/')]
    public function store(StoreUserRequest $request) {}

    #[Get('/{user}')]
    public function show(User $user) {}
}

// 事件监听
class UserRegistered
{
    #[Handle('send-welcome-email')]
    public function sendWelcomeEmail(User $event): void {}
}

Symfony

<?php
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[Route('/api/users')]
class UserController
{
    #[Route('', name: 'user_index', methods: ['GET'])]
    #[IsGranted('ROLE_ADMIN')]
    public function index(): Response {}
}

Doctrine

<?php
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User
{
    #[ORM\Id, ORM\GeneratedValue, ORM\Column]
    private int $id;

    #[ORM\Column(length: 255)]
    private string $name;

    #[ORM\Column(unique: true)]
    private string $email;
}

13.7 属性实现缓存驱动

<?php
declare(strict_types=1);

namespace App\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD)]
class Cache
{
    public function __construct(
        public readonly int $ttl = 3600,
        public readonly string $pool = 'default',
    ) {}
}

// AOP 风格的缓存代理
class CachedService
{
    public function __construct(
        private readonly UserService $service,
        private readonly CacheInterface $cache,
    ) {}

    public function __call(string $method, array $args): mixed
    {
        $reflection = new \ReflectionMethod($this->service, $method);
        $attrs = $reflection->getAttributes(Cache::class);

        if (empty($attrs)) {
            return $this->service->$method(...$args);
        }

        $cache = $attrs[0]->newInstance();
        $key = sprintf('%s:%s:%s', get_class($this->service), $method, md5(serialize($args)));

        $cached = $this->cache->get($key);
        if ($cached !== null) {
            return $cached;
        }

        $result = $this->service->$method(...$args);
        $this->cache->set($key, $result, $cache->ttl);
        return $result;
    }
}

13.8 扩展阅读


上一章第 12 章 — 异常处理 下一章第 14 章 — 生成器