Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
54 / 54
100.00% covered (success)
100.00%
9 / 9
CRAP
100.00% covered (success)
100.00%
1 / 1
Registry
100.00% covered (success)
100.00%
54 / 54
100.00% covered (success)
100.00%
9 / 9
11
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
 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%
23 / 23
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\Collection\Schema;
6
7use Cosray\CollectionListMeta;
8use Cosray\Schema\Badge;
9use Cosray\Schema\Blueprints;
10use Cosray\Schema\Handle;
11use Cosray\Schema\Hidden;
12use Cosray\Schema\Icon;
13use Cosray\Schema\Label;
14use Cosray\Schema\Listing;
15use Cosray\Schema\Order;
16use Cosray\Schema\Permission;
17use Cosray\Schema\Types;
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 $class
53     * @param array<string, mixed> $resolved
54     * @return array<string, mixed>
55     */
56    public function resolveDefaults(string $class, 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($class, $properties);
66        }
67
68        return $properties;
69    }
70
71    public static function withDefaults(): self
72    {
73        $registry = new self();
74        $registry->register(Handle::class, new HandleHandler());
75        $registry->register(Label::class, new LabelHandler());
76        $registry->register(Icon::class, new IconHandler());
77        $registry->register(Badge::class, new BadgeHandler());
78        $registry->register(Permission::class, new PermissionHandler());
79        $registry->register(Hidden::class, new HiddenHandler());
80        $registry->register(Order::class, new OrderHandler());
81        $registry->register(Listing::class, new ListingHandler());
82        $registry->register(Blueprints::class, new BlueprintsHandler());
83        $registry->register(Types::class, new TypesHandler());
84
85        return $registry;
86    }
87
88    private function registerDefaultProperties(): void
89    {
90        $this->default('handle', static fn(
91            string $class,
92            array $properties,
93        ): string => self::deriveHandle(self::className($class)));
94        $this->default(
95            'label',
96            static fn(
97                string $class,
98                array $properties,
99            ): string => (string) preg_replace('/(?<!^)[A-Z]/', ' $0', self::className($class)),
100        );
101        $this->default('icon', static fn(string $class, array $properties): ?array => null);
102        $this->default('badge', static fn(string $class, array $properties): ?string => null);
103        $this->default('permission', static fn(string $class, array $properties): ?string => null);
104        $this->default('hidden', static fn(string $class, array $properties): bool => false);
105        $this->default('order', static fn(string $class, array $properties): int => 0);
106        $this->default(
107            'listing',
108            static fn(string $class, array $properties): CollectionListMeta => new CollectionListMeta(),
109        );
110        $this->default('blueprints', static fn(string $class, array $properties): array => []);
111        $this->default('search', static fn(string $class, array $properties): array => ['uid', 'title']);
112        $this->default('types', static fn(string $class, array $properties): array => []);
113    }
114
115    /**
116     * @param class-string $class
117     */
118    private static function className(string $class): string
119    {
120        return basename(str_replace('\\', '/', $class));
121    }
122
123    private static function deriveHandle(string $className): string
124    {
125        return ltrim(
126            strtolower(preg_replace(
127                '/[A-Z]([A-Z](?![a-z]))*/',
128                '-$0',
129                $className,
130            ) ?? $className),
131            '-',
132        );
133    }
134}