Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.00% covered (success)
90.00%
18 / 20
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
RegexValueObjectShouldImplementInterface
90.00% covered (success)
90.00%
18 / 20
75.00% covered (warning)
75.00%
3 / 4
8.06
0.00% covered (danger)
0.00%
0 / 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
88.24% covered (warning)
88.24%
15 / 17
0.00% covered (danger)
0.00%
0 / 1
5.04
 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\ValueObjects\Interfaces\HasRegexValueObjectInterface;
5use Apie\Core\ValueObjects\IsStringWithRegexValueObject;
6use PhpParser\Node;
7use PhpParser\Node\Stmt\Class_;
8use PHPStan\Analyser\Scope;
9use PHPStan\Reflection\ClassReflection;
10use PHPStan\Reflection\ReflectionProvider;
11use PHPStan\Rules\Rule;
12use PHPStan\Rules\RuleErrorBuilder;
13
14/**
15 * @implements Rule<Class_>
16 */
17final class RegexValueObjectShouldImplementInterface implements Rule
18{
19    public function __construct(
20        private ReflectionProvider $reflectionProvider
21    ) {
22    }
23
24    public function getNodeType(): string
25    {
26        return Class_::class;
27    }
28
29    /**
30     * @param Class_ $node
31     */
32    public function processNode(Node $node, Scope $scope): array
33    {
34        $nodeName = $node->name->toString();
35        if ($node->isAnonymous() || str_starts_with($nodeName, 'Anonymous')) {
36            return [];
37        }
38        $class = $this->getClass($node, $scope);
39        $nativeReflection = $class->getNativeReflection();
40        if (
41            in_array(IsStringWithRegexValueObject::class, $nativeReflection->getTraitNames(), true)
42            && !$class->implementsInterface(HasRegexValueObjectInterface::class)
43        ) {
44            return [
45                RuleErrorBuilder::message(
46                    sprintf(
47                        "Class '%s' uses IsStringWithRegexValueObject trait, but does not implement HasRegexValueObjectInterface.",
48                        $nodeName
49                    )
50                )->identifier('apie.regex.value.object.interface')
51                ->build()
52            ];
53        }
54        return [];
55    }
56
57    private function getClass(Class_ $node, Scope $scope): ClassReflection
58    {
59        return $this->reflectionProvider->getClass($scope->getNamespace() . '\\' . $node->name->toString());
60    }
61}