ci4
202 строки · 5.5 Кб
1<?php
2
3declare(strict_types=1);
4
5/**
6* This file is part of CodeIgniter 4 framework.
7*
8* (c) CodeIgniter Foundation <admin@codeigniter.com>
9*
10* For the full copyright and license information, please view
11* the LICENSE file that was distributed with this source code.
12*/
13
14namespace CodeIgniter\DataConverter;
15
16use Closure;
17use CodeIgniter\DataCaster\DataCaster;
18use CodeIgniter\Entity\Entity;
19
20/**
21* PHP data <==> DataSource data converter
22*
23* @see \CodeIgniter\DataConverter\DataConverterTest
24*
25* @template TEntity of object
26*/
27final class DataConverter
28{
29/**
30* The data caster.
31*/
32private readonly DataCaster $dataCaster;
33
34/**
35* @param array<string, class-string> $castHandlers Custom convert handlers
36*
37* @internal
38*/
39public function __construct(
40/**
41* Type definitions.
42*
43* @var array<string, string> [column => type]
44*/
45private readonly array $types,
46array $castHandlers = [],
47/**
48* Helper object.
49*/
50private readonly ?object $helper = null,
51/**
52* Static reconstruct method name or closure to reconstruct an object.
53* Used by reconstruct().
54*
55* @phpstan-var (Closure(array<string, mixed>): TEntity)|string|null
56*/
57private readonly Closure|string|null $reconstructor = 'reconstruct',
58/**
59* Extract method name or closure to extract data from an object.
60* Used by extract().
61*
62* @phpstan-var (Closure(TEntity, bool, bool): array<string, mixed>)|string|null
63*/
64private readonly Closure|string|null $extractor = null,
65) {
66$this->dataCaster = new DataCaster($castHandlers, $types, $this->helper);
67}
68
69/**
70* Converts data from DataSource to PHP array with specified type values.
71*
72* @param array<string, mixed> $data DataSource data
73*
74* @internal
75*/
76public function fromDataSource(array $data): array
77{
78foreach (array_keys($this->types) as $field) {
79if (array_key_exists($field, $data)) {
80$data[$field] = $this->dataCaster->castAs($data[$field], $field, 'get');
81}
82}
83
84return $data;
85}
86
87/**
88* Converts PHP array to data for DataSource field types.
89*
90* @param array<string, mixed> $phpData PHP data
91*
92* @internal
93*/
94public function toDataSource(array $phpData): array
95{
96foreach (array_keys($this->types) as $field) {
97if (array_key_exists($field, $phpData)) {
98$phpData[$field] = $this->dataCaster->castAs($phpData[$field], $field, 'set');
99}
100}
101
102return $phpData;
103}
104
105/**
106* Takes database data array and creates a specified type object.
107*
108* @param class-string $classname
109* @phpstan-param class-string<TEntity> $classname
110* @param array<string, mixed> $row Raw data from database
111*
112* @phpstan-return TEntity
113*
114* @internal
115*/
116public function reconstruct(string $classname, array $row): object
117{
118$phpData = $this->fromDataSource($row);
119
120// Use static reconstruct method.
121if (is_string($this->reconstructor) && method_exists($classname, $this->reconstructor)) {
122$method = $this->reconstructor;
123
124return $classname::$method($phpData);
125}
126
127// Use closure to reconstruct.
128if ($this->reconstructor instanceof Closure) {
129$closure = $this->reconstructor;
130
131return $closure($phpData);
132}
133
134$classObj = new $classname();
135
136if ($classObj instanceof Entity) {
137$classObj->injectRawData($phpData);
138$classObj->syncOriginal();
139
140return $classObj;
141}
142
143$classSet = Closure::bind(function ($key, $value): void {
144$this->{$key} = $value;
145}, $classObj, $classname);
146
147foreach ($phpData as $key => $value) {
148$classSet($key, $value);
149}
150
151return $classObj;
152}
153
154/**
155* Takes an object and extract properties as an array.
156*
157* @param bool $onlyChanged Only for CodeIgniter's Entity. If true, only returns
158* values that have changed since object creation.
159* @param bool $recursive Only for CodeIgniter's Entity. If true, inner
160* entities will be cast as array as well.
161*
162* @return array<string, mixed>
163*
164* @internal
165*/
166public function extract(object $object, bool $onlyChanged = false, bool $recursive = false): array
167{
168// Use extractor method.
169if (is_string($this->extractor) && method_exists($object, $this->extractor)) {
170$method = $this->extractor;
171$row = $object->{$method}($onlyChanged, $recursive);
172
173return $this->toDataSource($row);
174}
175
176// Use closure to extract.
177if ($this->extractor instanceof Closure) {
178$closure = $this->extractor;
179$row = $closure($object, $onlyChanged, $recursive);
180
181return $this->toDataSource($row);
182}
183
184if ($object instanceof Entity) {
185$row = $object->toRawArray($onlyChanged, $recursive);
186
187return $this->toDataSource($row);
188}
189
190$array = (array) $object;
191
192$row = [];
193
194foreach ($array as $key => $value) {
195$key = preg_replace('/\000.*\000/', '', $key);
196
197$row[$key] = $value;
198}
199
200return $this->toDataSource($row);
201}
202}
203