mirror of
https://devops.lemonos.cn/lawson/FendxPHP.git
synced 2026-06-15 23:12:49 +08:00
253 lines
5.9 KiB
PHP
253 lines
5.9 KiB
PHP
|
|
<?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) {}
|
||
|
|
}
|