php-modernization

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

PHP Modernization Skill

PHP现代化指南

Modernize PHP applications to PHP 8.x with type safety, PSR compliance, and static analysis.
将PHP应用现代化至PHP 8.x,实现类型安全、PSR合规与静态分析。

Expertise Areas

专业领域

  • PHP 8.x: Constructor promotion, readonly, enums, match, attributes, union types
  • PSR/PER Compliance: Active PHP-FIG standards
  • Static Analysis: PHPStan (level 9+), PHPat, Rector, PHP-CS-Fixer
  • Type Safety: DTOs/VOs over arrays, generics via PHPDoc
  • PHP 8.x: Constructor promotion、readonly、enums、match、attributes、union types
  • PSR/PER合规: 现行PHP-FIG标准
  • 静态分析: PHPStan(等级9+)、PHPat、Rector、PHP-CS-Fixer
  • 类型安全: 用DTOs/VOs替代数组,通过PHPDoc实现泛型

PHP 8.x Features

PHP 8.x特性

Constructor Property Promotion (PHP 8.0+)

Constructor Property Promotion (PHP 8.0+)

php
// ❌ OLD
class UserService
{
    private UserRepository $userRepository;
    private LoggerInterface $logger;

    public function __construct(
        UserRepository $userRepository,
        LoggerInterface $logger
    ) {
        $this->userRepository = $userRepository;
        $this->logger = $logger;
    }
}

// ✅ NEW
final class UserService
{
    public function __construct(
        private readonly UserRepository $userRepository,
        private readonly LoggerInterface $logger,
    ) {}
}
php
// ❌ 旧写法
class UserService
{
    private UserRepository $userRepository;
    private LoggerInterface $logger;

    public function __construct(
        UserRepository $userRepository,
        LoggerInterface $logger
    ) {
        $this->userRepository = $userRepository;
        $this->logger = $logger;
    }
}

// ✅ 新写法
final class UserService
{
    public function __construct(
        private readonly UserRepository $userRepository,
        private readonly LoggerInterface $logger,
    ) {}
}

Readonly Classes (PHP 8.2+)

Readonly Classes (PHP 8.2+)

php
// ✅ All properties are implicitly readonly
final readonly class UserDTO
{
    public function __construct(
        public int $id,
        public string $name,
        public string $email,
    ) {}
}
php
// ✅ 所有属性默认只读
final readonly class UserDTO
{
    public function __construct(
        public int $id,
        public string $name,
        public string $email,
    ) {}
}

Enums (PHP 8.1+)

Enums (PHP 8.1+)

php
// ❌ OLD - String constants
class Status
{
    public const DRAFT = 'draft';
    public const PUBLISHED = 'published';
    public const ARCHIVED = 'archived';
}

// ✅ NEW - Backed enum
enum Status: string
{
    case Draft = 'draft';
    case Published = 'published';
    case Archived = 'archived';

    public function label(): string
    {
        return match($this) {
            self::Draft => 'Draft',
            self::Published => 'Published',
            self::Archived => 'Archived',
        };
    }
}

// Usage
public function setStatus(Status $status): void
{
    $this->status = $status;
}

$item->setStatus(Status::Published);
php
// ❌ 旧写法 - 字符串常量
class Status
{
    public const DRAFT = 'draft';
    public const PUBLISHED = 'published';
    public const ARCHIVED = 'archived';
}

// ✅ 新写法 - 带返回值的枚举
enum Status: string
{
    case Draft = 'draft';
    case Published = 'published';
    case Archived = 'archived';

    public function label(): string
    {
        return match($this) {
            self::Draft => 'Draft',
            self::Published => 'Published',
            self::Archived => 'Archived',
        };
    }
}

// 使用示例
public function setStatus(Status $status): void
{
    $this->status = $status;
}

$item->setStatus(Status::Published);

Match Expression (PHP 8.0+)

Match Expression (PHP 8.0+)

