dep: DTO
This commit is contained in:
+84
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\DataTransferObject\Reflection;
|
||||
|
||||
use ReflectionClass;
|
||||
use ReflectionProperty;
|
||||
use Spatie\DataTransferObject\Attributes\Strict;
|
||||
use Spatie\DataTransferObject\DataTransferObject;
|
||||
use Spatie\DataTransferObject\Exceptions\ValidationException;
|
||||
|
||||
class DataTransferObjectClass
|
||||
{
|
||||
private ReflectionClass $reflectionClass;
|
||||
|
||||
private DataTransferObject $dataTransferObject;
|
||||
|
||||
private bool $isStrict;
|
||||
|
||||
public function __construct(DataTransferObject $dataTransferObject)
|
||||
{
|
||||
$this->reflectionClass = new ReflectionClass($dataTransferObject);
|
||||
$this->dataTransferObject = $dataTransferObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Spatie\DataTransferObject\Reflection\DataTransferObjectProperty[]
|
||||
*/
|
||||
public function getProperties(): array
|
||||
{
|
||||
$publicProperties = array_filter(
|
||||
$this->reflectionClass->getProperties(ReflectionProperty::IS_PUBLIC),
|
||||
fn (ReflectionProperty $property) => ! $property->isStatic()
|
||||
);
|
||||
|
||||
return array_map(
|
||||
fn (ReflectionProperty $property) => new DataTransferObjectProperty(
|
||||
$this->dataTransferObject,
|
||||
$property
|
||||
),
|
||||
$publicProperties
|
||||
);
|
||||
}
|
||||
|
||||
public function validate(): void
|
||||
{
|
||||
$validationErrors = [];
|
||||
|
||||
foreach ($this->getProperties() as $property) {
|
||||
$validators = $property->getValidators();
|
||||
|
||||
foreach ($validators as $validator) {
|
||||
$result = $validator->validate($property->getValue());
|
||||
|
||||
if ($result->isValid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$validationErrors[$property->name][] = $result;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($validationErrors)) {
|
||||
throw new ValidationException($this->dataTransferObject, $validationErrors);
|
||||
}
|
||||
}
|
||||
|
||||
public function isStrict(): bool
|
||||
{
|
||||
if (! isset($this->isStrict)) {
|
||||
$attribute = null;
|
||||
|
||||
$reflectionClass = $this->reflectionClass;
|
||||
while ($attribute === null && $reflectionClass !== false) {
|
||||
$attribute = $reflectionClass->getAttributes(Strict::class)[0] ?? null;
|
||||
|
||||
$reflectionClass = $reflectionClass->getParentClass();
|
||||
}
|
||||
|
||||
$this->isStrict = $attribute !== null;
|
||||
}
|
||||
|
||||
return $this->isStrict;
|
||||
}
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\DataTransferObject\Reflection;
|
||||
|
||||
use JetBrains\PhpStorm\Immutable;
|
||||
use ReflectionAttribute;
|
||||
use ReflectionClass;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionProperty;
|
||||
use ReflectionType;
|
||||
use ReflectionUnionType;
|
||||
use Spatie\DataTransferObject\Attributes\CastWith;
|
||||
use Spatie\DataTransferObject\Attributes\DefaultCast;
|
||||
use Spatie\DataTransferObject\Attributes\MapFrom;
|
||||
use Spatie\DataTransferObject\Caster;
|
||||
use Spatie\DataTransferObject\DataTransferObject;
|
||||
use Spatie\DataTransferObject\Validator;
|
||||
|
||||
class DataTransferObjectProperty
|
||||
{
|
||||
#[Immutable]
|
||||
public string $name;
|
||||
|
||||
private DataTransferObject $dataTransferObject;
|
||||
|
||||
private ReflectionProperty $reflectionProperty;
|
||||
|
||||
private ?Caster $caster;
|
||||
|
||||
public function __construct(
|
||||
DataTransferObject $dataTransferObject,
|
||||
ReflectionProperty $reflectionProperty
|
||||
) {
|
||||
$this->dataTransferObject = $dataTransferObject;
|
||||
$this->reflectionProperty = $reflectionProperty;
|
||||
|
||||
$this->name = $this->resolveMappedProperty();
|
||||
|
||||
$this->caster = $this->resolveCaster();
|
||||
}
|
||||
|
||||
public function setValue(mixed $value): void
|
||||
{
|
||||
if ($this->caster && $value !== null) {
|
||||
$value = $this->caster->cast($value);
|
||||
}
|
||||
|
||||
$this->reflectionProperty->setValue($this->dataTransferObject, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Spatie\DataTransferObject\Validator[]
|
||||
*/
|
||||
public function getValidators(): array
|
||||
{
|
||||
$attributes = $this->reflectionProperty->getAttributes(
|
||||
Validator::class,
|
||||
ReflectionAttribute::IS_INSTANCEOF
|
||||
);
|
||||
|
||||
return array_map(
|
||||
fn (ReflectionAttribute $attribute) => $attribute->newInstance(),
|
||||
$attributes
|
||||
);
|
||||
}
|
||||
|
||||
public function getValue(): mixed
|
||||
{
|
||||
return $this->reflectionProperty->getValue($this->dataTransferObject);
|
||||
}
|
||||
|
||||
private function resolveCaster(): ?Caster
|
||||
{
|
||||
$attributes = $this->reflectionProperty->getAttributes(CastWith::class);
|
||||
|
||||
if (! count($attributes)) {
|
||||
$attributes = $this->resolveCasterFromType();
|
||||
}
|
||||
|
||||
if (! count($attributes)) {
|
||||
return $this->resolveCasterFromDefaults();
|
||||
}
|
||||
|
||||
/** @var \Spatie\DataTransferObject\Attributes\CastWith $attribute */
|
||||
$attribute = $attributes[0]->newInstance();
|
||||
|
||||
return new $attribute->casterClass(
|
||||
array_map(fn ($type) => $this->resolveTypeName($type), $this->extractTypes()),
|
||||
...$attribute->args
|
||||
);
|
||||
}
|
||||
|
||||
private function resolveCasterFromType(): array
|
||||
{
|
||||
foreach ($this->extractTypes() as $type) {
|
||||
$name = $this->resolveTypeName($type);
|
||||
|
||||
if (! class_exists($name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$reflectionClass = new ReflectionClass($name);
|
||||
|
||||
do {
|
||||
$attributes = $reflectionClass->getAttributes(CastWith::class);
|
||||
|
||||
$reflectionClass = $reflectionClass->getParentClass();
|
||||
} while (! count($attributes) && $reflectionClass);
|
||||
|
||||
if (count($attributes) > 0) {
|
||||
return $attributes;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private function resolveCasterFromDefaults(): ?Caster
|
||||
{
|
||||
$defaultCastAttributes = [];
|
||||
|
||||
$class = $this->reflectionProperty->getDeclaringClass();
|
||||
|
||||
do {
|
||||
array_push($defaultCastAttributes, ...$class->getAttributes(DefaultCast::class));
|
||||
|
||||
$class = $class->getParentClass();
|
||||
} while ($class !== false);
|
||||
|
||||
if (! count($defaultCastAttributes)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($defaultCastAttributes as $defaultCastAttribute) {
|
||||
/** @var \Spatie\DataTransferObject\Attributes\DefaultCast $defaultCast */
|
||||
$defaultCast = $defaultCastAttribute->newInstance();
|
||||
|
||||
if ($defaultCast->accepts($this->reflectionProperty)) {
|
||||
return $defaultCast->resolveCaster();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function resolveMappedProperty(): string | int
|
||||
{
|
||||
$attributes = $this->reflectionProperty->getAttributes(MapFrom::class);
|
||||
|
||||
if (! count($attributes)) {
|
||||
return $this->reflectionProperty->name;
|
||||
}
|
||||
|
||||
return $attributes[0]->newInstance()->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ReflectionNamedType[]
|
||||
*/
|
||||
private function extractTypes(): array
|
||||
{
|
||||
$type = $this->reflectionProperty->getType();
|
||||
|
||||
if (! $type) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return match ($type::class) {
|
||||
ReflectionNamedType::class => [$type],
|
||||
ReflectionUnionType::class => $type->getTypes(),
|
||||
};
|
||||
}
|
||||
|
||||
private function resolveTypeName(ReflectionType $type): string
|
||||
{
|
||||
return match ($type->getName()) {
|
||||
'self' => $this->dataTransferObject::class,
|
||||
'parent' => get_parent_class($this->dataTransferObject),
|
||||
default => $type->getName(),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user