Skip to content

Commit

Permalink
Enhancement: Implement Methods\NoParameterWithContainerTypeDeclaratio…
Browse files Browse the repository at this point in the history
…nRule
  • Loading branch information
localheinz committed Sep 13, 2019
1 parent 03bc080 commit 9640d86
Show file tree
Hide file tree
Showing 26 changed files with 688 additions and 8 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

For a full diff see [`0.11.0...master`](https://github.com/localheinz/phpstan-rules/compare/0.11.0...master).

### Added

* Added `Methods\NoParameterWithContainerTypeDeclarationRule`, which reports an error when a method has a type declaration that corresponds to a known dependency injection container or service locator ([#122](https://github.com/localheinz/phpstan-rules/pull/122)), by [@dmecke](https://github.com/dmecke)

## [`0.11.0`](https://github.com/localheinz/phpstan-rules/releases/tag/0.10.0)

For a full diff see [`0.10.0...0.11.0`](https://github.com/localheinz/phpstan-rules/compare/0.10.0...0.11.0).
Expand Down
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ This package provides the following rules for use with [`phpstan/phpstan`](https
* [`Localheinz\PHPStan\Rules\Functions\NoParameterWithNullDefaultValueRule`](https://github.com/localheinz/phpstan-rules#functionsnoparameterwithnulldefaultvaluerule)
* [`Localheinz\PHPStan\Rules\Methods\NoConstructorParameterWithDefaultValueRule`](https://github.com/localheinz/phpstan-rules#methodsnoconstructorparameterwithdefaultvaluerule)
* [`Localheinz\PHPStan\Rules\Methods\NoNullableReturnTypeDeclarationRule`](https://github.com/localheinz/phpstan-rules#methodsnonullablereturntypedeclarationrule)
* [`Localheinz\PHPStan\Rules\Methods\NoParameterWithContainerTypeDeclarationRule`](https://github.com/localheinz/phpstan-rules#methodsnoparamterwithcontainertypedeclarationrule)
* [`Localheinz\PHPStan\Rules\Methods\NoParameterWithNullableTypeDeclarationRule`](https://github.com/localheinz/phpstan-rules#methodsnoparameterwithnullabletypedeclarationrule)
* [`Localheinz\PHPStan\Rules\Methods\NoParameterWithNullDefaultValueRule`](https://github.com/localheinz/phpstan-rules#methodsnoparameterwithnulldefaultvaluerule)
* [`Localheinz\PHPStan\Rules\Statements\NoSwitchRule`](https://github.com/localheinz/phpstan-rules#statementsnoswitchrule)
Expand Down Expand Up @@ -184,6 +185,29 @@ This rule reports an error when a method declared in

uses a nullable return type declaration.

#### `Methods\NoParameterWithContainerTypeDeclarationRule`

This rule reports an error when a method has a type declaration for a known dependency injection container or service locator.

##### Defaults

By default, this rule disallows the use of type declarations indicating an implementation of

* [`Psr\Container\ContainerInterface`](https://github.com/php-fig/container/blob/1.0.0/src/ContainerInterface.php)

is expected to be injected into a method.

##### Configuring container interfaces

If you want to configure the list of interfaces implemented by dependency injection containers and service locators yourself, you can set the `interfacesImplementedByContainers` parameter to a list of interface names:

```neon
parameters:
interfacesImplementedByContainers:
- Fancy\DependencyInjection\ContainerInterface
- Other\ServiceLocatorInterface
```

#### `Methods\NoParameterWithNullableTypeDeclarationRule`

This rule reports an error when a method declared in
Expand Down
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
"localheinz/test-util": "~0.7.0",
"phpstan/phpstan-deprecation-rules": "~0.11.2",
"phpstan/phpstan-strict-rules": "~0.11.1",
"phpunit/phpunit": "^7.5.15"
"phpunit/phpunit": "^7.5.15",
"psr/container": "^1.0.0",
"zendframework/zend-servicemanager": "^2.0.0"
},
"config": {
"preferred-install": "dist",
Expand Down
97 changes: 90 additions & 7 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions rules.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ parameters:
allowAbstractClasses: true
classesAllowedToBeExtended: []
classesNotRequiredToBeAbstractOrFinal: []
interfacesImplementedByContainers:
- Psr\Container\ContainerInterface

parametersSchema:
allowAbstractClasses: bool()
classesAllowedToBeExtended: listOf(string())
classesNotRequiredToBeAbstractOrFinal: listOf(string())
interfacesImplementedByContainers: listOf(string())

rules:
- Localheinz\PHPStan\Rules\Closures\NoNullableReturnTypeDeclarationRule
Expand Down Expand Up @@ -40,3 +43,10 @@ services:
classesAllowedToBeExtended: %classesAllowedToBeExtended%
tags:
- phpstan.rules.rule

-
class: Localheinz\PHPStan\Rules\Methods\NoParameterWithContainerTypeDeclarationRule
arguments:
interfacesImplementedByContainers: %interfacesImplementedByContainers%
tags:
- phpstan.rules.rule
148 changes: 148 additions & 0 deletions src/Methods/NoParameterWithContainerTypeDeclarationRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<?php

declare(strict_types=1);

/**
* Copyright (c) 2018 Andreas Möller
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*
* @see https://github.com/localheinz/phpstan-rules
*/

namespace Localheinz\PHPStan\Rules\Methods;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Broker\Broker;
use PHPStan\Reflection;
use PHPStan\Rules\Rule;
use PHPStan\ShouldNotHappenException;

final class NoParameterWithContainerTypeDeclarationRule implements Rule
{
private $broker;

private $interfacesImplementedByContainers;

/**
* @param string[] $interfacesImplementedByContainers
* @param Broker $broker
*/
public function __construct(Broker $broker, array $interfacesImplementedByContainers)
{
$this->broker = $broker;
$this->interfacesImplementedByContainers = \array_filter(
\array_map(static function (string $interfaceImplementedByContainers): string {
return $interfaceImplementedByContainers;
}, $interfacesImplementedByContainers),
static function (string $interfaceImplementedByContainer): bool {
return \interface_exists($interfaceImplementedByContainer);
}
);
}

public function getNodeType(): string
{
return Node\Stmt\ClassMethod::class;
}

public function processNode(Node $node, Scope $scope): array
{
if (!$node instanceof Node\Stmt\ClassMethod) {
throw new ShouldNotHappenException(\sprintf(
'Expected node to be instance of "%s", but got instance of "%s" instead.',
Node\Stmt\ClassMethod::class,
\get_class($node)
));
}

if (0 === \count($this->interfacesImplementedByContainers)) {
return [];
}

if (0 === \count($node->params)) {
return [];
}

$methodName = $node->name->toString();

/** @var Reflection\ClassReflection $containingClass */
$containingClass = $scope->getClassReflection();

return \array_reduce(
$node->params,
function (array $errors, Node\Param $node) use ($containingClass, $methodName) {
$type = $node->type;

if (!$type instanceof Node\Name) {
return $errors;
}

/** @var Node\Expr\Variable $variable */
$variable = $node->var;

/** @var string $parameterName */
$parameterName = $variable->name;

$classUsedInTypeDeclaration = $this->broker->getClass($type->toCodeString());

if ($classUsedInTypeDeclaration->isInterface()) {
foreach ($this->interfacesImplementedByContainers as $interfaceImplementedByContainer) {
if ($classUsedInTypeDeclaration->getNativeReflection()->isSubclassOf($interfaceImplementedByContainer)) {
$errors[] = self::createError(
$containingClass,
$methodName,
$parameterName,
$classUsedInTypeDeclaration
);

return $errors;
}
}
}

foreach ($this->interfacesImplementedByContainers as $interfaceImplementedByContainer) {
if ($classUsedInTypeDeclaration->getNativeReflection()->implementsInterface($interfaceImplementedByContainer)) {
$errors[] = self::createError(
$containingClass,
$methodName,
$parameterName,
$classUsedInTypeDeclaration
);

return $errors;
}
}

return $errors;
},
[]
);
}

private static function createError(
Reflection\ClassReflection $classReflection,
string $methodName,
string $parameterName,
Reflection\ClassReflection $classUsedInTypeDeclaration
): string {
if ($classReflection->isAnonymous()) {
return \sprintf(
'Method %s() in anonymous class has a parameter $%s with a type declaration of %s, but containers should not be injected.',
$methodName,
$parameterName,
$classUsedInTypeDeclaration->getName()
);
}

return \sprintf(
'Method %s::%s() has a parameter $%s with a type declaration of %s, but containers should not be injected.',
$classReflection->getName(),
$methodName,
$parameterName,
$classUsedInTypeDeclaration->getName()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Localheinz\PHPStan\Rules\Test\Fixture\Methods\NoParameterWithContainerTypeDeclarationRule\Failure;

use Psr\Container;

final class ClassImplementingContainerInterface implements Container\ContainerInterface
{
public function get($id): void
{
}

public function has($id): void
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Localheinz\PHPStan\Rules\Test\Fixture\Methods\NoParameterWithContainerTypeDeclarationRule\Failure;

final class ClassWithMethodWithParameterWithClassImplementingContainerInterfaceAsTypeDeclaration
{
public function method(ClassImplementingContainerInterface $container): void
{
}
}
Loading

0 comments on commit 9640d86

Please sign in to comment.