Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
58 / 58
100.00% covered (success)
100.00%
10 / 10
CRAP
100.00% covered (success)
100.00%
1 / 1
Factory
100.00% covered (success)
100.00%
58 / 58
100.00% covered (success)
100.00%
10 / 10
11
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 create
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
1 / 1
2
 proxy
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 blueprint
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 dataFor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fieldNamesFor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNodeState
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 meta
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hydrator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 uid
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Node;
6
7use Celemas\Container\Container;
8use Celemas\Core\Factory\Factory as CoreFactory;
9use Celemas\Core\Request;
10use Celemas\Quma\Database;
11use Celemas\Wire\Creator;
12use Cosray\Cms;
13use Cosray\Config;
14use Cosray\Context;
15use Cosray\Field\FieldHydrator;
16use Cosray\Field\Schema\Registry as SchemaRegistry;
17use Cosray\Node\Contract\HasInit;
18use Cosray\Uid;
19use WeakMap;
20
21class Factory
22{
23    /** @var WeakMap<object, array{data: array, fieldNames: string[]}> */
24    private static WeakMap $nodeState;
25
26    private readonly FieldHydrator $hydrator;
27    private readonly Types $types;
28
29    public function __construct(
30        private readonly Container $container,
31        Types $types,
32        private readonly Uid $uid,
33        ?SchemaRegistry $schemaRegistry = null,
34    ) {
35        $this->hydrator = new FieldHydrator($schemaRegistry ?? SchemaRegistry::withDefaults());
36        $this->types = $types;
37        self::$nodeState ??= new WeakMap();
38    }
39
40    /**
41     * Create a node instance from a class name and raw DB data.
42     *
43     * Uses Wire Creator for autowired construction,
44     * then FieldHydrator for field initialization.
45     */
46    public function create(string $class, Context $context, Cms $cms, array $data): object
47    {
48        $serializer = new Serializer($this->hydrator, $this->types, $this->uid);
49        $store = new Store($context->db, new PathManager(), $this->types, $this->uid);
50        $templateRenderer = new ViewRenderer(
51            $this->container,
52            $context->factory,
53            $this->hydrator,
54            $this->types,
55        );
56
57        $creator = new Creator($this->container);
58        $type = $this->types->typeOf($class);
59        $node = $creator->create($class, predefinedTypes: [
60            Context::class => $context,
61            Cms::class => $cms,
62            Request::class => $context->request,
63            Config::class => $context->config,
64            Database::class => $context->db,
65            Container::class => $context->container,
66            CoreFactory::class => $context->factory,
67            self::class => $this,
68            Type::class => $type,
69            ViewRenderer::class => $templateRenderer,
70            Serializer::class => $serializer,
71            Store::class => $store,
72            FieldHydrator::class => $this->hydrator,
73        ]);
74
75        $uid = $data['uid'] ?? $this->uid->generate();
76        $data['uid'] = $uid;
77        $owner = new FieldOwner($context, $uid);
78        $fieldNames = $this->hydrator->hydrate($node, $data['content'] ?? [], $owner);
79
80        if ($node instanceof HasInit) {
81            $node->init();
82        }
83
84        self::$nodeState[$node] = [
85            'data' => $data,
86            'fieldNames' => $fieldNames,
87        ];
88
89        return $node;
90    }
91
92    /**
93     * Wrap a node for template-friendly access.
94     */
95    public function proxy(
96        object $node,
97        Request $request,
98        ?Context $context = null,
99        ?Cms $cms = null,
100    ): Node {
101        return new Node(
102            $node,
103            self::fieldNamesFor($node),
104            $this->hydrator,
105            $this->types,
106            $request,
107            $context,
108            $cms,
109            $this,
110        );
111    }
112
113    /**
114     * Create a blueprint (empty) node for admin panel schema generation.
115     */
116    public function blueprint(string $class, Context $context, Cms $cms): object
117    {
118        return $this->create($class, $context, $cms, []);
119    }
120
121    /**
122     * Get the raw DB data associated with a node instance.
123     */
124    public static function dataFor(object $node): array
125    {
126        return self::getNodeState($node)['data'] ?? [];
127    }
128
129    /**
130     * Get the field names for a node instance.
131     */
132    public static function fieldNamesFor(object $node): array
133    {
134        return self::getNodeState($node)['fieldNames'] ?? [];
135    }
136
137    private static function getNodeState(object $node): array
138    {
139        self::$nodeState ??= new WeakMap();
140        $node = Node::unwrap($node);
141
142        return self::$nodeState[$node] ?? [];
143    }
144
145    /**
146     * Get a metadata value from the raw DB data for a node instance.
147     */
148    public static function meta(object $node, string $key): mixed
149    {
150        return self::dataFor($node)[$key] ?? null;
151    }
152
153    public function hydrator(): FieldHydrator
154    {
155        return $this->hydrator;
156    }
157
158    public function uid(): Uid
159    {
160        return $this->uid;
161    }
162}