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    public static function getRegularExpression(): string
33    {
34        return '/^([A-Z][a-zA-Z0-9]*\\\\)+$/';
35    }
36
37    protected function convert(string $input): string
38    {
39        return str_ends_with($input, '\\') ? $input : ($input . '\\');
40    }
41
42    /**
43     * @return ReflectionClass<covariant object>
44     */
45    public function toClass(string $className): ReflectionClass
46    {
47        return new ReflectionClass($this->internal . $className);
48    }
49
50    public function getParentNamespace(): self
51    {
52        $internal = explode('\\', rtrim($this->internal, '\\'));
53        array_pop($internal);
54        return new self(implode('\\', $internal));
55    }
56
57    public function getChildNamespace(string $childNamespace): self
58    {
59        return new self($this->internal . $childNamespace);
60    }
61
62    public function toTestNamespace(): self
63    {
64        $internal = explode('\\', rtrim($this->internal, '\\'));
65        $last = array_pop($internal);
66        array_push($internal, 'Tests', $last);
67        return new self(implode('\\', $internal));
68    }
69
70    /**
71     * Returns all classes found in $path assuming the namespace of this value object.
72     */
73    public function getClasses(string $path): ReflectionClassList
74    {
75        $classes = [];
76        if (!file_exists($path) || !is_dir($path)) {
77            return new ReflectionClassList([]);
78        }
79        foreach (Finder::create()->in($path)->files()->name('*.php')->depth('== 0') as $file) {
80            $classes[] = $this->toClass($file->getBasename('.php'));
81        }
82        sort($classes);
83        return new ReflectionClassList($classes);
84    }
85
86    /**
87     * Returns all non-magic methods found in $path assuming the namespace of this value object.
88     */
89    public function getMethods(string $path): ReflectionMethodList
90    {
91        $methods = [];
92        if (!file_exists($path) || !is_dir($path)) {
93            return new ReflectionMethodList([]);
94        }
95        foreach (Finder::create()->in($path)->files()->name('*.php')->depth('== 0') as $file) {
96            $class = $this->toClass($file->getBasename('.php'));
97            foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
98                if (strpos($method->name, '__') !== 0 || $method->name === '__invoke') {
99                    $methods[] = $method;
100                }
101            }
102        }
103        sort($methods);
104        return new ReflectionMethodList($methods);
105    }
106
107    public static function createRandom(Generator $generator): self
108    {
109        if (!isset(self::$list)) {
110            self::$list = array_filter(
111                array_map(
112                    function (string $class) {
113                        return (new ReflectionClass($class))->getNamespaceName();
114                    },
115                    get_declared_classes()
116                ),
117                function (string $namespace) {
118                    return preg_match(
119                        EntityNamespace::getRegularExpression(),
120                        $namespace . '\\'
121                    );
122                }
123            );
124            self::$list[] = 'Symfony\Component';
125        }
126        return self::fromNative($generator->randomElement(self::$list));
127    }
128}