Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
97.30% |
72 / 74 |
|
33.33% |
1 / 3 |
CRAP | |
0.00% |
0 / 1 |
Mapper | |
97.30% |
72 / 74 |
|
33.33% |
1 / 3 |
32 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
mapping | |
98.61% |
71 / 72 |
|
0.00% |
0 / 1 |
30 | |||
export | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace PeServer\Core\Serialization; |
6 | |
7 | use PeServer\Core\Collection\Arr; |
8 | use PeServer\Core\ReflectionUtility; |
9 | use PeServer\Core\Serialization\Converter\TypeConverterBase; |
10 | use PeServer\Core\Serialization\IMapper; |
11 | use PeServer\Core\Text; |
12 | use PeServer\Core\Throws\KeyNotFoundException; |
13 | use PeServer\Core\Throws\MapperKeyNotFoundException; |
14 | use PeServer\Core\Throws\MapperTypeException; |
15 | use PeServer\Core\Throws\NotImplementedException; |
16 | use PeServer\Core\TypeUtility; |
17 | use ReflectionClass; |
18 | use ReflectionNamedType; |
19 | use TypeError; |
20 | |
21 | /** |
22 | * マッピング通常処理。 |
23 | */ |
24 | class Mapper implements IMapper |
25 | { |
26 | #region variable |
27 | |
28 | /** |
29 | * 人畜無害なマッピング設定。 |
30 | */ |
31 | private readonly Mapping $defaultMapping; |
32 | |
33 | #endregion |
34 | |
35 | public function __construct() |
36 | { |
37 | $this->defaultMapping = new Mapping(Text::EMPTY, Mapping::FLAG_NONE); |
38 | } |
39 | |
40 | #region IMapper |
41 | |
42 | /** |
43 | * @SuppressWarnings(PHPMD.CyclomaticComplexity) |
44 | */ |
45 | public function mapping(array $source, object $destination): void |
46 | { |
47 | $destReflection = new ReflectionClass($destination); |
48 | $properties = ReflectionUtility::getAllProperties($destReflection); |
49 | |
50 | foreach ($properties as $property) { |
51 | $property->setAccessible(true); |
52 | $attrs = $property->getAttributes(Mapping::class); |
53 | |
54 | $mapping = $this->defaultMapping; |
55 | |
56 | if (!empty($attrs)) { |
57 | $attr = $attrs[0]; |
58 | |
59 | /** @var Mapping */ |
60 | $mapping = $attr->newInstance(); |
61 | |
62 | // 無視する |
63 | if ($mapping->flags === Mapping::FLAG_IGNORE) { |
64 | continue; |
65 | } |
66 | } |
67 | |
68 | $keyName = $property->name; |
69 | if (!Text::isNullOrWhiteSpace($mapping->name)) { |
70 | $keyName = $mapping->name; |
71 | } |
72 | |
73 | // 指定したキーが見つからない |
74 | if (!isset($source[$keyName])) { |
75 | if (($mapping->flags & Mapping::FLAG_EXCEPTION_NOT_FOUND_KEY) === Mapping::FLAG_EXCEPTION_NOT_FOUND_KEY) { |
76 | throw new MapperKeyNotFoundException('$keyName: ' . $keyName); |
77 | } |
78 | // 例外指定がないので現在キーは無視する |
79 | continue; |
80 | } |
81 | |
82 | $sourceValue = $source[$keyName]; |
83 | unset($source[$keyName]); //対象キーは不要なので破棄 |
84 | $sourceType = TypeUtility::getType($sourceValue); |
85 | $propertyTypes = ReflectionUtility::getTypes($property->getType()); |
86 | foreach ($propertyTypes as $propertyType) { |
87 | $nestTypeName = $propertyType->getName(); |
88 | $isArrayObject = $nestTypeName === TypeUtility::TYPE_ARRAY && class_exists($mapping->arrayValueClassName); |
89 | |
90 | if ($sourceType === $nestTypeName && !$isArrayObject) { |
91 | $property->setValue($destination, $sourceValue); |
92 | continue 2; // loop: $properties |
93 | } |
94 | |
95 | if ($sourceType === TypeUtility::TYPE_ARRAY) { |
96 | $nestDestination = null; |
97 | if ($property->isInitialized($destination)) { |
98 | $nestDestination = $property->getValue($destination); |
99 | } |
100 | |
101 | if ($nestDestination === null) { |
102 | if (($mapping->flags & Mapping::FLAG_OBJECT_INSTANCE_ONLY) === Mapping::FLAG_OBJECT_INSTANCE_ONLY) { |
103 | continue; |
104 | } |
105 | |
106 | $nestDestination = null; |
107 | |
108 | if ($isArrayObject) { |
109 | $nestDestination = []; |
110 | } elseif (class_exists($nestTypeName)) { |
111 | $nestDestination = new $nestTypeName(); |
112 | } |
113 | |
114 | $property->setValue($destination, $nestDestination); |
115 | } |
116 | |
117 | if ($isArrayObject) { |
118 | $isListArray = ($mapping->flags & Mapping::FLAG_LIST_ARRAY_VALUES) === Mapping::FLAG_LIST_ARRAY_VALUES; |
119 | foreach ($sourceValue as $key => $value) { |
120 | $objClassName = $mapping->arrayValueClassName; |
121 | $obj = new $objClassName(); |
122 | $this->mapping($value, $obj); |
123 | if ($isListArray) { |
124 | $nestDestination[] = $obj; |
125 | } else { |
126 | $nestDestination[$key] = $obj; |
127 | } |
128 | } |
129 | $property->setValue($destination, $nestDestination); |
130 | } else { |
131 | $this->mapping($sourceValue, $nestDestination); |
132 | } |
133 | |
134 | continue 2; // loop: $properties |
135 | } |
136 | |
137 | if (!Text::isNullOrWhiteSpace($mapping->converter)) { |
138 | /** @phpstan-var TypeConverterBase<mixed> */ |
139 | $converter = new $mapping->converter(); |
140 | $convertedValue = $converter->read($keyName, $propertyType, $sourceValue); |
141 | $property->setValue($destination, $convertedValue); |
142 | continue 2; // loop: $properties |
143 | } |
144 | } |
145 | |
146 | // 型一致せずにここまで来たので可能な限り元の型に合わせる |
147 | if (($mapping->flags & Mapping::FLAG_EXCEPTION_TYPE_MISMATCH) === Mapping::FLAG_EXCEPTION_TYPE_MISMATCH) { |
148 | // ただし型変換失敗例外指定の場合は全部諦める |
149 | throw new MapperTypeException($keyName . '(' . $sourceType . '): ' . Text::join('|', Arr::map($propertyTypes, fn (ReflectionNamedType $p) => $p->getName()))); |
150 | } |
151 | |
152 | foreach ($propertyTypes as $propertyType) { |
153 | // 常識的な型だけに限定(独断と偏見) |
154 | switch ($propertyType->getName()) { |
155 | case TypeUtility::TYPE_INTEGER: |
156 | case TypeUtility::TYPE_STRING: |
157 | case TypeUtility::TYPE_BOOLEAN: |
158 | case TypeUtility::TYPE_DOUBLE: |
159 | case TypeUtility::TYPE_NULL: |
160 | if (settype($sourceValue, $propertyType->getName())) { |
161 | $property->setValue($destination, $sourceValue); |
162 | continue 2; // loop: $properties |
163 | } |
164 | break; |
165 | |
166 | default: |
167 | break; |
168 | } |
169 | } |
170 | } |
171 | } |
172 | |
173 | public function export(object $source): array |
174 | { |
175 | throw new NotImplementedException('いるか?'); |
176 | } |
177 | |
178 | #endregion |
179 | } |