php
// ❌ OLD - Switch
switch ($type) {
    case 'a':
        $result = 'Type A';
        break;
    case 'b':
        $result = 'Type B';
        break;
    default:
        $result = 'Unknown';
}

// ✅ NEW - Match
$result = match($type) {
    'a' => 'Type A',
    'b' => 'Type B',
    default => 'Unknown',
};
php
// ❌ 旧写法 - Switch语句
switch ($type) {
    case 'a':
        $result = 'Type A';
        break;
    case 'b':
        $result = 'Type B';
        break;
    default:
        $result = 'Unknown';
}

// ✅ 新写法 - Match表达式
$result = match($type) {
    'a' => 'Type A',
    'b' => 'Type B',
    default => 'Unknown',
};

Named Arguments (PHP 8.0+)

Named Arguments (PHP 8.0+)

php
// ✅ Clearer and order-independent
$this->doSomething(
    name: 'value',
    options: ['key' => 'value'],
    enabled: true,
);
php
// ✅ 更清晰且无需关注参数顺序
$this->doSomething(
    name: 'value',
    options: ['key' => 'value'],
    enabled: true,
);

Null Safe Operator (PHP 8.0+)

Null Safe Operator (PHP 8.0+)

php
// ❌ OLD
$country = null;
if ($user !== null && $user->getAddress() !== null) {
    $country = $user->getAddress()->getCountry();
}

// ✅ NEW
$country = $user?->getAddress()?->getCountry();
php
// ❌ 旧写法
$country = null;
if ($user !== null && $user->getAddress() !== null) {
    $country = $user->getAddress()->getCountry();
}

// ✅ 新写法
$country = $user?->getAddress()?->getCountry();

Union Types (PHP 8.0+)

Union Types (PHP 8.0+)

php
public function process(string|int $value): string|null
{
    // ...
}
php
public function process(string|int $value): string|null
{
    // ...
}

Intersection Types (PHP 8.1+)

Intersection Types (PHP 8.1+)

php
public function handle(Countable&Iterator $collection): void
{
    // $collection must implement both interfaces
}
php
public function handle(Countable&Iterator $collection): void
{
    // $collection必须同时实现两个接口
}

Attributes (PHP 8.0+)

Attributes (PHP 8.0+)

php
use TYPO3\CMS\Core\Attribute\AsEventListener;

#[AsEventListener(identifier: 'myext/my-listener')]
final class MyListener
{
    public function __invoke(SomeEvent $event): void
    {
        // Handle event
    }
}
php
use TYPO3\CMS\Core\Attribute\AsEventListener;

#[AsEventListener(identifier: 'myext/my-listener')]
final class MyListener
{
    public function __invoke(SomeEvent $event): void
    {
        // 处理事件
    }
}

DTOs and Value Objects

DTO与值对象

Never Use Arrays for Structured Data

切勿使用数组传递结构化数据

php
// ❌ BAD - Array passing
public function createUser(array $data): array
{
    // What fields are expected? What types?
}

// ✅ GOOD - DTO pattern
public function createUser(CreateUserDTO $dto): UserDTO
{
    // Type-safe, documented, IDE-friendly
}
php
// ❌ 不良写法 - 传递数组
public function createUser(array $data): array
{
    // 预期哪些字段?类型是什么?
}

// ✅ 推荐写法 - DTO模式
public function createUser(CreateUserDTO $dto): UserDTO
{
    // 类型安全、文档完善、IDE友好
}

Data Transfer Object

数据传输对象(DTO)

php
<?php

declare(strict_types=1);

namespace Vendor\MyExtension\DTO;

final readonly class CreateUserDTO
{
    public function __construct(
        public string $name,
        public string $email,
        public ?string $phone = null,
    ) {}

    public static function fromArray(array $data): self
    {
        return new self(
            name: $data['name'] ?? throw new \InvalidArgumentException('Name required'),
            email: $data['email'] ?? throw new \InvalidArgumentException('Email required'),
            phone: $data['phone'] ?? null,
        );
    }
}
php
<?php

