Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.78% covered (success)
97.78%
44 / 45
88.89% covered (warning)
88.89%
8 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
EntityNamespace
97.78% covered (success)
97.78%
44 / 45
88.89% covered (warning)
88.89%
8 / 9
20
0.00% covered (danger)
0.00%
0 / 1
 getRegularExpression
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 convert
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 toClass
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getParentNamespace
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getChildNamespace
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 toTestNamespace
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getClasses
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 getMethods
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
7.05
 createRandom
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2namespace Apie\Common\ValueObjects;
3
4use Apie\Core\Attributes\Description;
5use Apie\Core\Attributes\ExampleValue;
6use Apie\Core\Attributes\FakeMethod;
7use Apie\Core\Lists\ReflectionClassList;
8use Apie\Core\Lists\ReflectionMethodList;
9use Apie\Core\ValueObjects\Interfaces\HasRegexValueObjectInterface;
10use Apie\Core\ValueObjects\Interfaces\StringValueObjectInterface;
11use Apie\Core\ValueObjects\IsStringWithRegexValueObject;
12use Faker\Generator;
13use ReflectionClass;
14use ReflectionMethod;
15use Symfony\Component\Finder\Finder;
16
17/**
18 * Value object that represents a PHP class namespace.
19 *
20 * Example values:
21 * "Apie\Common\"
22 * "Symfony\Component\"
23 */
24#[Description('A PHP class namespace with trailing slash, for example: "Symfony\Component\\"')]
25#[ExampleValue("Symfony\Component\\")]
26#[FakeMethod('createRandom')]
27final class EntityNamespace implements StringValueObjectInterface, HasRegexValueObjectInterface
28{
29    use IsStringWithRegexValueObject;
30    /** @var array<int, string> */
31    private static array $list;
32
33    public static function getRegularExpression(): string
34    {
35        return '/^([A-Z][a-zA-Z0-9]*\\\\)+$/';
36    }
37
38    protected function convert(string $input): string
39    {
40        return str_ends_with($input, '\\') ? $input : ($input . '\\');
41    }
42
43    /**
44     * @return ReflectionClass<covariant object>
45     */
46    public function toClass(string $className): ReflectionClass
47    {
48        return new ReflectionClass($this->internal . $className);
49    }
50
51    public function getParentNamespace(): self
52    {
53        $internal = explode('\\', rtrim($this->internal, '\\'));
54        array_pop($internal);
55        return new self(implode('\\', $internal));
56    }
57
58    public function getChildNamespace(string $childNamespace): self
59    {
60        return new self($this->internal . $childNamespace);
61    }
62
63    public function toTestNamespace(): self
64    {
65        $internal = explode('\\', rtrim($this->internal, '\\'));
66        $last = array_pop($internal);
67        array_push($internal, 'Tests', $last);
68        return new self(implode('\\', $internal));
69    }
70
71    /**
72     * Returns all classes found in $path assuming the namespace of this value object.
73     */
74    public function getClasses(string $path): ReflectionClassList
75    {
76        $classes = [];
77        if (!file_exists($path) || !is_dir($path)) {
78            return new ReflectionClassList([]);
79        }
80        foreach (Finder::create()->in($path)->files()->name('*.php')->depth('== 0') as $file) {
81            $classes[] = $this->toClass($file->getBasename('.php'));
82        }
83        sort($classes);
84        return new ReflectionClassList($classes);
85    }
86
87    /**
88     * Returns all non-magic methods found in $path assuming the namespace of this value object.
89     */
90    public function getMethods(string $path): ReflectionMethodList
91    {
92        $methods = [];
93        if (!file_exists($path) || !is_dir($path)) {
94            return new ReflectionMethodList([]);
95        }
96        foreach (Finder::create()->in($path)->files()->name('*.php')->depth('== 0') as $file) {
97            $class = $this->toClass($file->getBasename('.php'));
98            foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
99                if (strpos($method->name, '__') !== 0 || $method->name === '__invoke') {
100                    $methods[] = $method;
101                }
102            }
103        }
104        sort($methods);
105        return new ReflectionMethodList($methods);
106    }
107
108    public static function createRandom(Generator $generator): self
109    {
110        if (!isset(self::$list)) {
111            self::$list = array_filter(
112                array_map(
113                    function (string $class) {
114                        return (new ReflectionClass($class))->getNamespaceName();
115                    },
116                    get_declared_classes()
117                ),
118                function (string $namespace) {
119                    return preg_match(
120                        EntityNamespace::getRegularExpression(),
121                        $namespace . '\\'
122                    );
123                }
124            );
125            self::$list[] = 'Symfony\Component';
126        }
127        return self::fromNative($generator->randomElement(self::$list));
128    }
129}