diff --git a/README.md b/README.md index c9e99b6..18f1c0f 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,12 @@ This extension provides the following features: ### Rules -* Checks if the string argument passed to `config()` or `model()` function is a valid class string extending `CodeIgniter\Config\BaseConfig` or `CodeIgniter\Model`, respectively. This can be turned off by setting `codeigniter.checkArgumentTypeOfFactories: false` in your `phpstan.neon`. +* Checks if the string argument passed to `config()` or `model()` function is a valid class string extending +`CodeIgniter\Config\BaseConfig` or `CodeIgniter\Model`, respectively. This can be turned off by setting +`codeigniter.checkArgumentTypeOfFactories: false` in your `phpstan.neon`. For fine-grained control, you can +individually choose which factory function to disable using `codeigniter.checkArgumentTypeOfConfig` and +`codeigniter.checkArgumentTypeOfModel`. **NOTE:** Setting `codeigniter.checkArgumentTypeOfFactories: false` will effectively +bypass the two specific options. * Checks if the string argument passed to `service()` or `single_service()` function is a valid service name. This can be turned off by setting `codeigniter.checkArgumentTypeOfServices: false` in your `phpstan.neon`. * Disallows instantiating cache handlers using `new` and suggests to use the `CacheFactory` class instead. * Disallows instantiating `FrameworkException` classes using `new`. diff --git a/extension.neon b/extension.neon index 8288897..9af541b 100644 --- a/extension.neon +++ b/extension.neon @@ -12,6 +12,8 @@ parameters: additionalServices: [] notStringFormattedFields: [] checkArgumentTypeOfFactories: true + checkArgumentTypeOfConfig: true + checkArgumentTypeOfModel: true checkArgumentTypeOfServices: true parametersSchema: @@ -21,6 +23,8 @@ parametersSchema: additionalServices: listOf(string()) notStringFormattedFields: arrayOf(string()) checkArgumentTypeOfFactories: bool() + checkArgumentTypeOfConfig: bool() + checkArgumentTypeOfModel: bool() checkArgumentTypeOfServices: bool() ]) @@ -76,6 +80,9 @@ services: # conditional rules - class: CodeIgniter\PHPStan\Rules\Functions\FactoriesFunctionArgumentTypeRule + arguments: + checkArgumentTypeOfConfig: %codeigniter.checkArgumentTypeOfConfig% + checkArgumentTypeOfModel: %codeigniter.checkArgumentTypeOfModel% - class: CodeIgniter\PHPStan\Rules\Functions\ServicesFunctionArgumentTypeRule diff --git a/src/Rules/Functions/FactoriesFunctionArgumentTypeRule.php b/src/Rules/Functions/FactoriesFunctionArgumentTypeRule.php index a0b4e82..cfef140 100644 --- a/src/Rules/Functions/FactoriesFunctionArgumentTypeRule.php +++ b/src/Rules/Functions/FactoriesFunctionArgumentTypeRule.php @@ -38,10 +38,22 @@ final class FactoriesFunctionArgumentTypeRule implements Rule 'model' => Model::class, ]; + /** + * @var array + */ + private array $argumentTypeCheck = []; + public function __construct( private readonly ReflectionProvider $reflectionProvider, - private readonly FactoriesReturnTypeHelper $factoriesReturnTypeHelper - ) {} + private readonly FactoriesReturnTypeHelper $factoriesReturnTypeHelper, + bool $checkArgumentTypeOfConfig, + bool $checkArgumentTypeOfModel + ) { + $this->argumentTypeCheck = [ + 'config' => $checkArgumentTypeOfConfig, + 'model' => $checkArgumentTypeOfModel, + ]; + } public function getNodeType(): string { @@ -104,6 +116,10 @@ public function processNode(Node $node, Scope $scope): array } if (! (new ObjectType($this->instanceofMap[$function]))->isSuperTypeOf($returnType)->yes()) { + if (! $this->argumentTypeCheck[$function]) { + return []; + } + return [RuleErrorBuilder::message(sprintf( 'Argument #1 $%s (%s) passed to function %s does not extend %s.', $firstParameter->getName(), diff --git a/tests/Fixtures/Rules/Functions/bug-8.php b/tests/Fixtures/Rules/Functions/bug-8.php new file mode 100644 index 0000000..93543b7 --- /dev/null +++ b/tests/Fixtures/Rules/Functions/bug-8.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +class NotificationModel +{ + public string $returnType = 'object'; +} + +model(NotificationModel::class); diff --git a/tests/Rules/Functions/FactoriesFunctionArgumentTypeRuleTest.php b/tests/Rules/Functions/FactoriesFunctionArgumentTypeRuleTest.php index ccbaf33..10ca60e 100644 --- a/tests/Rules/Functions/FactoriesFunctionArgumentTypeRuleTest.php +++ b/tests/Rules/Functions/FactoriesFunctionArgumentTypeRuleTest.php @@ -30,11 +30,24 @@ final class FactoriesFunctionArgumentTypeRuleTest extends RuleTestCase { use AdditionalConfigFilesTrait; + private bool $checkArgumentTypeOfConfig; + private bool $checkArgumentTypeOfModel; + + protected function setUp(): void + { + parent::setUp(); + + $this->checkArgumentTypeOfConfig = true; + $this->checkArgumentTypeOfModel = true; + } + protected function getRule(): Rule { return new FactoriesFunctionArgumentTypeRule( self::createReflectionProvider(), - self::getContainer()->getByType(FactoriesReturnTypeHelper::class) + self::getContainer()->getByType(FactoriesReturnTypeHelper::class), + $this->checkArgumentTypeOfConfig, + $this->checkArgumentTypeOfModel ); } @@ -90,4 +103,10 @@ public function testRule(): void ], ]); } + + public function testAllowNonModelClassesOnModelCall(): void + { + $this->checkArgumentTypeOfModel = false; + $this->analyse([__DIR__ . '/../../Fixtures/Rules/Functions/bug-8.php'], []); + } }