declare(strict_types=1);

namespace Vendor\MyExtension\DTO;

final readonly class CreateUserDTO
{
    public function __construct(
        public string $name,
        public string $email,
        public ?string $phone = null,
    ) {}

    public static function fromArray(array $data): self
    {
        return new self(
            name: $data['name'] ?? throw new \InvalidArgumentException('名称为必填项'),
            email: $data['email'] ?? throw new \InvalidArgumentException('邮箱为必填项'),
            phone: $data['phone'] ?? null,
        );
    }
}

Value Object

值对象

php
<?php

declare(strict_types=1);

namespace Vendor\MyExtension\ValueObject;

final readonly class EmailAddress
{
    private function __construct(
        public string $value,
    ) {}

    public static function fromString(string $email): self
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new \InvalidArgumentException('Invalid email address');
        }

        return new self($email);
    }

    public function equals(self $other): bool
    {
        return $this->value === $other->value;
    }

    public function __toString(): string
    {
        return $this->value;
    }
}
php
<?php

declare(strict_types=1);

namespace Vendor\MyExtension\ValueObject;

final readonly class EmailAddress
{
    private function __construct(
        public string $value,
    ) {}

    public static function fromString(string $email): self
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new \InvalidArgumentException('无效邮箱地址');
        }

        return new self($email);
    }

    public function equals(self $other): bool
    {
        return $this->value === $other->value;
    }

    public function __toString(): string
    {
        return $this->value;
    }
}

PSR/PER Compliance

PSR/PER合规

Active Standards

现行标准

StandardPurposeStatus
PSR-1Basic CodingRequired
PSR-4AutoloadingRequired
PER CSCoding StyleRequired (supersedes PSR-12)
PSR-3Logger InterfaceUse for logging
PSR-6/16CacheUse for caching
PSR-7/17/18HTTPUse for HTTP clients
PSR-11ContainerUse for DI
PSR-14EventsUse for event dispatching
PSR-15MiddlewareUse for HTTP middleware
PSR-20ClockUse for time-dependent code
标准用途状态
PSR-1基础编码规范强制要求
PSR-4自动加载规范强制要求
PER CS编码风格强制要求(替代PSR-12)
PSR-3日志接口用于日志记录
PSR-6/16缓存规范用于缓存实现
PSR-7/17/18HTTP规范用于HTTP客户端
PSR-11容器规范用于依赖注入
PSR-14事件规范用于事件分发
PSR-15中间件规范用于HTTP中间件
PSR-20时钟规范用于时间相关代码

PER Coding Style

PER编码风格示例

php
<?php

declare(strict_types=1);

namespace Vendor\Package;

use Vendor\Package\SomeClass;

final class MyClass
{
    public function __construct(
        private readonly SomeClass $dependency,
    ) {}

    public function doSomething(
        string $param1,
        int $param2,
    ): string {
        return match ($param2) {
            1 => $param1,
            2 => $param1 . $param1,
            default => '',
        };
    }
}
php
<?php

declare(strict_types=1);

namespace Vendor\Package;

use Vendor\Package\SomeClass;

final class MyClass
{
    public function __construct(
        private readonly SomeClass $dependency,
    ) {}

    public function doSomething(
        string $param1,
        int $param2,
    ): string {
        return match ($param2) {
            1 => $param1,
            2 => $param1 . $param1,
            default => '',
        };
    }
}

Static Analysis Tools

静态分析工具

PHPStan (Level 9+)

PHPStan(等级9+)

neon
undefined
neon
undefined

phpstan.neon

phpstan.neon

