本文目录导读:

在PHP项目中实现设计模式检测,通常有几种不同的方法,从简单的代码审查到使用专门的静态分析工具,以下是几种常见的实现方式:
使用静态分析工具
PHPStan 自定义规则
// 检测单例模式
class SingletonRule implements \PHPStan\Rules\Rule
{
public function getNodeType(): string
{
return \PhpParser\Node\Stmt\ClassMethod::class;
}
public function processNode(
\PhpParser\Node $node,
\PHPStan\Analyser\Scope $scope
): array
{
$errors = [];
if ($node->name->name === '__construct' &&
$node->isPrivate()) {
// 检查是否有静态的getInstance方法
$class = $scope->getClassReflection();
if ($class && !$class->hasNativeMethod('getInstance')) {
$errors[] = \PHPStan\Rules\RuleErrorBuilder::message(
'私有构造函数但没有getInstance方法,可能不是单例模式'
)->build();
}
}
return $errors;
}
}
PHP_CodeSniffer 自定义嗅探器
class FactoryPatternSniff implements \PHP_CodeSniffer\Sniffs\Sniff
{
public function register()
{
return [T_CLASS];
}
public function process(\PHP_CodeSniffer\Files\File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$className = $phpcsFile->getDeclarationName($stackPtr);
// 检测工厂模式:查找返回类型为self或类的静态方法
for ($i = $stackPtr + 1; $i < $tokens[$stackPtr]['scope_closer']; $i++) {
if ($tokens[$i]['code'] === T_STATIC &&
$tokens[$i + 2]['code'] === T_FUNCTION) {
// 检查方法是否返回特定类型
// ...
}
}
}
}
使用反射实现运行时检测
class DesignPatternDetector
{
public function detectSingleton(string $className): bool
{
try {
$reflection = new \ReflectionClass($className);
// 检查私有构造函数
$constructor = $reflection->getConstructor();
if (!$constructor || !$constructor->isPrivate()) {
return false;
}
// 检查静态的getInstance方法
if (!$reflection->hasMethod('getInstance')) {
return false;
}
$method = $reflection->getMethod('getInstance');
return $method->isStatic() && $method->isPublic();
} catch (\ReflectionException $e) {
return false;
}
}
public function detectFactory(string $className): bool
{
$reflection = new \ReflectionClass($className);
$methods = $reflection->getMethods(\ReflectionMethod::IS_STATIC | \ReflectionMethod::IS_PUBLIC);
foreach ($methods as $method) {
// 检查是否有返回自身或派生类的静态工厂方法
$returnType = $method->getReturnType();
if ($returnType && !$returnType->isBuiltin()) {
$returnTypeName = $returnType->getName();
if (is_subclass_of($returnTypeName, $className) ||
$returnTypeName === $className) {
return true;
}
}
}
return false;
}
}
基于AST的检测
use PhpParser\{ParserFactory, NodeTraverser, NodeVisitorAbstract};
class PatternDetectorVisitor extends NodeVisitorAbstract
{
private $patterns = [];
public function enterNode(Node $node)
{
// 检测观察者模式
if ($node instanceof Node\Stmt\Class_) {
$this->detectObserverPattern($node);
$this->detectAdapterPattern($node);
$this->detectStrategyPattern($node);
}
// 检测策略模式(接口+多个实现)
if ($node instanceof Node\Stmt\Interface_) {
$this->checkForStrategyInterface($node);
}
}
private function detectObserverPattern(Node\Stmt\Class_ $class)
{
// 检查是否实现了SplSubject或SplObserver
foreach ($class->implements as $implement) {
$name = $implement->toString();
if (in_array($name, ['SplSubject', 'SplObserver'])) {
$this->patterns[] = [
'type' => 'Observer',
'class' => $class->name->toString(),
'line' => $class->getLine()
];
}
}
// 检查是否有attach/detach/notify方法
$hasAttach = $hasDetach = $hasNotify = false;
foreach ($class->stmts as $stmt) {
if ($stmt instanceof Node\Stmt\ClassMethod) {
switch ($stmt->name->name) {
case 'attach': $hasAttach = true; break;
case 'detach': $hasDetach = true; break;
case 'notify': $hasNotify = true; break;
}
}
}
if ($hasAttach && $hasDetach && $hasNotify) {
$this->patterns[] = [
'type' => 'Observer (Conventional)',
'class' => $class->name->toString(),
'line' => $class->getLine()
];
}
}
}
// 使用示例
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
$traverser = new NodeTraverser();
$visitor = new PatternDetectorVisitor();
$traverser->addVisitor($visitor);
$code = file_get_contents('your_file.php');
$ast = $parser->parse($code);
$traverser->traverse($ast);
print_r($visitor->getPatterns());
自动化测试脚本
<?php
require 'vendor/autoload.php';
class DesignPatternTest extends \PHPUnit\Framework\TestCase
{
private $detector;
protected function setUp(): void
{
$this->detector = new DesignPatternDetector();
}
/**
* @dataProvider singletonProvider
*/
public function testSingletonPattern(string $className)
{
$this->assertTrue(
$this->detector->detectSingleton($className),
"{$className} should be a singleton"
);
}
public function singletonProvider(): array
{
return [
[Database::class],
[Logger::class],
[Cache::class],
];
}
public function testAllClassesForPatterns()
{
$directory = new RecursiveDirectoryIterator('./src');
$iterator = new RecursiveIteratorIterator($directory);
$regex = new RegexIterator($iterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH);
foreach ($regex as $file) {
$className = $this->getClassNameFromFile($file[0]);
if ($className) {
$this->detectPatternsInClass($className);
}
}
}
private function detectPatternsInClass(string $className)
{
// 检测各种模式
if ($this->detector->detectSingleton($className)) {
echo "Singletion: {$className}\n";
}
// ... 其他模式检测
}
}
Git Hooks 集成
#!/bin/bash
# pre-commit hook 检测设计模式
echo "Running design pattern detection..."
# 使用自定义PHP脚本
php pattern-detector.php --check-modified-files
if [ $? -ne 0 ]; then
echo "Design pattern violations found!"
echo "Please fix before committing."
exit 1
fi
完整的检测类实现
class DesignPatternAnalyzer
{
private $patterns = [];
private $classes = [];
public function analyzeDirectory(string $path): array
{
$this->scanDirectory($path);
$this->analyzeClasses();
return $this->patterns;
}
private function scanDirectory(string $path): void
{
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path)
);
foreach ($files as $file) {
if ($file->getExtension() === 'php') {
$this->analyzeFile($file->getRealPath());
}
}
}
private function analyzeFile(string $filePath): void
{
$content = file_get_contents($filePath);
$tokens = token_get_all($content);
// 简单的模式识别
if (preg_match('/class\s+(\w+)/', $content, $matches)) {
$className = $matches[1];
// 检测单例模式
if (preg_match('/private\s+function\s+__construct/', $content) &&
preg_match('/public\s+static\s+function\s+getInstance/', $content)) {
$this->patterns[] = [
'pattern' => 'Singleton',
'class' => $className,
'file' => $filePath
];
}
// 检测工厂模式
if (preg_match('/interface\s+(\w+Factory)/', $content) ||
preg_match('/abstract\s+class\s+(\w+Factory)/', $content)) {
$this->patterns[] = [
'pattern' => 'Factory',
'class' => $className,
'file' => $filePath
];
}
}
}
}
使用建议
- 开发阶段:使用PHPStan或PHP_CodeSniffer自定义规则
- CI/CD流程:集成自动化检测脚本
- 代码审查:使用反射检测工具辅助人工审查
- 大型项目:结合AST分析工具进行全面检测
这些方法可以帮助你在PHP项目中实现自动化的设计模式检测,提高代码质量和一致性。