Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
ObjectShouldNotImplementMultipleInterfaces
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
4 / 4
12
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNodeType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 processNode
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
1 / 1
9
 getClass
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2namespace Apie\ApiePhpstanRules;
3
4use Apie\Core\Dto\DtoInterface;
5use Apie\Core\Entities\EntityInterface;
6use Apie\Core\ValueObjects\Interfaces\ValueObjectInterface;
7use PhpParser\Node;
8use PhpParser\Node\Stmt\Class_;
9use PHPStan\Analyser\Scope;
10use PHPStan\Reflection\ClassReflection;
11use PHPStan\Reflection\ReflectionProvider;
12use PHPStan\Rules\Rule;
13use PHPStan\Rules\RuleErrorBuilder;
14use Psr\Http\Message\UploadedFileInterface;
15use Throwable;
16
17/**
18 * Ensure a class is not a value object and an entity at the same time for example.
19 * @implements Rule<Class_>
20 */
21final class ObjectShouldNotImplementMultipleInterfaces implements Rule
22{
23    public function __construct(
24        private ReflectionProvider $reflectionProvider
25    ) {
26    }
27
28    public function getNodeType(): string
29    {
30        return Class_::class;
31    }
32
33    /**
34     * @param Class_ $node
35     */
36    public function processNode(Node $node, Scope $scope): array
37    {
38        $nodeName = $node->name->toString();
39        if ($node->isAnonymous() || str_starts_with($nodeName, 'Anonymous')) {
40            return [];
41        }
42        $class = $this->getClass($node, $scope);
43        $interfacesToCheck = [
44            UploadedFileInterface::class,
45            ValueObjectInterface::class,
46            DtoInterface::class,
47            EntityInterface::class,
48            Throwable::class
49        ];
50        $conflicted = [];
51        foreach ($interfacesToCheck as $interfaceToCheck) {
52            if ($class->implementsInterface($interfaceToCheck)) {
53                foreach ($interfacesToCheck as $shouldNotBeImplemented) {
54                    if ($shouldNotBeImplemented === $interfaceToCheck) {
55                        continue;
56                    }
57                    if ($class->implementsInterface($shouldNotBeImplemented)) {
58                        $conflicted[$shouldNotBeImplemented] = $shouldNotBeImplemented;
59                    }
60                }
61            }
62        }
63        if (!empty($conflicted)) {
64            return [
65                RuleErrorBuilder::message(
66                    sprintf(
67                        "Class '%s' has conflicting interfaces: %s",
68                        $nodeName,
69                        implode(', ', $conflicted)
70                    )
71                )->identifier('apie.conflicting.interface')
72                ->build()
73            ];
74        }
75        return [
76        ];
77    }
78
79    private function getClass(Class_ $node, Scope $scope): ClassReflection
80    {
81        return $this->reflectionProvider->getClass($scope->getNamespace() . '\\' . $node->name->toString());
82    }
83}