includes: - vendor/phpstan/phpstan-strict-rules/rules.neon - vendor/saschaegerer/phpstan-typo3/extension.neon
parameters: level: 10 paths: - Classes - Tests excludePaths: - Classes/Domain/Model/*

**Level Guide:**
- Level 0-5: Basic checks
- Level 6-8: Type checking
- Level 9: Strict mixed handling
- Level 10: Maximum strictness (recommended)
includes: - vendor/phpstan/phpstan-strict-rules/rules.neon - vendor/saschaegerer/phpstan-typo3/extension.neon
parameters: level: 10 paths: - Classes - Tests excludePaths: - Classes/Domain/Model/*

**等级指南:**
- 等级0-5:基础检查
- 等级6-8:类型检查
- 等级9:严格混合类型处理
- 等级10:最高严格度(推荐)

PHP-CS-Fixer

PHP-CS-Fixer

php
<?php
// .php-cs-fixer.dist.php
$config = new PhpCsFixer\Config();

return $config
    ->setRules([
        '@PER-CS' => true,
        '@PER-CS:risky' => true,
        'declare_strict_types' => true,
        'no_unused_imports' => true,
        'ordered_imports' => ['sort_algorithm' => 'alpha'],
        'single_line_empty_body' => true,
        'trailing_comma_in_multiline' => [
            'elements' => ['arguments', 'arrays', 'match', 'parameters'],
        ],
    ])
    ->setRiskyAllowed(true)
    ->setFinder(
        PhpCsFixer\Finder::create()
            ->in(__DIR__ . '/Classes')
            ->in(__DIR__ . '/Tests')
    );
php
<?php
// .php-cs-fixer.dist.php
$config = new PhpCsFixer\Config();

return $config
    ->setRules([
        '@PER-CS' => true,
        '@PER-CS:risky' => true,
        'declare_strict_types' => true,
        'no_unused_imports' => true,
        'ordered_imports' => ['sort_algorithm' => 'alpha'],
        'single_line_empty_body' => true,
        'trailing_comma_in_multiline' => [
            'elements' => ['arguments', 'arrays', 'match', 'parameters'],
        ],
    ])
    ->setRiskyAllowed(true)
    ->setFinder(
        PhpCsFixer\Finder::create()
            ->in(__DIR__ . '/Classes')
            ->in(__DIR__ . '/Tests')
    );

Rector

Rector

php
<?php
// rector.php
declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\LevelSetList;
use Rector\Set\ValueObject\SetList;

return RectorConfig::configure()
    ->withPaths([
        __DIR__ . '/Classes',
        __DIR__ . '/Tests',
    ])
    ->withSets([
        LevelSetList::UP_TO_PHP_83,
        SetList::CODE_QUALITY,
        SetList::TYPE_DECLARATION,
        SetList::DEAD_CODE,
    ]);
php
<?php
// rector.php
declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\LevelSetList;
use Rector\Set\ValueObject\SetList;

return RectorConfig::configure()
    ->withPaths([
        __DIR__ . '/Classes',
        __DIR__ . '/Tests',
    ])
    ->withSets([
        LevelSetList::UP_TO_PHP_83,
        SetList::CODE_QUALITY,
        SetList::TYPE_DECLARATION,
        SetList::DEAD_CODE,
    ]);

PHPat (Architecture Testing)

PHPat(架构测试)

php
<?php

declare(strict_types=1);

namespace Vendor\MyExtension\Tests\Architecture;

use PHPat\Selector\Selector;
use PHPat\Test\Builder\Rule;
use PHPat\Test\PHPat;

final class ArchitectureTest
{
    public function testDomainDoesNotDependOnInfrastructure(): Rule
    {
        return PHPat::rule()
            ->classes(Selector::inNamespace('Vendor\MyExtension\Domain'))
            ->shouldNotDependOn()
            ->classes(Selector::inNamespace('Vendor\MyExtension\Infrastructure'));
    }
}
php
<?php

declare(strict_types=1);

namespace Vendor\MyExtension\Tests\Architecture;

use PHPat\Selector\Selector;
use PHPat\Test\Builder\Rule;
use PHPat\Test\PHPat;

final class ArchitectureTest
{
    public function testDomainDoesNotDependOnInfrastructure(): Rule
    {
        return PHPat::rule()
            ->classes(Selector::inNamespace('Vendor\MyExtension\Domain'))
            ->shouldNotDependOn()
            ->classes(Selector::inNamespace('Vendor\MyExtension\Infrastructure'));
    }
}

Type Safety Patterns

类型安全模式

Typed Arrays with PHPDoc Generics

基于PHPDoc泛型的类型化数组

php
/**
 * @return array<int, User>
 */
