Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Add rule "resource models should be used directly"
  • Loading branch information
shochdoerfer committed Apr 7, 2023
commit a6a1d5feacc8b859d15fc3296790b06d9ef25a0d
11 changes: 11 additions & 0 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,17 @@ parameters:
checkServiceContracts: false
```

### Resource Models should be used directly

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.

To disable this rule add the following code to your `phpstan.neon` configuration file:
```neon
parameters:
magento:
checkResourceModelsUsedDirectly: false
```

### Collections should be used directly via factory

Since Magento framework version 101.0.0 Collections should be used directly via factory instead of calling
Expand Down
6 changes: 5 additions & 1 deletion extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ parameters:
magento:
checkCollectionViaFactory: true
checkServiceContracts: true
checkResourceModelsUsedDirectly: true
magentoRoot: %currentWorkingDirectory%
bootstrapFiles:
- magento-autoloader.php
Expand All @@ -17,7 +18,8 @@ conditionalTags:
phpstan.rules.rule: %magento.checkCollectionViaFactory%
bitExpert\PHPStan\Magento\Rules\AbstractModelUseServiceContractRule:
phpstan.rules.rule: %magento.checkServiceContracts%

bitExpert\PHPStan\Magento\Rules\ResourceModelsShouldBeUsedDirectlyRule:
phpstan.rules.rule: %magento.checkResourceModelsUsedDirectly%
services:
-
class: bitExpert\PHPStan\Magento\Type\ObjectManagerDynamicReturnTypeExtension
Expand All @@ -43,6 +45,8 @@ services:
class: bitExpert\PHPStan\Magento\Rules\AbstractModelRetrieveCollectionViaFactoryRule
-
class: bitExpert\PHPStan\Magento\Rules\AbstractModelUseServiceContractRule
-
class: bitExpert\PHPStan\Magento\Rules\ResourceModelsShouldBeUsedDirectlyRule
fileCacheStorage:
class: bitExpert\PHPStan\Magento\Autoload\Cache\FileCacheStorage
arguments:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

/*
* This file is part of the phpstan-magento package.
*
* (c) bitExpert AG
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);

namespace bitExpert\PHPStan\Magento\Rules;

use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\ObjectType;
use PHPStan\Type\VerbosityLevel;

/**
* Since 100.1.0 resource models should be used directly.
*
* @implements Rule<MethodCall>
*/
class ResourceModelsShouldBeUsedDirectlyRule implements Rule
{
/**
* @phpstan-return class-string<MethodCall>
* @return string
*/
public function getNodeType(): string
{
return MethodCall::class;
}

/**
* @param Node $node
* @param Scope $scope
* @return (string|\PHPStan\Rules\RuleError)[] errors
* @throws ShouldNotHappenException
*/
public function processNode(Node $node, Scope $scope): array
{
if (!$node instanceof MethodCall) {
throw new ShouldNotHappenException();
}

if (!$node->name instanceof Node\Identifier) {
return [];
}

if (!in_array($node->name->name, ['getResource', '_getResource'], true)) {
return [];
}

$type = $scope->getType($node->var);
$isAbstractModelType = (new ObjectType('Magento\Framework\Model\AbstractModel'))->isSuperTypeOf($type);
if (!$isAbstractModelType->yes()) {
return [];
}

return [
sprintf(
'%s::%s() is deprecated. Use Resource Models directly',
$type->describe(VerbosityLevel::typeOnly()),
$node->name->name
)
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?php

$model = new \bitExpert\PHPStan\Magento\Rules\Helper\SampleModel();
$model->getResource();
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

/*
* This file is part of the phpstan-magento package.
*
* (c) bitExpert AG
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);

namespace bitExpert\PHPStan\Magento\Rules;

use bitExpert\PHPStan\Magento\Rules\Helper\SampleModel;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\Variable;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\ShouldNotHappenException;
use PHPStan\Testing\RuleTestCase;

/**
* @extends \PHPStan\Testing\RuleTestCase<ResourceModelsShouldBeUsedDirectlyRule>
*/
class ResourceModelsShouldBeUsedDirectlyRuleUnitTest extends RuleTestCase
{
protected function getRule(): Rule
{
return new ResourceModelsShouldBeUsedDirectlyRule();
}

/**
* @test
*/
public function checkCaughtExceptions(): void
{
$this->analyse([__DIR__ . '/Helper/resource_model.php'], [
[
SampleModel::class . '::getResource() is deprecated. Use Resource Models directly',
4,
],
]);
}

/**
* @test
*/
public function getNodeTypeMethodReturnsMethodCall(): void
{
$rule = new ResourceModelsShouldBeUsedDirectlyRule();

self::assertSame(MethodCall::class, $rule->getNodeType());
}

/**
* @test
*/
public function processNodeThrowsExceptionForNonMethodCallNodes(): void
{
$this->expectException(ShouldNotHappenException::class);

$node = new Variable('var');
$scope = $this->createMock(Scope::class);

$rule = new ResourceModelsShouldBeUsedDirectlyRule();
$rule->processNode($node, $scope);
}

/**
* @test
*/
public function processNodeReturnsEarlyWhenNodeNameIsWrongType(): void
{
$node = new MethodCall(new Variable('var'), new Variable('wrong_node'));
$scope = $this->createMock(Scope::class);

$rule = new ResourceModelsShouldBeUsedDirectlyRule();
$return = $rule->processNode($node, $scope);

self::assertCount(0, $return);
}

/**
* @test
*/
public function processNodeReturnsEarlyWhenNodeNameIsNotSaveOrLoadOrDelete(): void
{
$node = new MethodCall(new Variable('var'), 'wrong_node_name');
$scope = $this->createMock(Scope::class);

$rule = new ResourceModelsShouldBeUsedDirectlyRule();
$return = $rule->processNode($node, $scope);

self::assertCount(0, $return);
}
}