🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
#如何为PHPMD编写规则 这篇文章教你怎样给PHPMD写规则类。这些类可以用来检查被分析代码中的设计问题或错误。 我们从PHPMD的一些基础结构开始。PHPMD的所有规则一定是至少实现了[\PHPMD\Rule](https://github.com/phpmd/phpmd/blob/master/src/main/php/PHPMD/Rule.php)接口。也可以扩展抽象基类[\PHPMD\AbstractRule](https://github.com/phpmd/phpmd/blob/master/src/main/php/PHPMD/AbstractRule.php),这个类已经提供了所有必需的基础设施的方法和应用逻辑的实现,所以你的唯一任务就是为规则编写具体的验证代码。PHPMD规则接口声明了apply()方法,在代码分析阶段会被应用调用到,从而执行验证代码。 ```php require_once 'PHPMD/AbstractRule.php'; class Com_Example_Rule_NoFunctions extends \PHPMD\AbstractRule { public function apply(\PHPMD\AbstractNode $node) { // Check constraints against the given node instance } } ``` apply()方法的参数是\PHPMD\AbstractNode实例。这个实例表达被分析代码中发现的不同的高级别代码构件。在这种情况下,高级别代码构件意味着接口、类、方法和函数。我们当然不想在每一个规则中都重复执行决策代码,但是要怎样告诉PHPMD,这些代码构件中,哪个才是我们规则感兴趣的?为了解决这个问题,PHPMD使用所谓的标记接口。这些接口的唯一目的是给规则类打标签。下面的列表显示可用的标记接口: - [\PHPMD\Rule\ClassAware](https://github.com/phpmd/phpmd/blob/master/src/main/php/PHPMD/Rule/ClassAware.php) - [\PHPMD\Rule\FunctionAware](https://github.com/phpmd/phpmd/blob/master/src/main/php/PHPMD/Rule/FunctionAware.php) - [\PHPMD\Rule\InterfaceAware](https://github.com/phpmd/phpmd/blob/master/src/main/php/PHPMD/Rule/InterfaceAware.php) - [\PHPMD\Rule\MethodAware](https://github.com/phpmd/phpmd/blob/master/src/main/php/PHPMD/Rule/MethodAware.php) 有了这个标记接口,我们就可以扩展上一个例子,这样,该规则将在分析被测代码中发现函数时调用到。 ```php class Com_Example_Rule_NoFunctions extends \PHPMD\AbstractRule implements \PHPMD\Rule\FunctionAware { public function apply(\PHPMD\AbstractNode $node) { // Check constraints against the given node instance } } ``` 假定我们的编码准则禁止使用函数,每一次调用函数都会导致apply()方法产生一个规则违反信息。 PHPMD通过addViolation()方法将其报告出来。规则类从父类PHPMD\AbstractRule继承这个助手方法。 ```php class Com_Example_Rule_NoFunctions // ... { public function apply(\PHPMD\AbstractNode $node) { $this->addViolation($node); } } ``` 现在只需要在规则集中加入配置入口。规则集文件是一个XML文档,可以配置一个或多个规则,可以配置全部规则设置,所以每个人都可以改造现有的规则,而无需改变其本身。本规则集文件的语法全部改变自PHPMD的示例。开始使用自定义规则集之前,你应该看看现有的XML文件,再调整新创建规则的配置。规则配置中最重要的元素是: - @name: 易读规则名。 - @message: 错误/违规信息。 - @class: 规则的全称类名。 - priority: 规则优先权,值可以为1-5,1是最高优先权,5最低。 ```xml <ruleset name="example.com rules" xmlns="http://pmd.sf.net/ruleset/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd" xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd"> <rule name="FunctionRule" message = "Please do not use functions." class="Com_Example_Rule_NoFunctions" externalInfoUrl="http://example.com/phpmd/rules.html#functionrule"> <priority>1</priority> </rule> </ruleset> ``` 上面清单显示了一个基本规则集文件,更多细节请参考[创建自定义规则集](http://phpmd.org/documentation/creating-a-ruleset.html)。 假设我们的Com/Example/Rule/NoFunction.php是一个规则类, example-rule.xml是一个规则集。可以从命令行中测试: ```bash ~ $ phpmd /my/source/example.com text /my/rules/example-rule.xml /my/source/example.com/functions.php:2 Please do not use functions. ``` 搞定。现在我们有了PHPMD的第一个自定义规则类。 ## 根据现有的软件度量编写规则 开发PHPMD的根本目标是为PHP_Depend实现一个简单友好的接口。本节你将会看到,如何开发一个规则类,并以输入数据的形式使用PDepend作为软件度量。 你会看到如何访问一个给定的\PHPMD\AbstractNode实例软件计量,以及如何使用phpmd配置后端,阈值等设置可定制而不改变任何PHP代码。此外,您将看到怎样改进错误信息的内容。 现在新规则需要一个软件度量标准作基础。完整并及时更新的可用软件计量标准列表可以从PHP_Depend的计量类目获得。在这里,我们选公共方法个数(npm:Number of Public Methods)标准,然后定义规则的上下限。我们设置上限10,如果存在更多的公共方法会暴露更多的类的内部信息,那么它应该被分成几个类了。下限可以设为1,因为如果类没有公有方法,那它就不会为周边应用提供任何服务。 下面的代码清单显示整个规则类的骨架。你可以看到,它实现了\PHPMD\Rule\ClassAware接口,所以PHPMD知道这个规则只能被类调用。 ```php class Com_Example_Rule_NumberOfPublicMethods extends \PHPMD\AbstractRule implements \PHPMD\Rule\ClassAware { const MINIMUM = 1, MAXIMUM = 10; public function apply(\PHPMD\AbstractNode $node) { // Check constraints against the given node instance } } ``` 接下来使用nmp规则对象,它和节点实例是关联的,所有节点计算的规则对象都可以使用节点实例的getMetric()方法直接访问。getMetric()接受一个参数,即规则的缩写。这些缩写可以从PHP_Depends规则分类中找到。 ```php class Com_Example_Rule_NumberOfPublicMethods extends \PHPMD\AbstractRule implements \PHPMD\Rule\ClassAware { const MINIMUM = 1, MAXIMUM = 10; public function apply(\PHPMD\AbstractNode $node) { $npm = $node->getMetric('npm'); if ($npm < self::MINIMUM || $npm > self::MAXIMUM) { $this->addViolation($node); } } } ``` 以上就是规则的代码部分。现在我们把它加到规则集文件中。 ```xml <ruleset name="example.com rules" xmlns="http://pmd.sf.net/ruleset/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd" xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd"> <!-- ... --> <rule name="NumberOfPublics" message = "The context class violates the NPM metric." class="Com_Example_Rule_NumberOfPublicMethods" externalInfoUrl="http://example.com/phpmd/rules.html#numberofpublics"> <priority>3</priority> </rule> </ruleset> ``` 现在运行PHPMD,它会报告不满足NPM规则的所有类。但正如我们所承诺的,我们将使这个规则更加可定制,以便可以调整它适应不同的项目要求。我们将MINIMUM和MAXIMUM替换为在规则集文件中可以配置的属性。 ```xml <ruleset name="example.com rules" xmlns="http://pmd.sf.net/ruleset/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd" xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd"> <!-- ... --> <rule name="NumberOfPublics" message = "The context class violates the NPM metric." class="Com_Example_Rule_NumberOfPublicMethods" externalInfoUrl="http://example.com/phpmd/rules.html#numberofpublics"> <priority>3</priority> <properties> <property name="minimum" value="1" description="Minimum number of public methods." /> <property name="maximum" value="10" description="Maximum number of public methods." /> </properties> </rule> </ruleset> ``` PMD规则集文件中你可以随便定义几个属性,随你喜欢。它们会被PHPMD运行时环境注入到一个规则实例中,然后可以通过get<type>Property()方法访问。当前PHPMD支持以下几个getter方法。 getBooleanProperty() getIntProperty() (好像也只有两个。。。上面的话真不要脸) 现在我们来修改规则类,用可配置属性替换硬编码常量。 ```php class Com_Example_Rule_NumberOfPublicMethods extends \PHPMD\AbstractRule implements \PHPMD\Rule\ClassAware { public function apply(\PHPMD\AbstractNode $node) { $npm = $node->getMetric('npm'); if ($npm < $this->getIntProperty('minimum') || $npm > $this->getIntProperty('maximum') ) { $this->addViolation($node); } } } ``` 现在我们差不多完成了,还有一个小小细节。我们执行这个规则,用户将收到消息“The context class violates the NPM metric.“这个消息不是真的有用,因为它必须手动检查上下阈值与实际阈值。可以用PHPMD最简模版/占位符引擎得到更详细的信息,你可以定义(违规信息)占位符,这会被替换为真实值。占位符的格式是'{' + \d+ '}'。 ```xml <ruleset name="example.com rules" xmlns="http://pmd.sf.net/ruleset/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd" xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd"> <!-- ... --> <rule name="NumberOfPublics" message = "The class {0} has {1} public method, the threshold is {2}." class="Com_Example_Rule_NumberOfPublicMethods" externalInfoUrl="http://example.com/phpmd/rules.html#numberofpublics"> <priority>3</priority> <properties> <property name="minimum" value="1" description="Minimum number of public methods." /> <property name="maximum" value="10" description="Maximum number of public methods." /> </properties> </rule> </ruleset> ``` 现在我们可以以这种方式调整规则类,自动填充占位符 {0}, {1}, {2} ```php class Com_Example_Rule_NumberOfPublicMethods extends \PHPMD\AbstractRule implements \PHPMD\Rule\ClassAware { public function apply(\PHPMD\AbstractNode $node) { $min = $this->getIntProperty('minimum'); $max = $this->getIntProperty('maximum'); $npm = $node->getMetric('npm'); if ($npm < $min) { $this->addViolation($node, array(get_class($node), $npm, $min)); } else if ($npm > $max) { $this->addViolation($node, array(get_class($node), $npm, $max)); } } } ``` 如果我们运行这个版本的规则,我们会得到一个错误信息,如下图所示。 ```The class FooBar has 42 public method, the threshold is 10.``` ## 编写基于抽象语法树的规则 现在我们将学习如何开发一个PHPMD规则,利用PHP_Depend的抽象语法树检测被分析源代码的违规或可能的错误。访问PHP_Depend的抽象语法树给你PHPMD规则最强大的能力,你可以分析测试软件几乎所有的方面。语法树可以通过\PHPMD\AbstractNode类的getFirstChildOfType()和findChildrenOfType()方法访问。 在这个例子中我们将实现一个规则,用来检测新但有争议的PHP新功能,goto。因为我们都知道并同意Basic中的goto非常糟糕,我们想阻止我们的开发者使用坏的特征。因此,我们实现了一个PHPMD规则,通过PHP_Depend搜索goto语言构造。 goto语句不会出现在类和对象中,但会在方法和函数里出现。新规则类必需实现两个标记接口,\PHPMD\Rule\FunctionAware 和 \PHPMD\Rule\MethodAware。 ```php namespace PHPMD\Rule\Design; use PHPMD\AbstractNode; use PHPMD\AbstractRule; use PHPMD\Rule\MethodAware; use PHPMD\Rule\FunctionAware; class GotoStatement extends AbstractRule implements MethodAware, FunctionAware { public function apply(AbstractNode $node) { foreach ($node->findChildrenOfType('GotoStatement') as $goto) { $this->addViolation($goto, array($node->getType(), $node->getName())); } } } ``` 如你所见,我们在上面例子中查询字符串GotoStatement。PHPMD使用这个快捷注记定位具体PHP_Depend语法树节点。所有PDepend抽象语法树类都像这样:`\PDepend\Source\AST\ASTGotoStatement`,其中`\PDepend\Source\AST\AST`是固定的,其他部分取决于节点类型。固定的这部分搜索抽象语法书节点时可以省略。你应该看看PHP_Depend's代码包,可以找到所有当前支持的代码节点,以便实现附加规则。 总结 在这篇文章中我们展示了实现PHPMD自定义规则的几种方法。如果你认为你的规则可以被其他人或其他项目重用,不要犹豫,请提交你的自定义规则集到GitHub上项目的问题追踪器,或是开启pull请求。