重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
php-modernization by dirnbauer/webconsulting-skills
npx skills add https://github.com/dirnbauer/webconsulting-skills --skill php-modernization将 PHP 应用程序现代化至 PHP 8.x,具备类型安全、PSR 合规性和静态分析。
// ❌ 旧方式
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,
) {}
}
// ✅ 所有属性隐式只读
final readonly class UserDTO
{
public function __construct(
public int $id,
public string $name,
public string $email,
) {}
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
// ❌ 旧方式 - 字符串常量
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 => '草稿',
self::Published => '已发布',
self::Archived => '已归档',
};
}
}
// 用法
public function setStatus(Status $status): void
{
$this->status = $status;
}
$item->setStatus(Status::Published);
// ❌ 旧方式 - Switch
switch ($type) {
case 'a':
$result = '类型 A';
break;
case 'b':
$result = '类型 B';
break;
default:
$result = '未知';
}
// ✅ 新方式 - Match
$result = match($type) {
'a' => '类型 A',
'b' => '类型 B',
default => '未知',
};
// ✅ 更清晰且顺序无关
$this->doSomething(
name: 'value',
options: ['key' => 'value'],
enabled: true,
);
// ❌ 旧方式
$country = null;
if ($user !== null && $user->getAddress() !== null) {
$country = $user->getAddress()->getCountry();
}
// ✅ 新方式
$country = $user?->getAddress()?->getCountry();
public function process(string|int $value): string|null
{
// ...
}
public function handle(Countable&Iterator $collection): void
{
// $collection 必须同时实现两个接口
}
use TYPO3\CMS\Core\Attribute\AsEventListener;
#[AsEventListener(identifier: 'myext/my-listener')]
final class MyListener
{
public function __invoke(SomeEvent $event): void
{
// 处理事件
}
}
// ❌ 不好 - 传递数组
public function createUser(array $data): array
{
// 期望哪些字段?什么类型?
}
// ✅ 好 - DTO 模式
public function createUser(CreateUserDTO $dto): UserDTO
{
// 类型安全、有文档、IDE 友好
}
<?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
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;
}
}
| 标准 | 用途 | 状态 |
|---|---|---|
| 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 | 时钟 | 用于时间相关代码 |
<?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 => '',
};
}
}
# 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/*
级别指南:
<?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
// 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
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'));
}
}
/**
* @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;
}
}
// ❌ 松散比较
if ($value == '1') {}
// ✅ 严格比较
if ($value === '1') {}
if ($value === 1) {}
// ❌ 嵌套条件
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);
}
declare(strict_types=1)finalreadonly@var 注解感谢 Netresearch DTT GmbH 对 TYPO3 社区的贡献。
周安装量
48
代码仓库
GitHub 星标
13
首次出现
2026年1月24日
安全审计
安装于
opencode43
codex43
gemini-cli41
github-copilot38
cursor37
claude-code37
Modernize PHP applications to PHP 8.x with type safety, PSR compliance, and static analysis.
// ❌ 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,
) {}
}
// ✅ All properties are implicitly readonly
final readonly class UserDTO
{
public function __construct(
public int $id,
public string $name,
public string $email,
) {}
}
// ❌ 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);
// ❌ 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',
};
// ✅ Clearer and order-independent
$this->doSomething(
name: 'value',
options: ['key' => 'value'],
enabled: true,
);
// ❌ OLD
$country = null;
if ($user !== null && $user->getAddress() !== null) {
$country = $user->getAddress()->getCountry();
}
// ✅ NEW
$country = $user?->getAddress()?->getCountry();
public function process(string|int $value): string|null
{
// ...
}
public function handle(Countable&Iterator $collection): void
{
// $collection must implement both interfaces
}
use TYPO3\CMS\Core\Attribute\AsEventListener;
#[AsEventListener(identifier: 'myext/my-listener')]
final class MyListener
{
public function __invoke(SomeEvent $event): void
{
// Handle event
}
}
// ❌ 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
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
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;
}
}
| 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 |
<?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 => '',
};
}
}
# 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:
<?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
// 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
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'));
}
}
/**
* @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;
}
}
// ❌ Loose comparison
if ($value == '1') {}
// ✅ Strict comparison
if ($value === '1') {}
if ($value === 1) {}
// ❌ 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);
}
declare(strict_types=1) in all filesfinal on classes not designed for inheritancereadonly on immutable classes@var annotations when type is declaredThanks to Netresearch DTT GmbH for their contributions to the TYPO3 community.
Weekly Installs
48
Repository
GitHub Stars
13
First Seen
Jan 24, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode43
codex43
gemini-cli41
github-copilot38
cursor37
claude-code37
Laravel架构模式指南:生产级开发模式与最佳实践
1,400 周安装
| Use for time-dependent code |