diff --git a/rules.neon b/rules.neon index f570bd3..28af64d 100644 --- a/rules.neon +++ b/rules.neon @@ -5,7 +5,7 @@ parameters: addBehaviorExistsClassRule: true tableGetMatchOptionsTypesRule: true ormSelectQueryFindMatchOptionsTypesRule: true - disallowEntityArrayAccessRule: false + disallowEntityArrayAccessRule: true getMailerExistsClassRule: true loadComponentExistsClassRule: true controllerMethodMustBeUsedRule: true diff --git a/src/Rule/Model/DisallowEntityArrayAccessRule.php b/src/Rule/Model/DisallowEntityArrayAccessRule.php index fa50a72..e372299 100644 --- a/src/Rule/Model/DisallowEntityArrayAccessRule.php +++ b/src/Rule/Model/DisallowEntityArrayAccessRule.php @@ -6,12 +6,22 @@ use Cake\Datasource\EntityInterface; use PhpParser\Node; use PhpParser\Node\Expr\ArrayDimFetch; +use PhpParser\Node\Scalar\String_; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; class DisallowEntityArrayAccessRule implements Rule { + /** + * @var list + */ + protected array $allowedKeys = [ + '_matchingData', + '_joinData', + '_ids', + ]; + /** * @inheritDoc */ @@ -38,9 +48,13 @@ public function processNode(Node $node, Scope $scope): array return []; } + if ($node->dim instanceof String_ && in_array($node->dim->value, $this->allowedKeys, true)) { + return []; + } + return [ RuleErrorBuilder::message(sprintf( - 'Array access to entity to %s is not allowed, access as object instead', + 'Array access to entity %s is not allowed, access as object instead', $reflection->getName(), )) ->identifier('cake.entity.arrayAccess') diff --git a/tests/TestCase/Rule/Model/DisallowEntityArrayAccessRuleTest.php b/tests/TestCase/Rule/Model/DisallowEntityArrayAccessRuleTest.php index 62a2a68..cf7c8c1 100644 --- a/tests/TestCase/Rule/Model/DisallowEntityArrayAccessRuleTest.php +++ b/tests/TestCase/Rule/Model/DisallowEntityArrayAccessRuleTest.php @@ -38,24 +38,28 @@ public function testRule(): void // each error consists of the asserted error message, and the asserted error file line $this->analyse([__DIR__ . '/Fake/FailingEntityUseLogic.php'], [ [ - 'Array access to entity to App\Model\Entity\Note is not allowed, access as object instead', - 23, // asserted error line + 'Array access to entity App\Model\Entity\Note is not allowed, access as object instead', + 24, // asserted error line ], [ - 'Array access to entity to App\Model\Entity\Note is not allowed, access as object instead', - 24, // asserted error line + 'Array access to entity App\Model\Entity\Note is not allowed, access as object instead', + 25, // asserted error line + ], + [ + 'Array access to entity App\Model\Entity\Note is not allowed, access as object instead', + 27, // asserted error line ], [ - 'Array access to entity to Cake\Datasource\EntityInterface is not allowed, access as object instead', - 29, + 'Array access to entity Cake\Datasource\EntityInterface is not allowed, access as object instead', + 36, ], [ - 'Array access to entity to App\Model\Entity\User is not allowed, access as object instead', - 31, // asserted error line + 'Array access to entity App\Model\Entity\User is not allowed, access as object instead', + 38, // asserted error line ], [ - 'Array access to entity to App\Model\Entity\Note is not allowed, access as object instead', - 37, // asserted error line + 'Array access to entity App\Model\Entity\Note is not allowed, access as object instead', + 44, // asserted error line ], ]); } diff --git a/tests/TestCase/Rule/Model/Fake/FailingEntityUseLogic.php b/tests/TestCase/Rule/Model/Fake/FailingEntityUseLogic.php index c167531..4e21e6a 100644 --- a/tests/TestCase/Rule/Model/Fake/FailingEntityUseLogic.php +++ b/tests/TestCase/Rule/Model/Fake/FailingEntityUseLogic.php @@ -3,6 +3,7 @@ namespace CakeDC\PHPStan\Test\TestCase\Rule\Model\Fake; +use App\Model\Entity\Note; use Cake\ORM\Locator\LocatorAwareTrait; use SplFixedArray; @@ -23,6 +24,12 @@ public function execute(): array $note = $entity['note']; $notable = 'Notable ' . $entity['note']; $noted = $entity->note; + $note2 = $entity[Note::FIELD_NOTE]; + $matching = $entity['_matchingData'];//allowed access to _matchingData + $matchingUser = $entity['_matchingData']['Users'];//allowed access to _matchingData + $joinData = $entity['_joinData'];//allowed access to _matchingData + $joinDataPosts = $entity['_joinData']['Posts'];//allowed access to _matchingData + $ids = $entity['_ids'];//allowed access to _matchingData //Unknown entity $unknown = $this->fetchTable('UnknownRecords')->get(20); @@ -39,6 +46,12 @@ public function execute(): array 'noted' => $noted, 'notable' => $notable, 'date' => $date, + 'note2' => $note2, + 'matchingData' => $matching, + 'matchingUser' => $matchingUser, + 'joinData' => $joinData, + 'joinDataPosts' => $joinDataPosts, + 'ids' => $ids, ]; } } diff --git a/tests/test_app/Model/Entity/Note.php b/tests/test_app/Model/Entity/Note.php index 2d576bc..d1b3ceb 100644 --- a/tests/test_app/Model/Entity/Note.php +++ b/tests/test_app/Model/Entity/Note.php @@ -15,4 +15,5 @@ */ class Note extends Entity { + public const FIELD_NOTE = 'note'; }