Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
61 / 61
100.00% covered (success)
100.00%
10 / 10
CRAP
100.00% covered (success)
100.00%
1 / 1
Registry
100.00% covered (success)
100.00%
61 / 61
100.00% covered (success)
100.00%
10 / 10
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
 register
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHandler
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 default
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 resolveDefaults
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 defaultKeys
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withDefaults
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 registerDefaultProperties
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
1
 className
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 deriveHandle
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Node\Schema;
6
7use Cosray\Schema\Children;
8use Cosray\Schema\Deletable;
9use Cosray\Schema\FieldOrder;
10use Cosray\Schema\Handle;
11use Cosray\Schema\Icon;
12use Cosray\Schema\Label;
13use Cosray\Schema\Labels;
14use Cosray\Schema\Permission;
15use Cosray\Schema\Render;
16use Cosray\Schema\Route;
17use Cosray\Schema\Title;
18use Cosray\Security\NodeAccess;
19
20class Registry
21{
22    /** @var array<class-string, Handler> */
23    private array $handlers = [];
24
25    /** @var array<string, callable(class-string, array<string, mixed>): mixed> */
26    private array $defaults = [];
27
28    public function __construct()
29    {
30        $this->registerDefaultProperties();
31    }
32
33    /** @param class-string $schema */
34    public function register(string $schema, Handler $handler): void
35    {
36        $this->handlers[$schema] = $handler;
37    }
38
39    public function getHandler(object $schema): ?Handler
40    {
41        return $this->handlers[$schema::class] ?? null;
42    }
43
44    /**
45     * @param callable(class-string, array<string, mixed>): mixed $resolver
46     */
47    public function default(string $key, callable $resolver): void
48    {
49        $this->defaults[$key] = $resolver;
50    }
51
52    /**
53     * @param class-string $nodeClass
54     * @param array<string, mixed> $resolved
55     * @return array<string, mixed>
56     */
57    public function resolveDefaults(string $nodeClass, array $resolved): array
58    {
59        $properties = $resolved;
60
61        foreach ($this->defaults as $key => $resolver) {
62            if (array_key_exists($key, $properties)) {
63                continue;
64            }
65
66            $properties[$key] = $resolver($nodeClass, $properties);
67        }
68
69        return $properties;
70    }
71
72    /** @return list<string> */
73    public function defaultKeys(): array
74    {
75        return array_keys($this->defaults);
76    }
77
78    public static function withDefaults(): self
79    {
80        $registry = new self();
81        $registry->register(Handle::class, new HandleHandler());
82        $registry->register(Label::class, new LabelHandler());
83        $registry->register(Icon::class, new IconHandler());
84        $registry->register(Route::class, new RouteHandler());
85        $registry->register(Render::class, new RenderHandler());
86        $registry->register(Permission::class, new PermissionHandler());
87        $registry->register(Title::class, new TitleHandler());
88        $registry->register(FieldOrder::class, new FieldOrderHandler());
89        $registry->register(Deletable::class, new DeletableHandler());
90        $registry->register(Labels::class, new LabelsHandler());
91        $registry->register(Children::class, new ChildrenHandler());
92
93        return $registry;
94    }
95
96    private function registerDefaultProperties(): void
97    {
98        $this->default('handle', static fn(
99            string $nodeClass,
100            array $properties,
101        ): string => self::deriveHandle(self::className($nodeClass)));
102        $this->default('label', static fn(
103            string $nodeClass,
104            array $properties,
105        ): string => self::className($nodeClass));
106        $this->default('icon', static fn(string $nodeClass, array $properties): ?array => null);
107        $this->default(
108            'renderer',
109            static fn(string $nodeClass, array $properties): string => (string) ($properties['handle'] ?? ''),
110        );
111        $this->default('route', static fn(string $nodeClass, array $properties): string => '');
112        $this->default('routable', static fn(string $nodeClass, array $properties): bool => false);
113        $this->default(
114            'renderable',
115            static fn(string $nodeClass, array $properties): bool => $properties['routable'] ?? false,
116        );
117        $this->default('permission', static fn(string $nodeClass, array $properties): array => NodeAccess::defaults(
118            (bool) ($properties['routable'] ?? false),
119        ));
120        $this->default('titleField', static fn(string $nodeClass, array $properties): ?string => null);
121        $this->default('titleEmbedded', static fn(string $nodeClass, array $properties): ?string => null);
122        $this->default('fieldOrder', static fn(string $nodeClass, array $properties): ?array => null);
123        $this->default('deletable', static fn(string $nodeClass, array $properties): bool => true);
124        $this->default('children', static fn(string $nodeClass, array $properties): array => []);
125        $this->default('sortableChildren', static fn(string $nodeClass, array $properties): bool => false);
126    }
127
128    /**
129     * @param class-string $nodeClass
130     */
131    private static function className(string $nodeClass): string
132    {
133        return basename(str_replace('\\', '/', $nodeClass));
134    }
135
136    private static function deriveHandle(string $className): string
137    {
138        return ltrim(
139            strtolower(preg_replace(
140                '/[A-Z]([A-Z](?![a-z]))*/',
141                '-$0',
142                $className,
143            ) ?? $className),
144            '-',
145        );
146    }
147}