Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.04% covered (success)
96.04%
97 / 101
70.00% covered (warning)
70.00%
7 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
Serializer
96.04% covered (success)
96.04%
97 / 101
70.00% covered (warning)
70.00%
7 / 10
19
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 content
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 data
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
1
 blueprint
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
4
 fields
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 read
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 resolveTitle
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
2.01
 order
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 resolveType
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 resolveDeletable
50.00% covered (danger)
50.00%
2 / 4
0.00% covered (danger)
0.00%
0 / 1
2.50
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Node;
6
7use Cosray\Field\FieldHydrator;
8use Cosray\Locales;
9use Cosray\Uid;
10use ReflectionMethod;
11
12class Serializer
13{
14    public function __construct(
15        private readonly FieldHydrator $hydrator,
16        private readonly Types $types,
17        private readonly Uid $uid,
18    ) {}
19
20    public function content(object $node, array $rawData, array $fieldNames): array
21    {
22        $content = [];
23
24        foreach ($fieldNames as $fieldName) {
25            $field = $this->hydrator->getField($node, $fieldName);
26            $structure = $field->structure();
27            $content[$fieldName] = array_merge($structure, $rawData['content'][$fieldName] ?? []);
28            $content[$fieldName]['type'] = $structure['type'];
29        }
30
31        return $content;
32    }
33
34    public function data(object $node, array $rawData, array $fieldNames): array
35    {
36        $class = $node::class;
37
38        return [
39            'uid' => $rawData['uid'],
40            'handle' => $rawData['handle'] ?? null,
41            'published' => $rawData['published'],
42            'hidden' => $rawData['hidden'],
43            'locked' => $rawData['locked'],
44            'created' => $rawData['created'],
45            'changed' => $rawData['changed'],
46            'deleted' => $rawData['deleted'],
47            'paths' => $rawData['paths'],
48            'parent' => $rawData['parent'] ?? null,
49            'type' => $this->resolveType($class, $rawData['type_handle'] ?? null),
50            'editor' => [
51                'uid' => $rawData['editor_uid'],
52                'email' => $rawData['editor_email'],
53                'username' => $rawData['editor_username'],
54                'data' => $rawData['editor_data'],
55            ],
56            'creator' => [
57                'uid' => $rawData['creator_uid'],
58                'email' => $rawData['creator_email'],
59                'username' => $rawData['creator_username'],
60                'data' => $rawData['creator_data'],
61            ],
62            'content' => $this->content($node, $rawData, $fieldNames),
63            'deletable' => $this->resolveDeletable($node),
64        ];
65    }
66
67    public function blueprint(
68        object $node,
69        array $fieldNames,
70        Locales $locales,
71        array $values = [],
72    ): array {
73        $content = [];
74        $paths = [];
75
76        foreach ($fieldNames as $fieldName) {
77            $field = $this->hydrator->getField($node, $fieldName);
78            $content[$fieldName] = $field->structure($values[$fieldName] ?? null);
79        }
80
81        $class = $node::class;
82        $schema = $this->types->schemaOf($class);
83
84        foreach ($locales as $locale) {
85            $paths[$locale->id] = '';
86        }
87
88        $result = [
89            'title' => _('Neues Dokument:') . ' ' . $schema->label,
90            'fields' => $this->fields($node, $fieldNames),
91            'uid' => $this->uid->generate(),
92            'handle' => null,
93            'published' => false,
94            'hidden' => false,
95            'locked' => false,
96            'deletable' => $this->resolveDeletable($node),
97            'content' => $content,
98            'type' => $this->resolveType($class),
99            'paths' => $paths,
100            'generatedPaths' => [],
101        ];
102
103        if ($schema->routable) {
104            $result['route'] = $schema->route;
105        }
106
107        return $result;
108    }
109
110    public function fields(object $node, array $fieldNames): array
111    {
112        $fields = [];
113        $orderedFields = $this->order($node, $fieldNames);
114        $missingFields = array_diff($fieldNames, $orderedFields);
115        $allFields = array_merge($orderedFields, $missingFields);
116
117        foreach ($allFields as $fieldName) {
118            $fields[] = $this->hydrator->getField($node, $fieldName)->properties();
119        }
120
121        return $fields;
122    }
123
124    public function read(object $node, array $rawData, array $fieldNames): array
125    {
126        $data = $this->data($node, $rawData, $fieldNames);
127
128        return array_merge([
129            'title' => $this->resolveTitle($node),
130            'uid' => $rawData['uid'],
131            'fields' => $this->fields($node, $fieldNames),
132        ], $data);
133    }
134
135    public function resolveTitle(object $node): string
136    {
137        $proxy = $node instanceof Node
138            ? $node
139            : new Node(
140                Node::unwrap($node),
141                Factory::fieldNamesFor($node),
142                $this->hydrator,
143                $this->types,
144            );
145
146        return $proxy->title();
147    }
148
149    /**
150     * @return string[]
151     */
152    private function order(object $node, array $fieldNames): array
153    {
154        $metaOrder = $this->types->get($node::class, 'fieldOrder');
155
156        if ($metaOrder !== null) {
157            return $metaOrder;
158        }
159
160        if (method_exists($node, 'order')) {
161            return $node->order();
162        }
163
164        return $fieldNames;
165    }
166
167    /**
168     * @param class-string $class
169     * @return array<string, mixed>
170     */
171    private function resolveType(string $class, ?string $handle = null): array
172    {
173        $schema = $this->types->schemaOf($class);
174
175        return array_merge([
176            'handle' => $handle ?? $schema->handle,
177            'routable' => $schema->routable,
178            'renderable' => $schema->renderable,
179            'class' => $class,
180        ], $schema->properties());
181    }
182
183    private function resolveDeletable(object $node): bool
184    {
185        if (method_exists($node, 'deletable')) {
186            $method = new ReflectionMethod($node, 'deletable');
187
188            return $method->invoke($node);
189        }
190
191        return (bool) $this->types->get($node::class, 'deletable', true);
192    }
193}