php-censor/src/PHPCensor/Plugin/PhpMessDetector.php
2017-03-12 11:40:40 +07:00

257 lines
6.4 KiB
PHP

<?php
namespace PHPCensor\Plugin;
use PHPCensor;
use PHPCensor\Builder;
use PHPCensor\Model\Build;
use PHPCensor\Plugin;
use PHPCensor\ZeroConfigPluginInterface;
/**
* PHP Mess Detector Plugin - Allows PHP Mess Detector testing.
*
* @author Dan Cryer <dan@block8.co.uk>
*/
class PhpMessDetector extends Plugin implements ZeroConfigPluginInterface
{
/**
* @var array
*/
protected $suffixes;
/**
* @var string, based on the assumption the root may not hold the code to be
* tested, extends the base path only if the provided path is relative. Absolute
* paths are used verbatim
*/
protected $path;
/**
* @var array - paths to ignore
*/
protected $ignore;
/**
* Array of PHPMD rules. Can be one of the builtins (codesize, unusedcode, naming, design, controversial)
* or a filename (detected by checking for a / in it), either absolute or relative to the project root.
* @var array
*/
protected $rules;
protected $allowed_warnings;
/**
* @return string
*/
public static function pluginName()
{
return 'php_mess_detector';
}
/**
* {@inheritdoc}
*/
public function __construct(Builder $builder, Build $build, array $options = [])
{
parent::__construct($builder, $build, $options);
$this->suffixes = ['php'];
$this->ignore = $this->builder->ignore;
$this->path = '';
$this->rules = ['codesize', 'unusedcode', 'naming'];
$this->allowed_warnings = 0;
if (isset($options['zero_config']) && $options['zero_config']) {
$this->allowed_warnings = -1;
}
if (!empty($options['path'])) {
$this->path = $options['path'];
}
if (array_key_exists('allowed_warnings', $options)) {
$this->allowed_warnings = (int)$options['allowed_warnings'];
}
foreach (['rules', 'ignore', 'suffixes'] as $key) {
$this->overrideSetting($options, $key);
}
}
/**
* Check if this plugin can be executed.
* @param $stage
* @param Builder $builder
* @param Build $build
* @return bool
*/
public static function canExecute($stage, Builder $builder, Build $build)
{
if ($stage == 'test') {
return true;
}
return false;
}
/**
* Runs PHP Mess Detector in a specified directory.
*/
public function execute()
{
if (!$this->tryAndProcessRules()) {
return false;
}
$phpmdBinaryPath = $this->builder->findBinary('phpmd');
$this->executePhpMd($phpmdBinaryPath);
$errorCount = $this->processReport(trim($this->builder->getLastOutput()));
$this->build->storeMeta('phpmd-warnings', $errorCount);
return $this->wasLastExecSuccessful($errorCount);
}
/**
* Override a default setting.
* @param $options
* @param $key
*/
protected function overrideSetting($options, $key)
{
if (isset($options[$key]) && is_array($options[$key])) {
$this->{$key} = $options[$key];
}
}
/**
* Process PHPMD's XML output report.
*
* @param $xmlString
*
* @return integer
*
* @throws \Exception
*/
protected function processReport($xmlString)
{
$xml = simplexml_load_string($xmlString);
if ($xml === false) {
$this->builder->log($xmlString);
throw new \Exception('Could not process PHPMD report XML.');
}
$warnings = 0;
foreach ($xml->file as $file) {
$fileName = (string)$file['name'];
$fileName = str_replace($this->builder->buildPath, '', $fileName);
foreach ($file->violation as $violation) {
$warnings++;
$this->build->reportError(
$this->builder,
'php_mess_detector',
(string)$violation,
PHPCensor\Model\BuildError::SEVERITY_HIGH,
$fileName,
(int)$violation['beginline'],
(int)$violation['endline']
);
}
}
return $warnings;
}
/**
* Try and process the rules parameter from .php-censor.yml.
* @return bool
*/
protected function tryAndProcessRules()
{
if (!empty($this->rules) && !is_array($this->rules)) {
$this->builder->logFailure('The "rules" option must be an array.');
return false;
}
foreach ($this->rules as &$rule) {
if (strpos($rule, '/') !== false) {
$rule = $this->builder->buildPath . $rule;
}
}
return true;
}
/**
* Execute PHP Mess Detector.
* @param $binaryPath
*/
protected function executePhpMd($binaryPath)
{
$cmd = $binaryPath . ' "%s" xml %s %s %s';
$path = $this->getTargetPath();
$ignore = '';
if (count($this->ignore)) {
$ignore = ' --exclude ' . implode(',', $this->ignore);
}
$suffixes = '';
if (count($this->suffixes)) {
$suffixes = ' --suffixes ' . implode(',', $this->suffixes);
}
// Disable exec output logging, as we don't want the XML report in the log:
$this->builder->logExecOutput(false);
// Run PHPMD:
$this->builder->executeCommand(
$cmd,
$path,
implode(',', $this->rules),
$ignore,
$suffixes
);
// Re-enable exec output logging:
$this->builder->logExecOutput(true);
}
/**
* Get the path PHPMD should be run against.
* @return string
*/
protected function getTargetPath()
{
$path = $this->builder->buildPath . $this->path;
if (!empty($this->path) && $this->path{0} == '/') {
$path = $this->path;
return $path;
}
return $path;
}
/**
* Returns a boolean indicating if the error count can be considered a success.
*
* @param int $errorCount
* @return bool
*/
protected function wasLastExecSuccessful($errorCount)
{
$success = true;
if ($this->allowed_warnings != -1 && $errorCount > $this->allowed_warnings) {
$success = false;
return $success;
}
return $success;
}
}