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

nette / schema / 22292073757

23 Feb 2026 03:39AM UTC coverage: 96.579%. Remained the same
22292073757

push

github

dg
added CLAUDE.md

480 of 497 relevant lines covered (96.58%)

0.97 hits per line

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

98.86
/src/Schema/Helpers.php
1
<?php declare(strict_types=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;
9

10
use Nette;
11
use Nette\Utils\Reflection;
12
use function count, explode, get_debug_type, implode, in_array, is_array, is_float, is_int, is_object, is_scalar, is_string, method_exists, preg_match, preg_quote, preg_replace, preg_replace_callback, settype, str_replace, strlen, trim, var_export;
13

14

15
/**
16
 * @internal
17
 */
18
final class Helpers
19
{
20
        use Nette\StaticClass;
21

22
        public const PreventMerging = '_prevent_merging';
23

24

25
        /**
26
         * Merges dataset. Left has higher priority than right one.
27
         */
28
        public static function merge(mixed $value, mixed $base): mixed
1✔
29
        {
30
                if (is_array($value) && isset($value[self::PreventMerging])) {
1✔
31
                        unset($value[self::PreventMerging]);
1✔
32
                        return $value;
1✔
33
                }
34

35
                if (is_array($value) && is_array($base)) {
1✔
36
                        $index = 0;
1✔
37
                        foreach ($value as $key => $val) {
1✔
38
                                if ($key === $index) {
1✔
39
                                        $base[] = $val;
1✔
40
                                        $index++;
1✔
41
                                } else {
42
                                        $base[$key] = static::merge($val, $base[$key] ?? null);
1✔
43
                                }
44
                        }
45

46
                        return $base;
1✔
47

48
                } elseif ($value === null && is_array($base)) {
1✔
49
                        return $base;
1✔
50

51
                } else {
52
                        return $value;
1✔
53
                }
54
        }
55

56

57
        public static function getPropertyType(\ReflectionProperty|\ReflectionParameter $prop): ?string
1✔
58
        {
59
                if ($type = Nette\Utils\Type::fromReflection($prop)) {
1✔
60
                        return (string) $type;
1✔
61
                } elseif (
62
                        ($prop instanceof \ReflectionProperty)
1✔
63
                        && ($type = preg_replace('#\s.*#', '', (string) self::parseAnnotation($prop, 'var')))
1✔
64
                ) {
65
                        $class = Reflection::getPropertyDeclaringClass($prop);
1✔
66
                        return preg_replace_callback('#[\w\\\]+#', fn($m) => Reflection::expandClassName($m[0], $class), $type);
1✔
67
                }
68

69
                return null;
1✔
70
        }
71

72

73
        /**
74
         * Returns an annotation value.
75
         * @param  \ReflectionClass<object>|\ReflectionProperty  $ref
76
         */
77
        public static function parseAnnotation(\ReflectionClass|\ReflectionProperty $ref, string $name): ?string
1✔
78
        {
79
                if (!Reflection::areCommentsAvailable()) {
1✔
80
                        throw new Nette\InvalidStateException('You have to enable phpDoc comments in opcode cache.');
×
81
                }
82

83
                $re = '#[\s*]@' . preg_quote($name, '#') . '(?=\s|$)(?:[ \t]+([^@\s]\S*))?#';
1✔
84
                if ($ref->getDocComment() && preg_match($re, trim($ref->getDocComment(), '/*'), $m)) {
1✔
85
                        return $m[1] ?? '';
1✔
86
                }
87

88
                return null;
1✔
89
        }
90

91

92
        public static function formatValue(mixed $value): string
1✔
93
        {
94
                if ($value instanceof DynamicParameter) {
1✔
95
                        return 'dynamic';
1✔
96
                } elseif (is_object($value)) {
1✔
97
                        return 'object ' . $value::class;
1✔
98
                } elseif (is_string($value)) {
1✔
99
                        return "'" . Nette\Utils\Strings::truncate($value, 15, '...') . "'";
1✔
100
                } elseif (is_scalar($value)) {
1✔
101
                        return var_export($value, return: true);
1✔
102
                } else {
103
                        return get_debug_type($value);
1✔
104
                }
105
        }
106

107

108
        public static function validateType(mixed $value, string $expected, Context $context): void
1✔
109
        {
110
                if (!Nette\Utils\Validators::is($value, $expected)) {
1✔
111
                        $expected = str_replace(DynamicParameter::class . '|', '', $expected);
1✔
112
                        $expected = str_replace(['|', ':'], [' or ', ' in range '], $expected);
1✔
113
                        $context->addError(
1✔
114
                                'The %label% %path% expects to be %expected%, %value% given.',
1✔
115
                                Message::TypeMismatch,
1✔
116
                                ['value' => $value, 'expected' => $expected],
1✔
117
                        );
118
                }
119
        }
1✔
120

121

122
        /** @param  array{?float, ?float}  $range */
123
        public static function validateRange(mixed $value, array $range, Context $context, string $types = ''): void
1✔
124
        {
125
                if (is_array($value) || is_string($value)) {
1✔
126
                        [$length, $label] = is_array($value)
1✔
127
                                ? [count($value), 'items']
1✔
128
                                : (in_array('unicode', explode('|', $types), strict: true)
1✔
129
                                        ? [Nette\Utils\Strings::length($value), 'characters']
1✔
130
                                        : [strlen($value), 'bytes']);
1✔
131

132
                        if (!self::isInRange($length, $range)) {
1✔
133
                                $context->addError(
1✔
134
                                        "The length of %label% %path% expects to be in range %expected%, %length% $label given.",
1✔
135
                                        Message::LengthOutOfRange,
1✔
136
                                        ['value' => $value, 'length' => $length, 'expected' => implode('..', $range)],
1✔
137
                                );
138
                        }
139
                } elseif ((is_int($value) || is_float($value)) && !self::isInRange($value, $range)) {
1✔
140
                        $context->addError(
1✔
141
                                'The %label% %path% expects to be in range %expected%, %value% given.',
1✔
142
                                Message::ValueOutOfRange,
1✔
143
                                ['value' => $value, 'expected' => implode('..', $range)],
1✔
144
                        );
145
                }
146
        }
1✔
147

148

149
        /** @param  array{?float, ?float}  $range */
150
        public static function isInRange(mixed $value, array $range): bool
1✔
151
        {
152
                return ($range[0] === null || $value >= $range[0])
1✔
153
                        && ($range[1] === null || $value <= $range[1]);
1✔
154
        }
155

156

157
        public static function validatePattern(string $value, string $pattern, Context $context): void
1✔
158
        {
159
                if (!preg_match("\x01^(?:$pattern)$\x01Du", $value)) {
1✔
160
                        $context->addError(
1✔
161
                                "The %label% %path% expects to match pattern '%pattern%', %value% given.",
1✔
162
                                Message::PatternMismatch,
1✔
163
                                ['value' => $value, 'pattern' => $pattern],
1✔
164
                        );
165
                }
166
        }
1✔
167

168

169
        /** @return \Closure(mixed): mixed */
170
        public static function getCastStrategy(string $type): \Closure
1✔
171
        {
172
                if (Nette\Utils\Validators::isBuiltinType($type)) {
1✔
173
                        return static function ($value) use ($type) {
1✔
174
                                settype($value, $type);
1✔
175
                                return $value;
1✔
176
                        };
1✔
177
                } elseif (method_exists($type, '__construct')) {
1✔
178
                        return static fn($value) => is_array($value) || $value instanceof \stdClass
1✔
179
                                ? new $type(...(array) $value)
1✔
180
                                : new $type($value);
1✔
181
                } else {
182
                        return static fn($value) => Nette\Utils\Arrays::toObject((array) $value, new $type);
1✔
183
                }
184
        }
185
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc