Skip to content

Commit ec42712

Browse files
authored
Merge pull request #300 from shochdoerfer/feature/rule_disallow_setTemplate
Add rule "Disallow setTemplate() in Block classes"
2 parents f0141ee + 8948678 commit ec42712

File tree

7 files changed

+221
-0
lines changed

7 files changed

+221
-0
lines changed

docs/features.md

+11
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,14 @@ parameters:
8383
magento:
8484
checkCollectionViaFactory: false
8585
```
86+
87+
### Do not use setTemplate in Block classes
88+
89+
As the [ExtDN](https://github.com/extdn/extdn-phpcs/blob/master/Extdn/Sniffs/Blocks/SetTemplateInBlockSniff.md) folks have put it: Setters are deprecated in Block classes, because constructor arguments should be preferred instead. Any call to `$this->setTemplate()` after calling upon the parent constructor would undo the configuration via XML layout.
90+
91+
To disable this rule add the following code to your `phpstan.neon` configuration file:
92+
```neon
93+
parameters:
94+
extdn:
95+
setTemplateDisallowedForBlockClasses: false
96+
```

extension.neon

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ parameters:
44
checkServiceContracts: true
55
checkResourceModelsUsedDirectly: true
66
magentoRoot: %currentWorkingDirectory%
7+
extdn:
8+
setTemplateDisallowedForBlockClasses: true
79
bootstrapFiles:
810
- magento-autoloader.php
911
parametersSchema:
@@ -20,6 +22,8 @@ conditionalTags:
2022
phpstan.rules.rule: %magento.checkServiceContracts%
2123
bitExpert\PHPStan\Magento\Rules\ResourceModelsShouldBeUsedDirectlyRule:
2224
phpstan.rules.rule: %magento.checkResourceModelsUsedDirectly%
25+
bitExpert\PHPStan\Magento\Rules\SetTemplateDisallowedForBlockRule:
26+
phpstan.rules.rule: %extdn.setTemplateDisallowedForBlockClasses%
2327
services:
2428
-
2529
class: bitExpert\PHPStan\Magento\Type\ObjectManagerDynamicReturnTypeExtension
@@ -47,6 +51,8 @@ services:
4751
class: bitExpert\PHPStan\Magento\Rules\AbstractModelUseServiceContractRule
4852
-
4953
class: bitExpert\PHPStan\Magento\Rules\ResourceModelsShouldBeUsedDirectlyRule
54+
-
55+
class: bitExpert\PHPStan\Magento\Rules\SetTemplateDisallowedForBlockRule
5056
fileCacheStorage:
5157
class: bitExpert\PHPStan\Magento\Autoload\Cache\FileCacheStorage
5258
arguments:

phpstan.neon

+3
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ parameters:
5252
-
5353
message: '~bitExpert\\PHPStan\\Magento\\Rules\\Helper\\SampleModel::__construct\(\) does not call parent constructor~'
5454
path: tests/bitExpert/PHPStan/Magento/Rules/Helper/SampleModel.php
55+
-
56+
message: '~bitExpert\\PHPStan\\Magento\\Rules\\Helper\\SampleBlock::__construct\(\) does not call parent constructor~'
57+
path: tests/bitExpert/PHPStan/Magento/Rules/Helper/SampleBlock.php
5558
-
5659
message: '~Call to static method PHPUnit\\Framework\\Assert::assertInstanceOf~'
5760
path: tests/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtensionUnitTest.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the phpstan-magento package.
5+
*
6+
* (c) bitExpert AG
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
declare(strict_types=1);
12+
13+
namespace bitExpert\PHPStan\Magento\Rules;
14+
15+
use PhpParser\Node;
16+
use PhpParser\Node\Expr\MethodCall;
17+
use PHPStan\Analyser\Scope;
18+
use PHPStan\Rules\Rule;
19+
use PHPStan\ShouldNotHappenException;
20+
use PHPStan\Type\ObjectType;
21+
use PHPStan\Type\VerbosityLevel;
22+
23+
/**
24+
* Do not use setTemplate in Block classes, see
25+
* https://github.com/extdn/extdn-phpcs/blob/master/Extdn/Sniffs/Blocks/SetTemplateInBlockSniff.md
26+
*
27+
* @implements Rule<MethodCall>
28+
*/
29+
class SetTemplateDisallowedForBlockRule implements Rule
30+
{
31+
/**
32+
* @phpstan-return class-string<MethodCall>
33+
* @return string
34+
*/
35+
public function getNodeType(): string
36+
{
37+
return MethodCall::class;
38+
}
39+
40+
/**
41+
* @param Node $node
42+
* @param Scope $scope
43+
* @return (string|\PHPStan\Rules\RuleError)[] errors
44+
* @throws ShouldNotHappenException
45+
*/
46+
public function processNode(Node $node, Scope $scope): array
47+
{
48+
if (!$node instanceof MethodCall) {
49+
throw new ShouldNotHappenException();
50+
}
51+
52+
if (!$node->name instanceof Node\Identifier) {
53+
return [];
54+
}
55+
56+
if ($node->name->name !== 'setTemplate') {
57+
return [];
58+
}
59+
60+
$type = $scope->getType($node->var);
61+
$isAbstractModelType = (new ObjectType('Magento\Framework\View\Element\Template'))->isSuperTypeOf($type);
62+
if (!$isAbstractModelType->yes()) {
63+
return [];
64+
}
65+
66+
return [
67+
sprintf(
68+
'Setter methods like %s::%s() are deprecated in Block classes, use constructor arguments instead',
69+
$type->describe(VerbosityLevel::typeOnly()),
70+
$node->name->name
71+
)
72+
];
73+
}
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the phpstan-magento package.
5+
*
6+
* (c) bitExpert AG
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
declare(strict_types=1);
12+
13+
namespace bitExpert\PHPStan\Magento\Rules\Helper;
14+
15+
use \Magento\Framework\View\Element\Template;
16+
17+
class SampleBlock extends Template
18+
{
19+
public function __construct()
20+
{
21+
// We do not call the parent constructor here on purpose as we do not want do to deal with creating
22+
// not needed dependencies just for the testcase!
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?php
2+
3+
$block = new \bitExpert\PHPStan\Magento\Rules\Helper\SampleBlock();
4+
$block->setTemplate('template.phtml');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the phpstan-magento package.
5+
*
6+
* (c) bitExpert AG
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
declare(strict_types=1);
12+
13+
namespace bitExpert\PHPStan\Magento\Rules;
14+
15+
use bitExpert\PHPStan\Magento\Rules\Helper\SampleBlock;
16+
use bitExpert\PHPStan\Magento\Rules\Helper\SampleModel;
17+
use PhpParser\Node\Expr\MethodCall;
18+
use PhpParser\Node\Expr\Variable;
19+
use PHPStan\Analyser\Scope;
20+
use PHPStan\Rules\Rule;
21+
use PHPStan\ShouldNotHappenException;
22+
use PHPStan\Testing\RuleTestCase;
23+
24+
/**
25+
* @extends \PHPStan\Testing\RuleTestCase<SetTemplateDisallowedForBlockRule>
26+
*/
27+
class SetTemplateDisallowedForBlockRuleUnitTest extends RuleTestCase
28+
{
29+
protected function getRule(): Rule
30+
{
31+
return new SetTemplateDisallowedForBlockRule();
32+
}
33+
34+
/**
35+
* @test
36+
*/
37+
public function checkCaughtExceptions(): void
38+
{
39+
$this->analyse([__DIR__ . '/Helper/block_settemplate.php'], [
40+
[
41+
'Setter methods like '.SampleBlock::class.'::setTemplate() are deprecated in Block classes, '.
42+
'use constructor arguments instead',
43+
4,
44+
],
45+
]);
46+
}
47+
48+
/**
49+
* @test
50+
*/
51+
public function getNodeTypeMethodReturnsMethodCall(): void
52+
{
53+
$rule = new SetTemplateDisallowedForBlockRule();
54+
55+
self::assertSame(MethodCall::class, $rule->getNodeType());
56+
}
57+
58+
/**
59+
* @test
60+
*/
61+
public function processNodeThrowsExceptionForNonMethodCallNodes(): void
62+
{
63+
$this->expectException(ShouldNotHappenException::class);
64+
65+
$node = new Variable('var');
66+
$scope = $this->createMock(Scope::class);
67+
68+
$rule = new SetTemplateDisallowedForBlockRule();
69+
$rule->processNode($node, $scope);
70+
}
71+
72+
/**
73+
* @test
74+
*/
75+
public function processNodeReturnsEarlyWhenNodeNameIsWrongType(): void
76+
{
77+
$node = new MethodCall(new Variable('var'), new Variable('wrong_node'));
78+
$scope = $this->createMock(Scope::class);
79+
80+
$rule = new SetTemplateDisallowedForBlockRule();
81+
$return = $rule->processNode($node, $scope);
82+
83+
self::assertCount(0, $return);
84+
}
85+
86+
/**
87+
* @test
88+
*/
89+
public function processNodeReturnsEarlyWhenNodeNameIsNotSaveOrLoadOrDelete(): void
90+
{
91+
$node = new MethodCall(new Variable('var'), 'wrong_node_name');
92+
$scope = $this->createMock(Scope::class);
93+
94+
$rule = new SetTemplateDisallowedForBlockRule();
95+
$return = $rule->processNode($node, $scope);
96+
97+
self::assertCount(0, $return);
98+
}
99+
}

0 commit comments

Comments
 (0)