Skip to content

Commit

Permalink
Check also for Node\Scalar\Encapsed (#592)
Browse files Browse the repository at this point in the history
Co-authored-by: Matt Glaman <nmd.matt@gmail.com>
  • Loading branch information
claudiu-cristea and mglaman authored Jul 25, 2023
1 parent 0b93022 commit a40fb53
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use PhpParser\Node;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Analyser\Scope;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\Type;

class PluginManagerSetsCacheBackendRule extends AbstractPluginManagerRule
{
Expand Down Expand Up @@ -53,29 +53,30 @@ public function processNode(Node $node, Scope $scope): array
($statement->name instanceof Node\Identifier) &&
$statement->name->name === 'setCacheBackend') {
// setCacheBackend accepts a cache backend, the cache key, and optional (but suggested) cache tags.
$setCacheBackendArgs = $statement->args;

if ($setCacheBackendArgs[1] instanceof Node\VariadicPlaceholder) {
throw new ShouldNotHappenException();
}
$cacheKey = $setCacheBackendArgs[1]->value;
if (!$cacheKey instanceof Node\Scalar\String_) {
$setCacheBackendArgs = $statement->getArgs();
if (count($setCacheBackendArgs) < 2) {
continue;
}
$hasCacheBackendSet = true;

$cacheKey = array_map(
static fn (Type $type) => $type->getValue(),
$scope->getType($setCacheBackendArgs[1]->value)->getConstantStrings()
);
if (count($cacheKey) === 0) {
continue;
}

if (isset($setCacheBackendArgs[2])) {
if ($setCacheBackendArgs[2] instanceof Node\VariadicPlaceholder) {
throw new ShouldNotHappenException();
}
/** @var \PhpParser\Node\Expr\Array_ $cacheTags */
$cacheTags = $setCacheBackendArgs[2]->value;
if (count($cacheTags->items) > 0) {
/** @var \PhpParser\Node\Expr\ArrayItem $item */
foreach ($cacheTags->items as $item) {
if (($item->value instanceof Node\Scalar\String_) &&
strpos($item->value->value, $cacheKey->value) === false) {
$misnamedCacheTagWarnings[] = $item->value->value;
$cacheTagsType = $scope->getType($setCacheBackendArgs[2]->value);
foreach ($cacheTagsType->getConstantArrays() as $constantArray) {
foreach ($constantArray->getValueTypes() as $valueType) {
foreach ($valueType->getConstantStrings() as $cacheTagConstantString) {
foreach ($cacheKey as $cacheKeyValue) {
if (strpos($cacheTagConstantString->getValue(), $cacheKeyValue) === false) {
$misnamedCacheTagWarnings[] = $cacheTagConstantString->getValue();
}
}
}
}
}
Expand Down
43 changes: 43 additions & 0 deletions tests/src/Rules/PluginManagerSetsCacheBackendRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace mglaman\PHPStanDrupal\Tests\Rules;

use mglaman\PHPStanDrupal\Rules\Drupal\PluginManager\PluginManagerSetsCacheBackendRule;
use mglaman\PHPStanDrupal\Tests\DrupalRuleTestCase;
use PHPStan\Rules\Rule;

final class PluginManagerSetsCacheBackendRuleTest extends DrupalRuleTestCase
{

protected function getRule(): Rule
{
return new PluginManagerSetsCacheBackendRule();
}

/**
* @dataProvider ruleData
*/
public function testRule(string $path, array $errorMessages): void
{
$this->analyse([$path], $errorMessages);
}

public static function ruleData(): \Generator
{
yield [
__DIR__ . '/data/plugin-manager-cache-backend.php',
[
[
'Missing cache backend declaration for performance.',
12
],
[
'plugins cache tag might be unclear and does not contain the cache key in it.',
112,
]
]
];
}
}
127 changes: 127 additions & 0 deletions tests/src/Rules/data/plugin-manager-cache-backend.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php

namespace PluginManagerCacheBackend;

use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;

class Foo extends DefaultPluginManager
{

public function __construct(
\Traversable $namespaces,
ModuleHandlerInterface $module_handler,
) {
parent::__construct(
'Plugin/Foo',
$namespaces,
$module_handler,
'FooInterface',
'FooAnnotation',
);
}

}

class Bar extends DefaultPluginManager
{

public function __construct(
\Traversable $namespaces,
ModuleHandlerInterface $module_handler,
CacheBackendInterface $cache_backend
) {
parent::__construct(
'Plugin/Bar',
$namespaces,
$module_handler,
'BarInterface',
'BarAnnotation',
);
$this->setCacheBackend($cache_backend, 'bar_plugins');
}

}

class Baz extends DefaultPluginManager
{

public function __construct(
\Traversable $namespaces,
ModuleHandlerInterface $module_handler,
CacheBackendInterface $cache_backend,
string $type,
) {
parent::__construct(
'Plugin/Bar',
$namespaces,
$module_handler,
'BarInterface',
'BarAnnotation',
);
$this->setCacheBackend($cache_backend, 'bar_' . $type . '_plugins');
}

}

class Qux extends DefaultPluginManager
{

public function __construct(
\Traversable $namespaces,
ModuleHandlerInterface $module_handler,
CacheBackendInterface $cache_backend,
string $type,
) {
parent::__construct(
'Plugin/Bar',
$namespaces,
$module_handler,
'BarInterface',
'BarAnnotation',
);
$this->setCacheBackend($cache_backend, "bar_{$type}_plugins");
}

}

class BarTags extends DefaultPluginManager
{

public function __construct(
\Traversable $namespaces,
ModuleHandlerInterface $module_handler,
CacheBackendInterface $cache_backend
) {
parent::__construct(
'Plugin/Bar',
$namespaces,
$module_handler,
'BarInterface',
'BarAnnotation',
);
$this->setCacheBackend($cache_backend, 'bar_plugins', ['bar_plugins']);
}

}

class BarTagsNotClear extends DefaultPluginManager
{

public function __construct(
\Traversable $namespaces,
ModuleHandlerInterface $module_handler,
CacheBackendInterface $cache_backend
) {
parent::__construct(
'Plugin/Bar',
$namespaces,
$module_handler,
'BarInterface',
'BarAnnotation',
);
$this->setCacheBackend($cache_backend, 'bar_plugins', ['plugins']);
}

}

0 comments on commit a40fb53

Please sign in to comment.