Skip to content

Commit c7f882a

Browse files
committed
[FEATURE] StrictArgumentProcessor
1 parent 9689700 commit c7f882a

File tree

2 files changed

+655
-0
lines changed

2 files changed

+655
-0
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file belongs to the package "TYPO3 Fluid".
7+
* See LICENSE.txt that was shipped with this package.
8+
*/
9+
10+
namespace TYPO3Fluid\Fluid\Core\ViewHelper;
11+
12+
use ArrayAccess;
13+
use Stringable;
14+
use Traversable;
15+
16+
final class StrictArgumentProcessor implements ArgumentProcessorInterface
17+
{
18+
public function process(mixed $value, ArgumentDefinition $definition): mixed
19+
{
20+
if (!$definition->isRequired() && $value === $definition->getDefaultValue()) {
21+
return $value;
22+
}
23+
return match ($definition->getType()) {
24+
'string' => is_scalar($value) ? (string)$value : $value,
25+
'int', 'integer' => is_scalar($value) ? (int)$value : $value,
26+
'float', 'double' => is_scalar($value) ? (float)$value : $value,
27+
// @todo decide to implement special bool behavior here?
28+
//'bool', 'boolean' =>
29+
default => $value,
30+
};
31+
}
32+
33+
public function isValid(mixed $value, ArgumentDefinition $definition): bool
34+
{
35+
// Allow everything for mixed type
36+
if ($definition->getType() === 'mixed') {
37+
return true;
38+
}
39+
40+
// Always allow default value if argument is not required
41+
if (!$definition->isRequired() && $value === $definition->getDefaultValue()) {
42+
return true;
43+
}
44+
45+
// Perform type validation
46+
return $this->isValidType($definition->getType(), $value);
47+
}
48+
49+
/**
50+
* Check whether the defined type matches the value type
51+
*/
52+
private function isValidType(string $type, mixed $value): bool
53+
{
54+
if ($type === 'object') {
55+
return is_object($value);
56+
}
57+
if ($type === 'string') {
58+
return is_string($value) || $value instanceof Stringable;
59+
}
60+
if ($type === 'int' || $type === 'integer') {
61+
return is_int($value);
62+
}
63+
if ($type === 'float' || $type === 'double') {
64+
return is_float($value);
65+
}
66+
if ($type === 'bool' || $type === 'boolean') {
67+
return is_bool($value);
68+
}
69+
if ($type === 'array' || str_ends_with($type, '[]')) {
70+
if (!is_array($value) && !$value instanceof ArrayAccess && !$value instanceof Traversable) {
71+
return false;
72+
}
73+
if (str_ends_with($type, '[]')) {
74+
$firstElement = $this->getFirstElementOfNonEmpty($value);
75+
if ($firstElement === null) {
76+
return true;
77+
}
78+
return $this->isValidType(substr($type, 0, -2), $firstElement);
79+
}
80+
return true;
81+
}
82+
if (class_exists($type) && $value instanceof $type) {
83+
return true;
84+
}
85+
if (is_object($value) && is_a($value, $type, true)) {
86+
return true;
87+
}
88+
return false;
89+
}
90+
91+
/**
92+
* Return the first element of the given array, ArrayAccess or Traversable
93+
* that is not empty
94+
*/
95+
private function getFirstElementOfNonEmpty(mixed $value): mixed
96+
{
97+
if (is_array($value)) {
98+
return reset($value);
99+
}
100+
if ($value instanceof Traversable) {
101+
foreach ($value as $element) {
102+
return $element;
103+
}
104+
}
105+
return null;
106+
}
107+
}

0 commit comments

Comments
 (0)