Before the release of Mars it was possible to install PHPMD support in Eclipse, albeit with some caveats and difficulties.
Now support from PTI seems to have been removed completely, even if development of PHPMD hasn't stopped and PHPMD does offer some features that other tools do not: for example, detect unused variables.
For this last feature I've found a not too recent CodeSniffer plugin that does the trick. There are also some sniffs that should do the work but they don't seem to work for me, or not in all cases at the very least: I have a project in need of refactoring for which I have 11 warnings from CodeSniffer and 2524 from PHPMD.
I think I have a simple and inelegant way of shoehorning PHPMD back in, but before doing that, I wondered whether anyone has this specific same problem/need, and whether s/he managed to solve it somehow.
Okay, so here goes.
I do have a PHPMD binary that works from the command line. What I planned to do is to inject its output into that of the CodeSniffer plugin, to "enrich" the latter with PHPMD messages.
To this effect, I mangled phpcs.php
which comes in my plugins/org.ppsrc.eclipse.pti.tools.codesniffer_.../php/tools
directory.
(Since another problem I have with CodeSniffer is that it will often re-scan files it ought to know about, I decided to give CodeSniffer a memory.
First thing, I extract the last argument to the invocation, which is the file being analyzed (lines marked by +++ are my additions/changes):
// Optionally use PHP_Timer to print time/memory stats for the run.
// Note that the reports are the ones who actually print the data
// as they decide if it is ok to print this data to screen.
@include_once 'PHP/Timer.php';
if (class_exists('PHP_Timer', false) === true) {
PHP_Timer::start();
}
if (is_file(dirname(__FILE__).'/../CodeSniffer/CLI.php') === true) {
include_once dirname(__FILE__).'/../CodeSniffer/CLI.php';
} else {
include_once 'PHP/CodeSniffer/CLI.php';
}
+++ $lastArgument = array_pop($_SERVER['argv']);
Then I add some flags that CS doesn't seem to pass, and that I need, such as ignoring some directories:
+++ $_SERVER['argv'][] = '--ignore=tests,vendor,cache';
+++ $_SERVER['argv'][] = $lastArgument;
Then CS invocation proceeds, but now I save its results to a buffer instead of sending them straight to Eclipse.
$phpcs = new PHP_CodeSniffer_CLI();
$phpcs->checkRequirements();
+++ ob_start();
$numErrors = $phpcs->process();
+++ $dom = new DOMDocument();
+++ $dom->loadXML(ob_get_clean());
+++ $cs = $dom->getElementsByTagName('phpcs')->item(0);
+++ $xpath = new DOMXPath($dom);
Now I have PHPCS output ready as XML.
All that remains is to invoke PHPMD using its own syntax.
// Add PHPMD.
$mdCmd = "C:/PHP/composer/vendor/phpmd/phpmd/src/bin/phpmd \"{$lastArgument}\" xml \"C:/Program Files/eclipse/plugins/org.phpsrc.eclipse.pti.library.pear_1.2.2.R20120127000000/php/library/PEAR/data/PHP_PMD/resources/rulesets/codesize.xml,C:/Program Files/eclipse/plugins/org.phpsrc.eclipse.pti.library.pear_1.2.2.R20120127000000/php/library/PEAR/data/PHP_PMD/resources/rulesets/naming.xml,C:/Program Files/eclipse/plugins/org.phpsrc.eclipse.pti.library.pear_1.2.2.R20120127000000/php/library/PEAR/data/PHP_PMD/resources/rulesets/unusedcode.xml\"";
...and load it into another XML:
fprintf(STDERR, $mdCmd . "\n");
$dompmd = new DOMDocument();
$dompmd->loadXML($mdxml = shell_exec($mdCmd));
Now I get all errors out of the PMD object, and add it to the CS one:
$files = $dompmd->getElementsByTagName('file');
foreach ($files as $file) {
$name = $file->getAttribute('name');
$list = $xpath->query("//file[@name=\"{$name}\"]");
if (null === $list) {
continue;
}
$csFile = $list->item(0);
if (null === $csFile) {
// No errors from CS.
$csFile = $dom->createElement('file');
$csFile->setAttribute('name', $name);
$csFile->setAttribute('errors', 0);
$csFile->setAttribute('warnings', 0);
$csFile->setAttribute('fixable', 0);
$cs->appendChild($csFile);
}
$errs = 0;
foreach ($file->childNodes as $violation) {
if ($violation instanceof \DOMText) {
continue;
}
$error = $dom->createElement('warning', trim($violation->nodeValue));
$error->setAttribute('line', $violation->getAttribute('beginline'));
$error->setAttribute('column', 1);
$error->setAttribute('source', 'PHPMD.' . $violation->getAttribute('ruleset'));
$error->setAttribute('severity', $violation->getAttribute('priority'));
$error->setAttribute('fixable', 0);
$csFile->appendChild($error);
$errs++;
}
$csFile->getAttributeNode('errors')->value += $errs;
}
Finally, send the data back to Eclipse:
print $dom->saveXML();
exit($numErrors ? 1: 0);
Since another problem I have with CodeSniffer is that it will often re-scan files it ought to know about, I decided to give CodeSniffer a memory. It's pretty easy: I can store a temporary file with the saved XML and a name built up of the MD5 of the original file name and its contents:
/tmp/68ce1959ef67bcc94e05084e2e20462a.76e55e72f32156a20a183de82fe0b3b6.xml
So when PHPCS is asked to analyze /path/to/file/so-and-so.php
, it will: