Skip to content

Commit f0141ee

Browse files
authored
Merge pull request #299 from shochdoerfer/feature/rule_resource_models
Add rule "resource models should be used directly"
2 parents d25e05b + a6a1d5f commit f0141ee

File tree

5 files changed

+190
-1
lines changed

5 files changed

+190
-1
lines changed

docs/features.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,17 @@ parameters:
6161
checkServiceContracts: false
6262
```
6363

64+
### Resource Models should be used directly
65+
66+
Since Magento framework version 100.1.0 it is no longer recommended to use `\Magento\Framework\Model\AbtractModel::getResource()` for retrieving the model resource. Use [service contracts](https://devdocs.magento.com/guides/v2.4/extension-dev-guide/service-contracts/service-contracts.html) instead.
67+
68+
To disable this rule add the following code to your `phpstan.neon` configuration file:
69+
```neon
70+
parameters:
71+
magento:
72+
checkResourceModelsUsedDirectly: false
73+
```
74+
6475
### Collections should be used directly via factory
6576

6677
Since Magento framework version 101.0.0 Collections should be used directly via factory instead of calling

extension.neon

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ parameters:
22
magento:
33
checkCollectionViaFactory: true
44
checkServiceContracts: true
5+
checkResourceModelsUsedDirectly: true
56
magentoRoot: %currentWorkingDirectory%
67
bootstrapFiles:
78
- magento-autoloader.php
@@ -17,7 +18,8 @@ conditionalTags:
1718
phpstan.rules.rule: %magento.checkCollectionViaFactory%
1819
bitExpert\PHPStan\Magento\Rules\AbstractModelUseServiceContractRule:
1920
phpstan.rules.rule: %magento.checkServiceContracts%
20-
21+
bitExpert\PHPStan\Magento\Rules\ResourceModelsShouldBeUsedDirectlyRule:
22+
phpstan.rules.rule: %magento.checkResourceModelsUsedDirectly%
2123
services:
2224
-
2325
class: bitExpert\PHPStan\Magento\Type\ObjectManagerDynamicReturnTypeExtension
@@ -43,6 +45,8 @@ services:
4345
class: bitExpert\PHPStan\Magento\Rules\AbstractModelRetrieveCollectionViaFactoryRule
4446
-
4547
class: bitExpert\PHPStan\Magento\Rules\AbstractModelUseServiceContractRule
48+
-
49+
class: bitExpert\PHPStan\Magento\Rules\ResourceModelsShouldBeUsedDirectlyRule
4650
fileCacheStorage:
4751
class: bitExpert\PHPStan\Magento\Autoload\Cache\FileCacheStorage
4852
arguments:
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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+
* Since 100.1.0 resource models should be used directly.
25+
*
26+
* @implements Rule<MethodCall>
27+
*/
28+
class ResourceModelsShouldBeUsedDirectlyRule implements Rule
29+
{
30+
/**
31+
* @phpstan-return class-string<MethodCall>
32+
* @return string
33+
*/
34+
public function getNodeType(): string
35+
{
36+
return MethodCall::class;
37+
}
38+
39+
/**
40+
* @param Node $node
41+
* @param Scope $scope
42+
* @return (string|\PHPStan\Rules\RuleError)[] errors
43+
* @throws ShouldNotHappenException
44+
*/
45+
public function processNode(Node $node, Scope $scope): array
46+
{
47+
if (!$node instanceof MethodCall) {
48+
throw new ShouldNotHappenException();
49+
}
50+
51+
if (!$node->name instanceof Node\Identifier) {
52+
return [];
53+
}
54+
55+
if (!in_array($node->name->name, ['getResource', '_getResource'], true)) {
56+
return [];
57+
}
58+
59+
$type = $scope->getType($node->var);
60+
$isAbstractModelType = (new ObjectType('Magento\Framework\Model\AbstractModel'))->isSuperTypeOf($type);
61+
if (!$isAbstractModelType->yes()) {
62+
return [];
63+
}
64+
65+
return [
66+
sprintf(
67+
'%s::%s() is deprecated. Use Resource Models directly',
68+
$type->describe(VerbosityLevel::typeOnly()),
69+
$node->name->name
70+
)
71+
];
72+
}
73+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?php
2+
3+
$model = new \bitExpert\PHPStan\Magento\Rules\Helper\SampleModel();
4+
$model->getResource();
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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\SampleModel;
16+
use PhpParser\Node\Expr\MethodCall;
17+
use PhpParser\Node\Expr\Variable;
18+
use PHPStan\Analyser\Scope;
19+
use PHPStan\Rules\Rule;
20+
use PHPStan\ShouldNotHappenException;
21+
use PHPStan\Testing\RuleTestCase;
22+
23+
/**
24+
* @extends \PHPStan\Testing\RuleTestCase<ResourceModelsShouldBeUsedDirectlyRule>
25+
*/
26+
class ResourceModelsShouldBeUsedDirectlyRuleUnitTest extends RuleTestCase
27+
{
28+
protected function getRule(): Rule
29+
{
30+
return new ResourceModelsShouldBeUsedDirectlyRule();
31+
}
32+
33+
/**
34+
* @test
35+
*/
36+
public function checkCaughtExceptions(): void
37+
{
38+
$this->analyse([__DIR__ . '/Helper/resource_model.php'], [
39+
[
40+
SampleModel::class . '::getResource() is deprecated. Use Resource Models directly',
41+
4,
42+
],
43+
]);
44+
}
45+
46+
/**
47+
* @test
48+
*/
49+
public function getNodeTypeMethodReturnsMethodCall(): void
50+
{
51+
$rule = new ResourceModelsShouldBeUsedDirectlyRule();
52+
53+
self::assertSame(MethodCall::class, $rule->getNodeType());
54+
}
55+
56+
/**
57+
* @test
58+
*/
59+
public function processNodeThrowsExceptionForNonMethodCallNodes(): void
60+
{
61+
$this->expectException(ShouldNotHappenException::class);
62+
63+
$node = new Variable('var');
64+
$scope = $this->createMock(Scope::class);
65+
66+
$rule = new ResourceModelsShouldBeUsedDirectlyRule();
67+
$rule->processNode($node, $scope);
68+
}
69+
70+
/**
71+
* @test
72+
*/
73+
public function processNodeReturnsEarlyWhenNodeNameIsWrongType(): void
74+
{
75+
$node = new MethodCall(new Variable('var'), new Variable('wrong_node'));
76+
$scope = $this->createMock(Scope::class);
77+
78+
$rule = new ResourceModelsShouldBeUsedDirectlyRule();
79+
$return = $rule->processNode($node, $scope);
80+
81+
self::assertCount(0, $return);
82+
}
83+
84+
/**
85+
* @test
86+
*/
87+
public function processNodeReturnsEarlyWhenNodeNameIsNotSaveOrLoadOrDelete(): void
88+
{
89+
$node = new MethodCall(new Variable('var'), 'wrong_node_name');
90+
$scope = $this->createMock(Scope::class);
91+
92+
$rule = new ResourceModelsShouldBeUsedDirectlyRule();
93+
$return = $rule->processNode($node, $scope);
94+
95+
self::assertCount(0, $return);
96+
}
97+
}

0 commit comments

Comments
 (0)