php-modernization
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePHP 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
现行标准
| Standard | Purpose | Status |
|---|---|---|
| PSR-1 | Basic Coding | Required |
| PSR-4 | Autoloading | Required |
| PER CS | Coding Style | Required (supersedes PSR-12) |
| PSR-3 | Logger Interface | Use for logging |
| PSR-6/16 | Cache | Use for caching |
| PSR-7/17/18 | HTTP | Use for HTTP clients |
| PSR-11 | Container | Use for DI |
| PSR-14 | Events | Use for event dispatching |
| PSR-15 | Middleware | Use for HTTP middleware |
| PSR-20 | Clock | Use for time-dependent code |
| 标准 | 用途 | 状态 |
|---|---|---|
| PSR-1 | 基础编码规范 | 强制要求 |
| PSR-4 | 自动加载规范 | 强制要求 |
| PER CS | 编码风格 | 强制要求(替代PSR-12) |
| PSR-3 | 日志接口 | 用于日志记录 |
| PSR-6/16 | 缓存规范 | 用于缓存实现 |
| PSR-7/17/18 | HTTP规范 | 用于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
undefinedneon
undefinedphpstan.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
迁移检查清单
- in all files
declare(strict_types=1) - 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
- on classes not designed for inheritance
final - on immutable classes
readonly - No annotations when type is declared
@var - 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
资源
- PHP-FIG: https://www.php-fig.org/
- PHPStan: https://phpstan.org/
- Rector: https://getrector.com/
- PHP-CS-Fixer: https://cs.symfony.com/
- PHPat: https://www.phpat.dev/
- PHP-FIG: https://www.php-fig.org/
- PHPStan: https://phpstan.org/
- Rector: https://getrector.com/
- PHP-CS-Fixer: https://cs.symfony.com/
- PHPat: https://www.phpat.dev/
Credits & Attribution
致谢与版权
Thanks to Netresearch DTT GmbH for their contributions to the TYPO3 community.
感谢Netresearch DTT GmbH为TYPO3社区做出的贡献。