public function getUsers(): array
{
    return $this->users;
}

/**
 * @param array<string, mixed> $config
 */
public function configure(array $config): void
{
    // ...
}

/**
 * @return \Generator<int, Item, mixed, void>
 */
public function iterateItems(): \Generator
{
    foreach ($this->items as $item) {
        yield $item;
    }
}
php
/**
 * @return array<int, User>
 */
public function getUsers(): array
{
    return $this->users;
}

/**
 * @param array<string, mixed> $config
 */
public function configure(array $config): void
{
    // ...
}

/**
 * @return \Generator<int, Item, mixed, void>
 */
public function iterateItems(): \Generator
{
    foreach ($this->items as $item) {
        yield $item;
    }
}

Strict Comparison

严格比较

php
// ❌ Loose comparison
if ($value == '1') {}

// ✅ Strict comparison
if ($value === '1') {}
if ($value === 1) {}
php
// ❌ 松散比较
if ($value == '1') {}

// ✅ 严格比较
if ($value === '1') {}
if ($value === 1) {}

Early Returns

提前返回

php
// ❌ Nested conditions
public function process(?User $user): ?Result
{
    if ($user !== null) {
        if ($user->isActive()) {
            return $this->doProcess($user);
        }
    }
    return null;
}

// ✅ Early returns
public function process(?User $user): ?Result
{
    if ($user === null) {
        return null;
    }

    if (!$user->isActive()) {
        return null;
    }

    return $this->doProcess($user);
}
php
// ❌ 嵌套条件
public function process(?User $user): ?Result
{
    if ($user !== null) {
        if ($user->isActive()) {
            return $this->doProcess($user);
        }
    }
    return null;
}

// ✅ 提前返回
public function process(?User $user): ?Result
{
    if ($user === null) {
        return null;
    }

    if (!$user->isActive()) {
        return null;
    }

    return $this->doProcess($user);
}

Migration Checklist

迁移检查清单

  • declare(strict_types=1)
    in all files
  • PSR-4 autoloading in composer.json
  • PER Coding Style enforced via PHP-CS-Fixer
  • PHPStan level 9+ (level 10 for new projects)
  • All methods have return types
  • All parameters have type declarations
  • All properties have type declarations
  • DTOs for data transfer, Value Objects for domain concepts
  • Enums for fixed sets of values (not string constants)
  • Constructor property promotion used
  • final
    on classes not designed for inheritance
  • readonly
    on immutable classes
  • No
    @var
    annotations when type is declared
  • PHPat architecture tests for layer dependencies
  • 所有文件添加
    declare(strict_types=1)
  • composer.json中配置PSR-4自动加载
  • 通过PHP-CS-Fixer强制执行PER编码风格
  • PHPStan等级9+(新项目推荐等级10)
  • 所有方法添加返回类型
  • 所有参数添加类型声明
  • 所有属性添加类型声明
  • 用DTO传递数据,用值对象表示领域概念
  • 用Enums表示固定值集合(而非字符串常量)
  • 使用构造函数属性提升
  • 对不用于继承的类添加
    final
    关键字
  • 对不可变类添加
    readonly
    关键字
  • 已声明类型的属性不再使用
    @var
    注解
  • 用PHPat编写架构测试,确保层间依赖合规

Resources

资源

Credits & Attribution

致谢与版权

Thanks to Netresearch DTT GmbH for their contributions to the TYPO3 community.
感谢Netresearch DTT GmbH为TYPO3社区做出的贡献。