• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / schema / 29631259250

18 Jul 2026 04:51AM UTC coverage: 98.361% (+1.6%) from 96.735%
29631259250

push

github

dg
DOCS

540 of 549 relevant lines covered (98.36%)

0.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.12
/src/Schema/Elements/Type.php
1
<?php declare(strict_types=1);
1✔
2

3
/**
4
 * This file is part of the Nette Framework (https://nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
namespace Nette\Schema\Elements;
9

10
use Nette\Schema\Context;
11
use Nette\Schema\DynamicParameter;
12
use Nette\Schema\Helpers;
13
use Nette\Schema\MergeMode;
14
use Nette\Schema\Message;
15
use Nette\Schema\Schema;
16
use function array_key_exists, array_pop, implode, is_array, str_replace, strpos;
17

18

19
final class Type implements Schema
20
{
21
        use Base;
22

23
        private string $type;
24
        private ?Schema $itemsValue = null;
25
        private ?Schema $itemsKey = null;
26

27
        /** @var array{?float, ?float} */
28
        private array $range = [null, null];
29
        private ?string $pattern = null;
30
        private bool $merge = false;
31

32

33
        public function __construct(string $type)
1✔
34
        {
35
                $defaults = ['list' => [], 'array' => []];
1✔
36
                $this->type = $type;
1✔
37
                $this->default = strpos($type, '[]') ? [] : $defaults[$type] ?? null;
1✔
38
        }
1✔
39

40

41
        /**
42
         * Allows the value to be null in addition to the declared type.
43
         */
44
        public function nullable(): self
45
        {
46
                $this->type = 'null|' . $this->type;
1✔
47
                return $this;
1✔
48
        }
49

50

51
        #[\Deprecated('mergeDefaults is disabled by default')]
52
        public function mergeDefaults(bool $state = true): self
1✔
53
        {
54
                if ($state === true) {
1✔
55
                        trigger_error(__METHOD__ . '() is deprecated and will be removed in the next major version.', E_USER_DEPRECATED);
1✔
56
                }
57
                $this->merge = $state;
1✔
58
                return $this;
1✔
59
        }
60

61

62
        /**
63
         * Allows the value to be a DynamicParameter, which is recorded for deferred validation.
64
         */
65
        public function dynamic(): self
66
        {
67
                $this->type = DynamicParameter::class . '|' . $this->type;
1✔
68
                return $this;
1✔
69
        }
70

71

72
        public function min(?float $min): self
1✔
73
        {
74
                $this->range[0] = $min;
1✔
75
                return $this;
1✔
76
        }
77

78

79
        public function max(?float $max): self
1✔
80
        {
81
                $this->range[1] = $max;
1✔
82
                return $this;
1✔
83
        }
84

85

86
        /**
87
         * @internal  use arrayOf() or listOf()
88
         */
89
        public function items(string|Schema $valueType = 'mixed', string|Schema|null $keyType = null): self
1✔
90
        {
91
                $this->itemsValue = $valueType instanceof Schema
1✔
92
                        ? $valueType
1✔
93
                        : new self($valueType);
1✔
94
                $this->itemsKey = $keyType instanceof Schema || $keyType === null
1✔
95
                        ? $keyType
1✔
96
                        : new self($keyType);
×
97
                return $this;
1✔
98
        }
99

100

101
        /**
102
         * Sets a regex pattern the string value must match entirely (anchored to start and end).
103
         */
104
        public function pattern(?string $pattern): self
1✔
105
        {
106
                $this->pattern = $pattern;
1✔
107
                return $this;
1✔
108
        }
109

110

111
        /********************* processing ****************d*g**/
112

113

114
        public function normalize(mixed $value, Context $context): mixed
1✔
115
        {
116
                $value = $this->doNormalize($value, $context);
1✔
117
                if (is_array($value) && $this->itemsValue) {
1✔
118
                        $res = [];
1✔
119
                        foreach ($value as $key => $val) {
1✔
120
                                $context->path[] = $key;
1✔
121
                                $context->isKey = true;
1✔
122
                                $key = $this->itemsKey
1✔
123
                                        ? $this->itemsKey->normalize($key, $context)
1✔
124
                                        : $key;
1✔
125
                                $context->isKey = false;
1✔
126
                                $res[$key] = $this->itemsValue->normalize($val, $context);
1✔
127
                                array_pop($context->path);
1✔
128
                        }
129

130
                        $value = $res;
1✔
131
                }
132

133
                return $value;
1✔
134
        }
135

136

137
        public function merge(mixed $value, mixed $base, Context $context): mixed
