ci4
188 строк · 5.7 Кб
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\DataCaster;
15
16use CodeIgniter\DataCaster\Cast\ArrayCast;
17use CodeIgniter\DataCaster\Cast\BooleanCast;
18use CodeIgniter\DataCaster\Cast\CastInterface;
19use CodeIgniter\DataCaster\Cast\CSVCast;
20use CodeIgniter\DataCaster\Cast\DatetimeCast;
21use CodeIgniter\DataCaster\Cast\FloatCast;
22use CodeIgniter\DataCaster\Cast\IntBoolCast;
23use CodeIgniter\DataCaster\Cast\IntegerCast;
24use CodeIgniter\DataCaster\Cast\JsonCast;
25use CodeIgniter\DataCaster\Cast\TimestampCast;
26use CodeIgniter\DataCaster\Cast\URICast;
27use CodeIgniter\Entity\Cast\CastInterface as EntityCastInterface;
28use CodeIgniter\Entity\Exceptions\CastException;
29use InvalidArgumentException;
30
31final class DataCaster
32{
33/**
34* Array of field names and the type of value to cast.
35*
36* @var array<string, string> [field => type]
37*/
38private array $types = [];
39
40/**
41* Convert handlers
42*
43* @var array<string, class-string> [type => classname]
44*/
45private array $castHandlers = [
46'array' => ArrayCast::class,
47'bool' => BooleanCast::class,
48'boolean' => BooleanCast::class,
49'csv' => CSVCast::class,
50'datetime' => DatetimeCast::class,
51'double' => FloatCast::class,
52'float' => FloatCast::class,
53'int' => IntegerCast::class,
54'integer' => IntegerCast::class,
55'int-bool' => IntBoolCast::class,
56'json' => JsonCast::class,
57'timestamp' => TimestampCast::class,
58'uri' => URICast::class,
59];
60
61/**
62* @param array<string, class-string>|null $castHandlers Custom convert handlers
63* @param array<string, string>|null $types [field => type]
64* @param object|null $helper Helper object.
65* @param bool $strict Strict mode? Set to false for casts for Entity.
66*/
67public function __construct(
68?array $castHandlers = null,
69?array $types = null,
70private readonly ?object $helper = null,
71private readonly bool $strict = true
72) {
73$this->castHandlers = array_merge($this->castHandlers, $castHandlers);
74
75if ($types !== null) {
76$this->setTypes($types);
77}
78
79if ($this->strict) {
80foreach ($this->castHandlers as $handler) {
81if (
82! is_subclass_of($handler, CastInterface::class)
83&& ! is_subclass_of($handler, EntityCastInterface::class)
84) {
85throw new InvalidArgumentException(
86'Invalid class type. It must implement CastInterface. class: ' . $handler
87);
88}
89}
90}
91}
92
93/**
94* This method is only for Entity.
95*
96* @TODO if Entity::$casts is readonly, we don't need this method.
97*
98* @param array<string, string> $types [field => type]
99*
100* @return $this
101*
102* @internal
103*/
104public function setTypes(array $types): static
105{
106$this->types = $types;
107
108return $this;
109}
110
111/**
112* Provides the ability to cast an item as a specific data type.
113* Add ? at the beginning of the type (i.e. ?string) to get `null`
114* instead of casting $value when $value is null.
115*
116* @param mixed $value The value to convert
117* @param string $field The field name
118* @param string $method Allowed to "get" and "set"
119* @phpstan-param 'get'|'set' $method
120*/
121public function castAs(mixed $value, string $field, string $method = 'get'): mixed
122{
123// If the type is not defined, return as it is.
124if (! isset($this->types[$field])) {
125return $value;
126}
127
128$type = $this->types[$field];
129
130$isNullable = false;
131
132// Is nullable?
133if (str_starts_with($type, '?')) {
134$isNullable = true;
135
136if ($value === null) {
137return null;
138}
139
140$type = substr($type, 1);
141} elseif ($value === null) {
142if ($this->strict) {
143$message = 'Field "' . $field . '" is not nullable, but null was passed.';
144
145throw new InvalidArgumentException($message);
146}
147}
148
149// In order not to create a separate handler for the
150// json-array type, we transform the required one.
151$type = ($type === 'json-array') ? 'json[array]' : $type;
152
153$params = [];
154
155// Attempt to retrieve additional parameters if specified
156// type[param, param2,param3]
157if (preg_match('/\A(.+)\[(.+)\]\z/', $type, $matches)) {
158$type = $matches[1];
159$params = array_map(trim(...), explode(',', $matches[2]));
160}
161
162if ($isNullable && ! $this->strict) {
163$params[] = 'nullable';
164}
165
166$type = trim($type, '[]');
167
168$handlers = $this->castHandlers;
169
170if (! isset($handlers[$type])) {
171throw new InvalidArgumentException(
172'No such handler for "' . $field . '". Invalid type: ' . $type
173);
174}
175
176$handler = $handlers[$type];
177
178if (
179! $this->strict
180&& ! is_subclass_of($handler, CastInterface::class)
181&& ! is_subclass_of($handler, EntityCastInterface::class)
182) {
183throw CastException::forInvalidInterface($handler);
184}
185
186return $handler::$method($value, $params, $this->helper);
187}
188}
189