Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
Registry
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
7 / 7
10
100.00% covered (success)
100.00%
1 / 1
 register
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 has
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 all
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 create
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 cached
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 withDefaults
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 assertBlock
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Block;
6
7use Closure;
8use Cosray\Contract\Block;
9use Cosray\Exception\RuntimeException;
10use Cosray\Field\Owner;
11
12/**
13 * The default offer list of block types — what a Blocks field without
14 * `#[Allows]` offers — and the factory building type instances for a
15 * render.
16 */
17final class Registry
18{
19    /** @var list<class-string<Block>> */
20    private array $types = [];
21
22    /** @param class-string<Block> $class */
23    public function register(string $class): void
24    {
25        self::assertBlock($class);
26
27        if (!in_array($class, $this->types, true)) {
28            $this->types[] = $class;
29        }
30    }
31
32    /** @param class-string $class */
33    public function has(string $class): bool
34    {
35        return in_array($class, $this->types, true);
36    }
37
38    /** @return list<class-string<Block>> */
39    public function all(): array
40    {
41        return $this->types;
42    }
43
44    /**
45     * A fresh instance built for the owner's request, like an embedded
46     * class: block types are node-local helpers, never container services,
47     * and never receive a node. The constructor can ask for the Owner and
48     * the types Context::create() provides.
49     *
50     * @param class-string<Block> $class
51     */
52    public function create(string $class, Owner $owner): Block
53    {
54        self::assertBlock($class);
55
56        return $owner->create($class, [Owner::class => $owner]);
57    }
58
59    /**
60     * The factory for one render: each type is created once and reused
61     * for every block of that type.
62     *
63     * @return Closure(class-string<Block>): Block
64     */
65    public function cached(Owner $owner): Closure
66    {
67        $instances = [];
68
69        return function (string $class) use ($owner, &$instances): Block {
70            assert(is_a($class, Block::class, true), 'Blocks are rendered from block classes');
71
72            return $instances[$class] ??= $this->create($class, $owner);
73        };
74    }
75
76    public static function withDefaults(): self
77    {
78        $registry = new self();
79        $registry->register(RichText::class);
80        $registry->register(Heading::class);
81        $registry->register(Image::class);
82        $registry->register(Images::class);
83        $registry->register(Video::class);
84        $registry->register(Youtube::class);
85        $registry->register(Iframe::class);
86
87        return $registry;
88    }
89
90    private static function assertBlock(string $class): void
91    {
92        if (!class_exists($class) || !is_a($class, Block::class, true)) {
93            throw new RuntimeException('Block types must implement ' . Block::class . ": {$class}");
94        }
95    }
96}