1✔
138
        {
139
                if ($this->mergeWith) {
1✔
140
                        return ($this->mergeWith)($value, $base);
1✔
141
                }
142

143
                $mode = $this->mergeMode ?? MergeMode::AppendKeys;
1✔
144
                if ($mode === MergeMode::Replace) {
1✔
145
                        return $value;
1✔
146
                }
147

148
                if (is_array($value) && is_array($base)) {
1✔
149
                        $index = $mode === MergeMode::OverwriteKeys ? null : 0;
1✔
150
                        foreach ($value as $key => $val) {
1✔
151
                                if ($key === $index) {
1✔
152
                                        $base[] = $val;
1✔
153
                                        $index++;
1✔
154
                                } elseif (!array_key_exists($key, $base)) {
1✔
155
                                        $base[$key] = $val;
1✔
156
                                } elseif ($this->itemsValue) {
1✔
157
                                        $context->path[] = $key;
1✔
158
                                        $base[$key] = $this->itemsValue->merge($val, $base[$key], $context);
1✔
159
                                        array_pop($context->path);
1✔
160
                                } else {
161
                                        if (is_array($val) && is_array($base[$key]) && $this->mergeMode === null) {
1✔
162
                                                $context->addError(
1✔
163
                                                        'Cannot merge %path%: the schema does not describe array items, use arrayOf() or mergeMode().',
1✔
164
                                                        Message::CannotMerge,
1✔
165
                                                )->path[] = $key;
1✔
166
                                        }
167
                                        $base[$key] = $val;
1✔
168
                                }
169
                        }
170

171
                        return $base;
1✔
172
                }
173

174
                return $value === null && is_array($base) ? $base : $value;
1✔
175
        }
176

177

178
        public function complete(mixed $value, Context $context): mixed
1✔
179
        {
180
                if ($value === null && is_array($this->default)) {
1✔
181
                        $value = []; // is unable to distinguish null from array in NEON
1✔
182
                }
183

184
                $this->doDeprecation($context);
1✔
185

186
                $isOk = $context->createChecker();
1✔
187
                Helpers::validateType($value, $this->type, $context);
1✔
188
                $isOk() && Helpers::validateRange($value, $this->range, $context, $this->type);
1✔
189
                $isOk() && $value !== null && $this->pattern !== null && Helpers::validatePattern($value, $this->pattern, $context);
1✔
190
                $isOk() && is_array($value) && $this->validateItems($value, $context);
1✔
191
                $isOk() && $this->merge && $value = self::deepMerge($value, $this->default);
1✔
192
                $isOk() && $value = $this->doTransform($value, $context);
1✔
193
                if (!$isOk()) {
1✔
194
                        return null;
1✔
195
                }
196

197
                if ($value instanceof DynamicParameter) {
1✔
198
                        $expected = $this->type . ($this->range === [null, null] ? '' : ':' . implode('..', $this->range));
1✔
199
                        $context->dynamics[] = [$value, str_replace(DynamicParameter::class . '|', '', $expected), $context->path];
1✔
200
                }
201
                return $value;
1✔
202
        }
203

204

205
        /**
206
         * Blind deep merge used only by deprecated mergeDefaults().
207
         */
208
        private static function deepMerge(mixed $value, mixed $base): mixed
1✔
209
        {
210
                if (is_array($value) && is_array($base)) {
1✔
211
                        $index = 0;
1✔
212
                        foreach ($value as $key => $val) {
1✔
213
                                if ($key === $index) {
1✔
214
                                        $base[] = $val;
1✔
215
                                        $index++;
1✔
216
                                } else {
217
                                        $base[$key] = self::deepMerge($val, $base[$key] ?? null);
1✔
218
                                }
219
                        }
220

221
                        return $base;
1✔
222
                }
223

224
                return $value === null && is_array($base) ? $base : $value;
1✔
225
        }
226

227

228
        /** @param  array<mixed>  $value */
229
        private function validateItems(array &$value, Context $context): void
1✔
230
        {
231
                if (!$this->itemsValue) {
1✔
232
                        return;
1✔
233
                }
234

235
                $res = [];
1✔
236
                foreach ($value as $key => $val) {
1✔
237
                        $context->path[] = $key;
1✔
238
                        $context->isKey = true;
1✔
239
                        $key = $this->itemsKey ? $this->itemsKey->complete($key, $context) : $key;
1✔
240
                        $context->isKey = false;
1✔
241
                        $res[$key ?? ''] = $this->itemsValue->complete($val, $context);
1✔
242
                        array_pop($context->path);
1✔
243
                }
244
                $value = $res;
1✔
245
        }
1✔
246
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc