Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
521dba4
wip
cosmastech Feb 7, 2026
c40cb3f
more wip
cosmastech Feb 7, 2026
a14c893
more wip
cosmastech Feb 7, 2026
1a28011
simplified perhaps
cosmastech Feb 7, 2026
61175b7
ok I see you claude
cosmastech Feb 7, 2026
d27665c
rename
cosmastech Feb 7, 2026
b4dfda2
rename builder
cosmastech Feb 7, 2026
1d2d5c7
generics
cosmastech Feb 7, 2026
2a2a1c4
reflection
cosmastech Feb 7, 2026
0efd9f4
merge in defaults
cosmastech Feb 7, 2026
9a84e1a
merge in default values
cosmastech Feb 7, 2026
c81233c
claude cooking
cosmastech Feb 7, 2026
244920b
remove stuff
cosmastech Feb 7, 2026
1ce226e
merge validation rules from types
cosmastech Feb 7, 2026
a7d3aa3
phpstan ignore
cosmastech Feb 7, 2026
8d50001
ai slop
cosmastech Feb 7, 2026
6e33663
nested
cosmastech Feb 7, 2026
6699ba4
use container
cosmastech Feb 7, 2026
a587cd7
handle attributes and messages
cosmastech Feb 7, 2026
cb098ac
nested tests
cosmastech Feb 7, 2026
c4e17fe
claude limit reached
cosmastech Feb 7, 2026
b566b50
ok
cosmastech Feb 7, 2026
67c67d5
WithoutInferringRules
cosmastech Feb 8, 2026
38afbc6
fix tests
cosmastech Feb 8, 2026
886ace4
date casting
cosmastech Feb 8, 2026
dc9887c
object casting support
cosmastech Feb 8, 2026
c2da5e1
unions
cosmastech Feb 8, 2026
cda78e9
stan
cosmastech Feb 8, 2026
a524ac8
fix for invalid types
cosmastech Feb 8, 2026
2980ab9
rename and move around
cosmastech Feb 8, 2026
21c59d3
hydrate from request
cosmastech Feb 8, 2026
1347852
clean up
cosmastech Feb 8, 2026
409b163
unused
cosmastech Feb 8, 2026
6b5249f
clean up
cosmastech Feb 8, 2026
84f6c0d
clean up
cosmastech Feb 8, 2026
b0f5756
rename
cosmastech Feb 8, 2026
9adb4f3
move around
cosmastech Feb 8, 2026
eab0953
comment
cosmastech Feb 8, 2026
8ecbcd7
clean up
cosmastech Feb 8, 2026
0b83eda
clean up
cosmastech Feb 8, 2026
eb1aacf
collection casting
cosmastech Feb 8, 2026
9c12677
cleaning
cosmastech Feb 8, 2026
28d434d
test
cosmastech Feb 8, 2026
6172c3c
clean up
cosmastech Feb 8, 2026
c14da9a
cleaning up
cosmastech Feb 8, 2026
25ed330
clarity and simplification
cosmastech Feb 8, 2026
21ab5ff
more clarity and simplification
cosmastech Feb 8, 2026
f0fd3c8
stdClass (I wrote this by hand!)
cosmastech Feb 8, 2026
9b074fe
simplification
cosmastech Feb 8, 2026
bee8e94
Apply suggestion from @shaedrich
cosmastech Feb 8, 2026
14b495e
Update src/Illuminate/Foundation/Http/TypedFormRequestFactory.php
cosmastech Feb 8, 2026
d71db65
handle more built-ins
cosmastech Feb 8, 2026
6b5b48f
types
cosmastech Feb 8, 2026
d72f956
handle iterables
cosmastech Feb 9, 2026
f52bc9b
remove unused attribute classification
cosmastech Feb 9, 2026
bf92af9
add withValidator
cosmastech Feb 9, 2026
a0f23a3
passedValidation
cosmastech Feb 9, 2026
5c29a6d
passedValidation
cosmastech Feb 9, 2026
778d7cd
slide around
cosmastech Feb 9, 2026
d765cae
StopOnFirstFailure
cosmastech Feb 9, 2026
cfa972d
superfluous test
cosmastech Feb 9, 2026
edc5578
easier testing with a `from()` method
cosmastech Feb 10, 2026
3d18bf1
fix and tests
cosmastech Feb 10, 2026
57e4aca
uploaded file
cosmastech Feb 12, 2026
1c15fdb
uploaded file
cosmastech Feb 12, 2026
742866c
use Taylor's attributes
cosmastech Feb 14, 2026
abb4855
container DI
cosmastech Feb 14, 2026
6fbabb0
arrayable
cosmastech Feb 14, 2026
b2d7e54
makewith for clarity
cosmastech Feb 14, 2026
3a9c9a5
makeWith is actually more hops, nevermind
cosmastech Feb 14, 2026
f80732e
clarity in comments
cosmastech Feb 14, 2026
10b0307
wip
cosmastech Feb 14, 2026
05b8ca7
move to some traits
cosmastech Feb 14, 2026
0c16c0e
move around a little more
cosmastech Feb 14, 2026
be29eda
method that belongs to castsValidatedData
cosmastech Feb 14, 2026
64ff307
move to casts
cosmastech Feb 14, 2026
26e002f
move to traits
cosmastech Feb 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/Illuminate/Foundation/Http/Attributes/HydrateFromRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Illuminate\Foundation\Http\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PARAMETER)]
class HydrateFromRequest
{
//
}
16 changes: 16 additions & 0 deletions src/Illuminate/Foundation/Http/Attributes/MapFrom.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Illuminate\Foundation\Http\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_PARAMETER)]
class MapFrom
{
/**
* @param string $name The request field to map from.
*/
public function __construct(public $name)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Illuminate\Foundation\Http\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_PARAMETER | Attribute::TARGET_CLASS)]
class WithoutInferringRules
{
}
221 changes: 221 additions & 0 deletions src/Illuminate/Foundation/Http/Concerns/CastsValidatedData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
<?php

namespace Illuminate\Foundation\Http\Concerns;

use BackedEnum;
use Carbon\CarbonImmutable;
use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use Illuminate\Foundation\Http\TypedFormRequest;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Date;
use Illuminate\Validation\ValidationException;
use ReflectionNamedType;
use ReflectionParameter;
use ReflectionUnionType;
use stdClass;

use function Illuminate\Support\enum_value;

trait CastsValidatedData
{
/**
* Cast validated data into constructor arguments for the request class.
*
* @param array<string, mixed> $validated
* @return array<string, mixed>
*
* @throws \ReflectionException
*/
protected function castValidatedData(array $validated): array
{
if (($constructor = $this->reflectRequest()->getConstructor()) === null) {
return $validated;
}

$arguments = [];

foreach ($constructor->getParameters() as $param) {
$fieldName = $this->fieldNameFor($param);
$name = $param->getName();

if (! Arr::has($validated, $fieldName)) {
continue;
}

$value = Arr::get($validated, $fieldName);

if ($value === null) {
$arguments[$name] = null;

continue;
}

$type = $param->getType();

if ($type instanceof ReflectionUnionType) {
$nestedRequestClass = $this->nestedHydrationClassFromUnion($type, $param);

if ($nestedRequestClass !== null && is_array($value)) {
$arguments[$name] = $this->instantiateFromValidatedArray(
$nestedRequestClass,
$this->ensureArrayValue($fieldName, $value)
);
} else {
$arguments[$name] = $value;
}

continue;
}

if (! $type instanceof ReflectionNamedType) {
$arguments[$name] = $value;

continue;
}

$typeName = $type->getName();
if ($type->isBuiltin()) {
if ($typeName === 'object') {
$arguments[$name] = $this->castBuiltinObjectValue($value);
} else {
$arguments[$name] = $value;
}

continue;
}

if ($this->isDateObjectType($typeName)) {
$arguments[$name] = $this->castDateValue($typeName, $value);
} elseif (is_a($typeName, stdClass::class, true)) {
$arguments[$name] = $this->castBuiltinObjectValue($value);
} elseif ($this->shouldHydrateParameter($param, $typeName) || is_subclass_of($typeName, TypedFormRequest::class)) {
$arguments[$name] = $this->instantiateFromValidatedArray(
$typeName,
$this->ensureArrayValue($fieldName, $value)
);
} elseif (is_subclass_of($typeName, BackedEnum::class)) {
$arguments[$name] = $typeName::from($value);
} elseif (is_a($typeName, Collection::class, true)) {
if ($value instanceof $typeName) {
$arguments[$name] = $value;
} else {
$arguments[$name] = new $typeName($this->ensureArrayValue($fieldName, $value));
}
} else {
$arguments[$name] = $value;
}
}

return $arguments;
}

/**
* Cast an "object" builtin value into a stdClass instance when appropriate.
*/
protected function castBuiltinObjectValue(mixed $value): mixed
{
return is_array($value) ? (object) $value : $value;
}

/**
* Cast the given value to the requested date object type.
*
* @param class-string $typeName The date object class name.
* @param mixed $value The validated value.
*/
protected function castDateValue(string $typeName, mixed $value): ?DateTimeInterface
{
if ($value === null || ($value instanceof DateTimeInterface && $value instanceof $typeName)) {
return $value;
}

$parsed = Date::parse($value);

return match (true) {
$typeName === DateTimeInterface::class => $parsed,
$typeName === DateTime::class => $parsed->toDateTime(),
$typeName === DateTimeImmutable::class => $parsed->toDateTimeImmutable(),
is_a($typeName, CarbonImmutable::class, true) => CarbonImmutable::instance($parsed),
default => $parsed,
};
}

/**
* Determine if the given class name is a date object type.
*
* @param class-string $name
*/
protected function isDateObjectType(string $name): bool
{
return is_a($name, DateTimeInterface::class, true);
}


/**
* Get the first union branch that should be hydrated from an array payload.
*
* @return class-string|null
*/
protected function nestedHydrationClassFromUnion(ReflectionUnionType $type, ReflectionParameter $param): ?string
{
foreach ($type->getTypes() as $named) {
if ($named->getName() === 'null' || $named->isBuiltin()) {
continue;
}

$class = $named->getName();

if (in_array($class, $this->ancestors)) {
continue;
}

if (is_subclass_of($class, TypedFormRequest::class) || $this->shouldHydrateParameter($param, $class)) {
return $class;
}
}

return null;
}

/**
* Ensure the given value is an array payload or throw a validation exception.
*
* @template TValue of array<array-key, mixed>
*
* @param string $fieldName
* @param TValue|mixed $value
* @return array<array-key, mixed>
*
* @phpstan-return ($value is array<array-key, mixed> ? TValue : never)
*
* @throws \Illuminate\Validation\ValidationException
*/
protected function ensureArrayValue(string $fieldName, mixed $value): array
{
if (is_array($value)) {
return $value;
}

throw ValidationException::withMessages([
$fieldName => ["The {$fieldName} field must be an array."],
]);
}

/**
* Convert a reflected default value to a native value.
*
* @template TValue
*
* @param TValue $value
* @return mixed
*
* @phpstan-return ($value is empty ? null : ($value is \BackedEnum ? value-of<TValue> : ($value is \UnitEnum ? string : TValue)))
*/
protected function mapToNativeFromDefaultValue(mixed $value): mixed
{
return enum_value($value);
}
}
Loading
Loading