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