Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.67% covered (success)
91.67%
22 / 24
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
ValueObjectWithArrayShouldBeComposite
91.67% covered (success)
91.67%
22 / 24
75.00% covered (warning)
75.00%
3 / 4
12.08
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
90.48% covered (success)
90.48%
19 / 21
0.00% covered (danger)
0.00%
0 / 1
9.07
 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\Attributes\SchemaMethod;
5use Apie\Core\ValueObjects\CompositeValueObject;
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;
14
15/**
16 * If value objects have array or stdClass as return type then they require the CompositeValueObject trait
17 * as the spec has no defined behaviour for value objects with arrays otherwise.
18 *
19 * @implements Rule<Class_>
20 */
21final class ValueObjectWithArrayShouldBeComposite 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->isAbstract() || str_starts_with($nodeName, 'Anonymous') || $node->isAnonymous()) {
40            return [];
41        }
42        $class = $this->getClass($node, $scope);
43        if ($class->implementsInterface(ValueObjectInterface::class)) {
44            $method = $class->getMethod('toNative', $scope);
45            foreach ($method->getVariants() as $variant) {
46                if ($variant->getNativeReturnType()->isArray()->yes()) {
47                    if (!in_array(CompositeValueObject::class, $class->getNativeReflection()->getTraitNames())) {
48                        if (empty($class->getNativeReflection()->getAttributes(SchemaMethod::class))) {
49                            return [
50                                RuleErrorBuilder::message(
51                                    sprintf(
52                                        "Class '%s' is a value object that returns an array, but it does not use CompositeValueObject trait.",
53                                        $nodeName
54                                    )
55                                )->identifier('apie.array.value.object')
56                                ->build()
57                            ];
58                        }
59                    }
60                }
61            }
62        }
63        return [
64        ];
65    }
66
67    private function getClass(Class_ $node, Scope $scope): ClassReflection
68    {
69        return $this->reflectionProvider->getClass($scope->getNamespace() . '\\' . $node->name->toString());
70    }
71}