Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for cache results #1011

Merged
merged 59 commits into from
Jul 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
24d28fd
Merge pull request #6 from phpmd/master
frankdekker Jun 1, 2023
e5be40d
Add result cache config, state and engine
frankdekker Jun 3, 2023
f20700f
Add result cache config, state and engine
frankdekker Jun 3, 2023
e432cc6
Add result cache config, state and engine
frankdekker Jun 3, 2023
ee23400
Add result cache config, state and engine
frankdekker Jun 3, 2023
fde96e8
Extract AbstractNode from RuleViolation
frankdekker Jun 3, 2023
eb29a66
Added method to add RuleViolation to ResultCacheState
frankdekker Jun 4, 2023
3659e71
Add ResultCacheIO to save and read the state
frankdekker Jun 4, 2023
29f417f
Read state from result cache
frankdekker Jun 4, 2023
799abbf
Read state from result cache
frankdekker Jun 4, 2023
e198a23
Create RuleViolations from result cache
frankdekker Jun 4, 2023
5b7e504
Extract ResultCacheFileFilter
frankdekker Jun 4, 2023
ebe7df2
Reorganize classes
frankdekker Jun 4, 2023
5367f2b
Fix codestyle
frankdekker Jun 4, 2023
a075f3b
Add commandline options for --cache and --cache-file
frankdekker Jun 12, 2023
646da75
Update documentation
frankdekker Jun 12, 2023
ae964f1
Add commandline options to factory
frankdekker Jun 12, 2023
62f3636
Improve ResultCacheEngineFactory
frankdekker Jun 12, 2023
b462802
Add cache strategy commandline argument
frankdekker Jun 12, 2023
3ec1dcb
Reorganize namespace
frankdekker Jun 12, 2023
9ba711e
Add basics for ResultCacheKey
frankdekker Jun 12, 2023
f0ada49
Add cache key factory
frankdekker Jun 13, 2023
06641fd
Add cache key factory
frankdekker Jun 13, 2023
e4e026e
Add cache key factory
frankdekker Jun 13, 2023
ef03049
Cleanup ResultCacheConfig
frankdekker Jun 13, 2023
a2c02f0
Improve ResultCacheStateFactory
frankdekker Jun 13, 2023
3996b93
Add composer to cache key
frankdekker Jun 13, 2023
7f8c248
Run phpcbf
frankdekker Jun 13, 2023
8b486d2
Change mb5_file to sha1_file
frankdekker Jun 13, 2023
46559c0
Add coverage ResultCacheKey
frankdekker Jun 13, 2023
0f8573d
Add coverage ResultCacheEngine
frankdekker Jun 13, 2023
0ca1564
Add coverage ResultCacheFileFilter
frankdekker Jun 13, 2023
e6bef09
Add coverage ResultCacheFileFilter
frankdekker Jun 13, 2023
f59c603
Add coverage ResultCacheStateFactory
frankdekker Jun 14, 2023
2840b51
Add coverage ResultCacheKeyFactory
frankdekker Jun 14, 2023
c5d35fa
Add coverage ResultCacheKeyFactory
frankdekker Jun 14, 2023
0d67271
Add coverage ResultCacheWriter
frankdekker Jun 14, 2023
f99bfae
Add coverage ResultCacheUpdater
frankdekker Jun 18, 2023
fc122d6
Add coverage ResultCacheEngineFactory
frankdekker Jun 18, 2023
be344ef
Add coverage ResultCacheState
frankdekker Jun 18, 2023
3acf3e2
Fix coverage RuleViolationTest
frankdekker Jun 18, 2023
1c3eaa4
Fix PHPMDTest
frankdekker Jun 18, 2023
7916220
Update gitignore
frankdekker Jun 18, 2023
3e06735
Add coverage ResultCacheState
frankdekker Jun 18, 2023
8210ac4
Add coverage ResultCacheState
frankdekker Jun 18, 2023
77573ff
Add coverage ResultCacheState
frankdekker Jun 18, 2023
7d25e91
Add commandline cli information
frankdekker Jun 18, 2023
9156017
Add baseline file hash to cache key
frankdekker Jun 18, 2023
8ab75f2
Add baseline file hash to cache key
frankdekker Jun 18, 2023
12affac
Avoid reading or writing the cache when generating/updating the baseline
frankdekker Jun 18, 2023
b2e5fe5
Avoid reading or writing the cache when generating/updating the baseline
frankdekker Jun 18, 2023
f7303ff
Fix phpcs issues
frankdekker Jun 18, 2023
2380fe6
Fix ResultCacheKeyFactoryTest
frankdekker Jun 18, 2023
bc91be2
Add coverage NodeInfo
frankdekker Jun 18, 2023
b92d8f5
Add coverage NodeInfoFactory
frankdekker Jun 18, 2023
2010c2c
Add coverage Paths::concat
frankdekker Jun 18, 2023
1c323df
Fix codestyle issue
frankdekker Jun 18, 2023
b00bda2
Merge branch 'phpmd:master' into master
frankdekker Jul 2, 2023
efab9d0
Merge branch 'master' into Add-support-for-cache-results
frankdekker Jul 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
.project
.buildpath
.settings
.phpmd.result-cache.php
*.phar
/*.xml
composer.lock
Expand Down
12 changes: 11 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,15 @@ Command line options
- ``--ignore-violations-on-exit`` - will exit with a zero code, even if any
violations are found.

- ``--cache`` - will enable the result cache. Will default to ``.phpmd.result-cache.php`` in the
current working directory.

- ``--cache-file`` - in cooperation with ``--cache`` will override the default result cache file path of
``.phpmd.result-cache.php`` to the given file path.

- ``--cache-strategy`` - sets the caching strategy to determine if a file is still fresh. Either
`content` to base it on the file contents, or `timestamp` to base it on the file modified timestamp.

- ``--generate-baseline`` - will generate a ``phpmd.baseline.xml`` for existing violations
next to the ruleset definition file. The file paths of the violations will be relative to the current
working directory.
Expand All @@ -144,7 +153,8 @@ Command line options
that no longer exist. New violations will _not_ be added. The file path of the violations will be relative
to the current working directory.

- ``--baseline-file`` - the filepath to a custom baseline xml file.
- ``--baseline-file`` - the filepath to a custom baseline xml file. If absent will
default to ``phpmd.baseline.xml``

An example command line: ::

Expand Down
3 changes: 2 additions & 1 deletion src/main/php/PHPMD/AbstractRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use PHPMD\Node\ClassNode;
use PHPMD\Node\EnumNode;
use PHPMD\Node\InterfaceNode;
use PHPMD\Node\NodeInfoFactory;
use PHPMD\Node\TraitNode;

/**
Expand Down Expand Up @@ -396,7 +397,7 @@ protected function addViolation(
'args' => $args,
);

$ruleViolation = new RuleViolation($this, $node, $message, $metric);
$ruleViolation = new RuleViolation($this, NodeInfoFactory::fromNode($node), $message, $metric);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be able to create a RuleViolation without Node, changed signature to NodeInfo object that's serializable.

$this->report->addRuleViolation($ruleViolation);
}

Expand Down
59 changes: 59 additions & 0 deletions src/main/php/PHPMD/Cache/Model/ResultCacheKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace PHPMD\Cache\Model;

class ResultCacheKey
{
/** @var bool */
private $strict;
/** @var string|null */
private $baselineHash;
/** @var array<string, string> */
private $rules;
/** @var array<string, string> */
private $composer;
/** @var int */
private $phpVersion;

