Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
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
declare(strict_types=1);
9
 
10
namespace Nette\Schema\Elements;
11
 
12
use Nette;
13
use Nette\Schema\Context;
14
use Nette\Schema\Helpers;
15
use Nette\Schema\Schema;
16
 
17
 
18
final class Structure implements Schema
19
{
20
	use Base;
21
	use Nette\SmartObject;
22
 
23
	/** @var Schema[] */
24
	private $items;
25
 
26
	/** @var Schema|null  for array|list */
27
	private $otherItems;
28
 
29
	/** @var array{?int, ?int} */
30
	private $range = [null, null];
31
 
32
	/** @var bool */
33
	private $skipDefaults = false;
34
 
35
 
36
	/**
37
	 * @param  Schema[]  $items
38
	 */
39
	public function __construct(array $items)
40
	{
41
		(function (Schema ...$items) {})(...array_values($items));
42
		$this->items = $items;
43
		$this->castTo = 'object';
44
		$this->required = true;
45
	}
46
 
47
 
48
	public function default($value): self
49
	{
50
		throw new Nette\InvalidStateException('Structure cannot have default value.');
51
	}
52
 
53
 
54
	public function min(?int $min): self
55
	{
56
		$this->range[0] = $min;
57
		return $this;
58
	}
59
 
60
 
61
	public function max(?int $max): self
62
	{
63
		$this->range[1] = $max;
64
		return $this;
65
	}
66
 
67
 
68
	/**
69
	 * @param  string|Schema  $type
70
	 */
71
	public function otherItems($type = 'mixed'): self
72
	{
73
		$this->otherItems = $type instanceof Schema ? $type : new Type($type);
74
		return $this;
75
	}
76
 
77
 
78
	public function skipDefaults(bool $state = true): self
79
	{
80
		$this->skipDefaults = $state;
81
		return $this;
82
	}
83
 
84
 
85
	/********************* processing ****************d*g**/
86
 
87
 
88
	public function normalize($value, Context $context)
89
	{
90
		if ($prevent = (is_array($value) && isset($value[Helpers::PREVENT_MERGING]))) {
91
			unset($value[Helpers::PREVENT_MERGING]);
92
		}
93
 
94
		$value = $this->doNormalize($value, $context);
95
		if (is_object($value)) {
96
			$value = (array) $value;
97
		}
98
 
99
		if (is_array($value)) {
100
			foreach ($value as $key => $val) {
101
				$itemSchema = $this->items[$key] ?? $this->otherItems;
102
				if ($itemSchema) {
103
					$context->path[] = $key;
104
					$value[$key] = $itemSchema->normalize($val, $context);
105
					array_pop($context->path);
106
				}
107
			}
108
 
109
			if ($prevent) {
110
				$value[Helpers::PREVENT_MERGING] = true;
111
			}
112
		}
113
 
114
		return $value;
115
	}
116
 
117
 
118
	public function merge($value, $base)
119
	{
120
		if (is_array($value) && isset($value[Helpers::PREVENT_MERGING])) {
121
			unset($value[Helpers::PREVENT_MERGING]);
122
			$base = null;
123
		}
124
 
125
		if (is_array($value) && is_array($base)) {
126
			$index = 0;
127
			foreach ($value as $key => $val) {
128
				if ($key === $index) {
129
					$base[] = $val;
130
					$index++;
131
				} elseif (array_key_exists($key, $base)) {
132
					$itemSchema = $this->items[$key] ?? $this->otherItems;
133
					$base[$key] = $itemSchema
134
						? $itemSchema->merge($val, $base[$key])
135
						: Helpers::merge($val, $base[$key]);
136
				} else {
137
					$base[$key] = $val;
138
				}
139
			}
140
 
141
			return $base;
142
		}
143
 
144
		return Helpers::merge($value, $base);
145
	}
146
 
147
 
148
	public function complete($value, Context $context)
149
	{
150
		if ($value === null) {
151
			$value = []; // is unable to distinguish null from array in NEON
152
		}
153
 
154
		$this->doDeprecation($context);
155
 
156
		if (!$this->doValidate($value, 'array', $context)
157
			|| !$this->doValidateRange($value, $this->range, $context)
158
		) {
159
			return;
160
		}
161
 
162
		$errCount = count($context->errors);
163
		$items = $this->items;
164
		if ($extraKeys = array_keys(array_diff_key($value, $items))) {
165
			if ($this->otherItems) {
166
				$items += array_fill_keys($extraKeys, $this->otherItems);
167
			} else {
168
				$keys = array_map('strval', array_keys($items));
169
				foreach ($extraKeys as $key) {
170
					$hint = Nette\Utils\ObjectHelpers::getSuggestion($keys, (string) $key);
171
					$context->addError(
172
						'Unexpected item %path%' . ($hint ? ", did you mean '%hint%'?" : '.'),
173
						Nette\Schema\Message::UNEXPECTED_ITEM,
174
						['hint' => $hint]
175
					)->path[] = $key;
176
				}
177
			}
178
		}
179
 
180
		foreach ($items as $itemKey => $itemVal) {
181
			$context->path[] = $itemKey;
182
			if (array_key_exists($itemKey, $value)) {
183
				$value[$itemKey] = $itemVal->complete($value[$itemKey], $context);
184
			} else {
185
				$default = $itemVal->completeDefault($context); // checks required item
186
				if (!$context->skipDefaults && !$this->skipDefaults) {
187
					$value[$itemKey] = $default;
188
				}
189
			}
190
 
191
			array_pop($context->path);
192
		}
193
 
194
		if (count($context->errors) > $errCount) {
195
			return;
196
		}
197
 
198
		return $this->doFinalize($value, $context);
199
	}
200
 
201
 
202
	public function completeDefault(Context $context)
203
	{
204
		return $this->required
205
			? $this->complete([], $context)
206
			: null;
207
	}
208
}