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%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 registerDefaultProperties
100.00% covered (success)
100.00%
31 / 31
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\Permission;
14use Cosray\Schema\Render;
15use Cosray\Schema\Route;
16use Cosray\Schema\Title;
17
18class Registry
19{
20    /** @var array<class-string, Handler> */
21    private array $handlers = [];
22
23    /** @var array<string, callable(class-string, array<string, mixed>): mixed> */
24    private array $defaults = [];
25
26    public function __construct()
27    {
28        $this->registerDefaultProperties();
29    }
30
31    /** @param class-string $schema */
32    public function register(string $schema, Handler $handler): void
33    {
34        $this->handlers[$schema] = $handler;
35    }
36
37    public function getHandler(object $schema): ?Handler
38    {
39        return $this->handlers[$schema::class] ?? null;
40    }
41
42    /**
43     * @param callable(class-string, array<string, mixed>): mixed $resolver
44     */
45    public function default(string $key, callable $resolver): void
46    {
47        $this->defaults[$key] = $resolver;
48    }
49
50    /**
51     * @param class-string $nodeClass
52     * @param array<string, mixed> $resolved
53     * @return array<string, mixed>
54     */
55    public function resolveDefaults(string $nodeClass, array $resolved): array
56    {
57        $properties = $resolved;
58
59        foreach ($this->defaults as $key => $resolver) {
60            if (array_key_exists($key, $properties)) {
61                continue;
62            }
63
64            $properties[$key] = $resolver($nodeClass, $properties);
65        }
66
67        return $properties;
68    }
69
70    /** @return list<string> */
71    public function defaultKeys(): array
72    {
73        return array_keys($this->defaults);
74    }
75
76    public static function withDefaults(): self
77    {
78        $registry = new self();
79        $registry->register(Handle::class, new HandleHandler());
80        $registry->register(Label::class, new LabelHandler());
81        $registry->register(Icon::class, new IconHandler());
82        $registry->register(Route::class, new RouteHandler());
83        $registry->register(Render::class, new RenderHandler());
84        $registry->register(Permission::class, new PermissionHandler());
85        $registry->register(Title::class, new TitleHandler());
86        $registry->register(FieldOrder::class, new FieldOrderHandler());
87        $registry->register(Deletable::class, new DeletableHandler());
88        $registry->register(Children::class, new ChildrenHandler());
89
90        return $registry;
91    }
92
93    private function registerDefaultProperties(): void
94    {
95        $this->default('handle', static fn(
96            string $nodeClass,
97            array $properties,
98        ): string => self::deriveHandle(self::className($nodeClass)));
99        $this->default('label', static fn(
100            string $nodeClass,
101            array $properties,
102        ): string => self::className($nodeClass));
103        $this->default('icon', static fn(string $nodeClass, array $properties): ?array => null);
104        $this->default(
105            'renderer',
106            static fn(string $nodeClass, array $properties): string => (string) (
107                $properties['handle'] ?? ''
108            ),
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('fieldOrder', static fn(string $nodeClass, array $properties): ?array => null);
124        $this->default('deletable', static fn(string $nodeClass, array $properties): bool => true);
125        $this->default('children', static fn(string $nodeClass, array $properties): array => []);
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            )),
144            '-',
145        );
146    }
147}