Files
FendxPHP/app/Dto/BaseDto.php

253 lines
5.9 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
namespace App\Dto;
/**
* DTO基类
* 所有数据传输对象都应该继承此类
*/
abstract class BaseDto
{
/**
* 数组转DTO
*/
public static function fromArray(array $data): static
{
$dto = new static();
foreach ($data as $key => $value) {
$method = 'set' . str_replace('_', '', ucwords($key, '_'));
if (method_exists($dto, $method)) {
$dto->$method($value);
} elseif (property_exists($dto, $key)) {
$dto->$key = $value;
}
}
return $dto;
}
/**
* DTO转数组
*/
public function toArray(): array
{
$data = [];
$reflection = new \ReflectionClass($this);
foreach ($reflection->getProperties() as $property) {
$propertyName = $property->getName();
$method = 'get' . str_replace('_', '', ucwords($propertyName, '_'));
if (method_exists($this, $method)) {
$data[$propertyName] = $this->$method();
} else {
$data[$propertyName] = $this->$propertyName ?? null;
}
}
return $data;
}
/**
* DTO转JSON
*/
public function toJson(): string
{
return json_encode($this->toArray(), JSON_UNESCAPED_UNICODE);
}
/**
* JSON转DTO
*/
public static function fromJson(string $json): static
{
$data = json_decode($json, true);
if (!is_array($data)) {
throw new \InvalidArgumentException('Invalid JSON data');
}
return static::fromArray($data);
}
/**
* 验证DTO数据
*/
public function validate(): array
{
$errors = [];
$reflection = new \ReflectionClass($this);
foreach ($reflection->getProperties() as $property) {
$propertyName = $property->getName();
$value = $this->$propertyName ?? null;
// 检查必填字段
$attributes = $property->getAttributes('Required');
if (!empty($attributes) && ($value === null || $value === '')) {
$errors[$propertyName] = "Field {$propertyName} is required";
continue;
}
// 检查数据类型
$type = $property->getType();
if ($type && $value !== null) {
$typeName = $type->getName();
if (!$this->validateType($value, $typeName)) {
$errors[$propertyName] = "Field {$propertyName} must be of type {$typeName}";
}
}
}
return $errors;
}
/**
* 验证数据类型
*/
private function validateType(mixed $value, string $type): bool
{
return match ($type) {
'int', 'integer' => is_int($value),
'float', 'double' => is_float($value),
'string' => is_string($value),
'bool', 'boolean' => is_bool($value),
'array' => is_array($value),
'object' => is_object($value),
default => true,
};
}
/**
* 获取所有属性
*/
public function getProperties(): array
{
$reflection = new \ReflectionClass($this);
$properties = [];
foreach ($reflection->getProperties() as $property) {
$properties[$property->getName()] = $this->{$property->getName()} ?? null;
}
return $properties;
}
/**
* 设置属性
*/
public function setProperty(string $name, mixed $value): void
{
$method = 'set' . str_replace('_', '', ucwords($name, '_'));
if (method_exists($this, $method)) {
$this->$method($value);
} elseif (property_exists($this, $name)) {
$this->$name = $value;
}
}
/**
* 获取属性
*/
public function getProperty(string $name): mixed
{
$method = 'get' . str_replace('_', '', ucwords($name, '_'));
if (method_exists($this, $method)) {
return $this->$method();
}
return $this->$name ?? null;
}
/**
* 检查属性是否存在
*/
public function hasProperty(string $name): bool
{
return property_exists($this, $name) || method_exists($this, 'get' . str_replace('_', '', ucwords($name, '_')));
}
/**
* 克隆DTO
*/
public function clone(): static
{
return clone $this;
}
/**
* 合并另一个DTO
*/
public function merge(BaseDto $other): static
{
$newDto = $this->clone();
$otherData = $other->toArray();
foreach ($otherData as $key => $value) {
if ($value !== null) {
$newDto->setProperty($key, $value);
}
}
return $newDto;
}
/**
* 魔术方法:转换为字符串
*/
public function __toString(): string
{
return $this->toJson();
}
/**
* 魔术方法:调试输出
*/
public function __debugInfo(): array
{
return $this->toArray();
}
}
/**
* 必填字段注解
*/
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Required
{
public function __construct(public string $message = '') {}
}
/**
* 字段长度注解
*/
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Length
{
public function __construct(public int $min = 0, public int $max = 255) {}
}
/**
* 字段范围注解
*/
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Range
{
public function __construct(public mixed $min = null, public mixed $max = null) {}
}
/**
* 正则表达式注解
*/
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Pattern
{
public function __construct(public string $regex) {}
}