/**
* @param bool $strict
* @param string|null $baselineHash
* @param array<string, string> $rules
* @param array<string, string> $composer
* @param int $phpVersion
*/
public function __construct($strict, $baselineHash, $rules, $composer, $phpVersion)
{
$this->strict = $strict;
$this->baselineHash = $baselineHash;
$this->rules = $rules;
$this->composer = $composer;
$this->phpVersion = $phpVersion;
}

/**
* @return array
*/
public function toArray()
{
return array(
'strict' => $this->strict,
'baselineHash' => $this->baselineHash,
'rules' => $this->rules,
'composer' => $this->composer,
'phpVersion' => $this->phpVersion,
);
}

/**
* @return bool
*/
public function isEqualTo(ResultCacheKey $other)
{
return $this->strict === $other->strict
&& $this->baselineHash === $other->baselineHash
&& $this->rules === $other->rules
&& $this->composer === $other->composer
&& $this->phpVersion === $other->phpVersion;
}
}
167 changes: 167 additions & 0 deletions src/main/php/PHPMD/Cache/Model/ResultCacheState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<?php

namespace PHPMD\Cache\Model;

use PHPMD\Node\NodeInfo;
use PHPMD\Rule;
use PHPMD\RuleSet;
use PHPMD\RuleViolation;
use PHPMD\Utility\Paths;

