Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow specific argument type checks for factories #14

Merged
merged 1 commit into from
Nov 14, 2023
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
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
7 changes: 7 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ parameters:
additionalServices: []
notStringFormattedFields: []
checkArgumentTypeOfFactories: true
checkArgumentTypeOfConfig: true
checkArgumentTypeOfModel: true
checkArgumentTypeOfServices: true

parametersSchema:
Expand All @@ -21,6 +23,8 @@ parametersSchema:
additionalServices: listOf(string())
notStringFormattedFields: arrayOf(string())
checkArgumentTypeOfFactories: bool()
checkArgumentTypeOfConfig: bool()
checkArgumentTypeOfModel: bool()
checkArgumentTypeOfServices: bool()
])

Expand Down Expand Up @@ -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
Expand Down
20 changes: 18 additions & 2 deletions src/Rules/Functions/FactoriesFunctionArgumentTypeRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,22 @@ final class FactoriesFunctionArgumentTypeRule implements Rule
'model' => Model::class,
];

/**
* @var array<string, bool>
*/
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
{
Expand Down Expand Up @@ -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(),
Expand Down
19 changes: 19 additions & 0 deletions tests/Fixtures/Rules/Functions/bug-8.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) 2023 CodeIgniter Foundation <admin@codeigniter.com>
*
* 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);
21 changes: 20 additions & 1 deletion tests/Rules/Functions/FactoriesFunctionArgumentTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}

Expand Down Expand Up @@ -90,4 +103,10 @@ public function testRule(): void
],
]);
}

public function testAllowNonModelClassesOnModelCall(): void
{
$this->checkArgumentTypeOfModel = false;
$this->analyse([__DIR__ . '/../../Fixtures/Rules/Functions/bug-8.php'], []);
}
}