class ResultCacheState
{
/** @var ResultCacheKey */
private $cacheKey;

/** @var array{files: array<string, array{hash: string, violations: array}>} */
private $state;

/**
* @param array{files: array<string, array{hash: string, violations: array}>} $state
*/
public function __construct(ResultCacheKey $cacheKey, $state = array())
{
$this->cacheKey = $cacheKey;
$this->state = $state;
}

/**
* @return ResultCacheKey
*/
public function getCacheKey()
{
return $this->cacheKey;
}

/**
* @param string $filePath
* @return array
*/
public function getViolations($filePath)
{
if (isset($this->state['files'][$filePath]['violations']) === false) {
return array();
}

return $this->state['files'][$filePath]['violations'];
}

/**
* @param string $filePath
*/
public function setViolations($filePath, array $violations)
{
$this->state['files'][$filePath]['violations'] = $violations;
}

/**
* @param string $filePath
*/
public function addRuleViolation($filePath, RuleViolation $violation)
{
$this->state['files'][$filePath]['violations'][] = array(
'rule' => get_class($violation->getRule()),
'namespaceName' => $violation->getNamespaceName(),
'className' => $violation->getClassName(),
'methodName' => $violation->getMethodName(),
'functionName' => $violation->getFunctionName(),
'beginLine' => $violation->getBeginLine(),
'endLine' => $violation->getEndLine(),
'description' => $violation->getDescription(),
'args' => $violation->getArgs(),
'metric' => $violation->getMetric()
);
}

/**
* @param string $basePath
* @param RuleSet[] $ruleSetList
*/
public function getRuleViolations($basePath, array $ruleSetList)
{
if (isset($this->state['files']) === false) {
return array();
}

$ruleViolations = array();

foreach ($this->state['files'] as $filePath => $violations) {
if (isset($violations['violations']) === false) {
continue;
}
foreach ($violations['violations'] as $violation) {
$rule = self::findRuleIn($violation['rule'], $ruleSetList);
$nodeInfo = new NodeInfo(
Paths::concat($basePath, $filePath),
$violation['namespaceName'],
$violation['className'],
$violation['methodName'],
$violation['functionName'],
$violation['beginLine'],
$violation['endLine']
);

if ($violation['args'] === null) {
$violationMessage = $violation['description'];
} else {
$violationMessage = array('args' => $violation['args'], 'message' => $violation['description']);
}
$ruleViolations[] = new RuleViolation($rule, $nodeInfo, $violationMessage, $violation['metric']);
}
}

return $ruleViolations;
}

/**
* @param string $filePath
* @param string $hash
* @return bool
*/
public function isFileModified($filePath, $hash)
{
if (isset($this->state['files'][$filePath]['hash']) === false) {
return true;
}

return $this->state['files'][$filePath]['hash'] !== $hash;
}

/**
* @param string $filePath
* @param string $hash
*/
public function setFileState($filePath, $hash)
{
return $this->state['files'][$filePath]['hash'] = $hash;
}

/**
* @return array
*/
public function toArray()
{
return array(
'key' => $this->cacheKey->toArray(),
'state' => $this->state
);
}

/**
* @param string $ruleClassName
* @param RuleSet[] $ruleSetList
* @return Rule|null
*/
private static function findRuleIn($ruleClassName, array $ruleSetList)
{
foreach ($ruleSetList as $ruleSet) {
foreach ($ruleSet->getRules() as $rule) {
if (get_class($rule) === $ruleClassName) {
return $rule;
}
}
}

return null;
}
}
16 changes: 16 additions & 0 deletions src/main/php/PHPMD/Cache/Model/ResultCacheStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace PHPMD\Cache\Model;

class ResultCacheStrategy
{
/**
* Determine the file cache freshness based on sha hash of the contents of the file
*/
const CONTENT = 'content';

/**
* Determine the file cache freshness based on the file modified timestamp
*/
const TIMESTAMP = 'timestamp';
}
50 changes: 50 additions & 0 deletions src/main/php/PHPMD/Cache/ResultCacheEngine.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace PHPMD\Cache;

class ResultCacheEngine
{
/** @var ResultCacheFileFilter */
private $fileFilter;

/** @var ResultCacheUpdater */
private $updater;

/** @var ResultCacheWriter */
private $writer;

public function __construct(
ResultCacheFileFilter $fileFilter,
ResultCacheUpdater $updater,
ResultCacheWriter $writer
) {

$this->fileFilter = $fileFilter;
$this->updater = $updater;
$this->writer = $writer;
}

/**
* @return ResultCacheFileFilter
*/
public function getFileFilter()
{
return $this->fileFilter;
}

/**
* @return ResultCacheUpdater
*/
public function getUpdater()
{
return $this->updater;
}

/**
* @return ResultCacheWriter
*/
public function getWriter()
{
return $this->writer;
}
}
49 changes: 49 additions & 0 deletions src/main/php/PHPMD/Cache/ResultCacheEngineFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace PHPMD\Cache;

use PHPMD\RuleSet;
use PHPMD\TextUI\CommandLineOptions;

class ResultCacheEngineFactory
{
/** @var ResultCacheKeyFactory */
private $cacheKeyFactory;
/** @var ResultCacheStateFactory */
private $cacheStateFactory;

public function __construct(ResultCacheKeyFactory $cacheKeyFactory, ResultCacheStateFactory $cacheStateFactory)
{
$this->cacheKeyFactory = $cacheKeyFactory;
$this->cacheStateFactory = $cacheStateFactory;
}

/**
* @param string $basePath
* @param RuleSet[] $ruleSetList
* @return ResultCacheEngine|null
*/
public function create($basePath, CommandLineOptions $options, array $ruleSetList)
{
if ($options->isCacheEnabled() === false) {
return null;
}

// create cache key based on the current rules and environment
$cacheKey = $this->cacheKeyFactory->create($options->hasStrict(), $ruleSetList);

// load result cache from file
$state = $this->cacheStateFactory->fromFile($options->cacheFile());

// the cache key doesn't match the stored cache key. Invalidate cache
if ($state !== null && $state->getCacheKey()->isEqualTo($cacheKey) === false) {
$state = null;
}

return new ResultCacheEngine(
new ResultCacheFileFilter($basePath, $options->cacheStrategy(), $cacheKey, $state),
new ResultCacheUpdater($basePath),
new ResultCacheWriter($options->cacheFile())
);
}
}