Skip to content

Commit f29deee

Browse files
committed
实现对象映射功能
0 parents  commit f29deee

16 files changed

+914
-0
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2023 余兴
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# php-object-mapping
2+
3+
# 说明
4+
PHP对象映射,可以将array/object映射为类对象。可做参数的传递、Request参数映射、Entity、Bean等。并可根据定义的映射对象生成API文档。
5+
6+
# 示例
7+
```php
8+
class TestMapping extends BaseMapping
9+
{
10+
/** @var int 总数 */
11+
public int $total;
12+
13+
public ?TestItem $item = null;
14+
15+
/** @var \Xbyter\PhpObjectMappingTests\Data\TestItem[] 列表对象 */
16+
public array $list = [];
17+
18+
/** @var string 受保护的值,不会被设置 */
19+
protected string $protected_key = 'protected value';
20+
}
21+
22+
class TestItem extends BaseMapping
23+
{
24+
/** @var string|null 标题 */
25+
public ?string $title = null;
26+
27+
/**
28+
* @var string|null 会返回装饰过的值
29+
* @return TitleDecorator
30+
*/
31+
public ?string $decorate_title = null;
32+
}
33+
34+
//装饰类
35+
class TitleDecorator implements DecoratorInterface
36+
{
37+
38+
public function decorate($value)
39+
{
40+
return sprintf("this is a decorated value: %s", $value);
41+
}
42+
}
43+
44+
//映射对象,多维数组可用 TestMapping::fromList([[...], [...]]);
45+
$testMapping = TestMapping::fromItem([
46+
'total' => 10,
47+
'item' => [
48+
'title' => 'a'
49+
],
50+
'list' => [
51+
[
52+
'title' => 'b',
53+
'decorate_title' => 'c',
54+
]
55+
],
56+
'protected_key' => '受保护的字段不会被赋值',
57+
]);
58+
59+
//转为数组
60+
$array = $testMapping->toArray();
61+
62+
var_dump($testMapping->total); //10
63+
var_dump($testMapping->item->title); //a
64+
var_dump($testMapping->list[0]->title); //b
65+
var_dump($testMapping->list[0]->decorate_title); //this is a decorated value: c
66+
var_dump($testMapping->protected_key); //Error : Cannot access protected property $protected_key
67+
```

composer.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "xbyter/php-object-mapping",
3+
"description": "PHP对象映射,可以将array/object映射为类对象。可做参数的传递、Request参数映射、Entity、Bean等",
4+
"type": "library",
5+
"license": "MIT",
6+
"keywords": [
7+
"request",
8+
"bean",
9+
"entity",
10+
"mapping",
11+
"object-mapping",
12+
"object-fill"
13+
],
14+
"homepage": "https://github.com/xbyter/php-object-mapping",
15+
"require": {
16+
"ext-json": "*",
17+
"php": "^7.4 || ^8.0"
18+
},
19+
"autoload": {
20+
"psr-4": {
21+
"Xbyter\\PhpObjectMapping\\": "src/"
22+
}
23+
},
24+
"autoload-dev": {
25+
"psr-4": {
26+
"Xbyter\\PhpObjectMappingTests\\": "tests/"
27+
}
28+
},
29+
"require-dev": {
30+
"vlucas/phpdotenv": "^3.3",
31+
"phpunit/phpunit": "^6.5.6 || ^7.0 || ^9.6"
32+
}
33+
}

src/BaseMapping.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Xbyter\PhpObjectMapping;
4+
5+
use Xbyter\PhpObjectMapping\Traits\PropertyFillerTrait;
6+
use Xbyter\PhpObjectMapping\Traits\PropertyToArrayTrait;
7+
8+
abstract class BaseMapping
9+
{
10+
use PropertyToArrayTrait, PropertyFillerTrait;
11+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Xbyter\PhpObjectMapping\Exceptions;
4+
5+
class PropertySetError extends \TypeError
6+
{
7+
/** @var string[] */
8+
protected array $properties;
9+
10+
11+
public function __construct(\Throwable $e, array $properties)
12+
{
13+
$this->properties = $properties;
14+
parent::__construct($e->getMessage(), 0, $e);
15+
}
16+
17+
/**
18+
* @return array
19+
*/
20+
public function getProperties(): array
21+
{
22+
return $this->properties;
23+
}
24+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Xbyter\PhpObjectMapping\Interfaces;
4+
5+
interface DecoratorInterface
6+
{
7+
public function decorate($value);
8+
}

src/PropertyDocParser.php

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
<?php
2+
3+
namespace Xbyter\PhpObjectMapping;
4+
5+
class PropertyDocParser
6+
{
7+
/**
8+
* 文档属性类型
9+
*
10+
* @var array
11+
*/
12+
protected static array $_properties = [];
13+
14+
/**
15+
* 获取对象文档属性类型
16+
*
17+
* @param string $className
18+
* @return array
19+
* @throws \ReflectionException
20+
* @throws \Exception
21+
*/
22+
public static function getProperties(string $className): array
23+
{
24+
$_properties = &self::$_properties[$className];
25+
if (isset($_properties)) {
26+
return self::$_properties[$className];
27+
}
28+
29+
$_properties = [];
30+
$reflection = new \ReflectionClass($className);
31+
$properties = $reflection->getProperties();
32+
foreach ($properties as $property) {
33+
if (!$property->isPublic()) {
34+
continue;
35+
}
36+
37+
$name = $property->getName();
38+
$_properties[$name] = [
39+
'name' => $name,
40+
'type' => 'string',
41+
'class_name' => null,
42+
'nullable' => true,
43+
'decorator' => null,//装饰器, 用于转换为新的值
44+
];
45+
46+
//根据参数Doc文档设置指定对象值
47+
$doc = $property->getDocComment();
48+
//解析$type装饰类,用于转换某种具体规则的值。比如转换时区(业务逻辑层以固定时区做逻辑处理,展示层可以加$type转为相应时区值)
49+
if ($doc) {
50+
$_properties[$name]['decorator'] = self::getDecoratorByDoc($doc);
51+
}
52+
53+
//如果属性是强类型, 则以强类型为主将类型注入到propertyTypes里
54+
if ($property->hasType() && $property->getType() instanceof \ReflectionNamedType) {
55+
$typeName = $property->getType()->getName();
56+
$_properties[$name] = [
57+
'type' => $typeName,
58+
'class_name' => self::isScalarType($typeName) || self::isArrayType($typeName) ? null : $typeName,
59+
'nullable' => $property->getType()->allowsNull(),
60+
] + $_properties[$name];
61+
62+
//如果不是数组的话则已经确定类型了, 不必往下走
63+
if (!self::isArrayType($typeName)) {
64+
continue;
65+
}
66+
}
67+
68+
69+
//非强类型解析
70+
if ($doc) {
71+
//解析类似@var \DemoNamespace\DemoProperty[]的文档对象
72+
preg_match("/@var \??(.*?)(\[\])?[\|\s\*]/", $doc, $matches);
73+
74+
//解析属性类型到propertyTypes
75+
//如果未设置强类型, 或者强类型为数组并且注释类型为对象数组时则重新设置类型
76+
$matchType = $matches[1] ?? '';
77+
if (!$matchType) {
78+
continue;
79+
}
80+
81+
//如果注释没有命名空间, 则查看是否再当前命名空间下存在该类
82+
if (strpos($matchType, "\\") !== 0) {
83+
$matchType = $reflection->getNamespaceName() . "\\" . $matchType;
84+
}
85+
86+
$isArray = isset($matches[2]);
87+
if ($isArray && $_properties[$name]['type'] === 'array' && class_exists($matchType)) {
88+
$_properties[$name]['class_name'] = $matchType;
89+
}
90+
}
91+
}
92+
93+
return $_properties;
94+
}
95+
96+
97+
/**
98+
* 获取装饰类
99+
*
100+
* @param string $doc
101+
* @return string|null
102+
* @throws \Exception
103+
*/
104+
protected static function getDecoratorByDoc(string $doc): ?string
105+
{
106+
//解析类似@return DecoratorInterface 的文档对象
107+
preg_match("/@return[ ]+([\w\\\]+)/", $doc, $matches);
108+
$decorator = $matches[1] ?? '';
109+
if (!$decorator || !class_exists($decorator)) {
110+
return null;
111+
}
112+
113+
return $decorator;
114+
}
115+
116+
public static function isStringType(string $type): bool
117+
{
118+
return $type === 'string';
119+
}
120+
121+
public static function isIntType(string $type): bool
122+
{
123+
return $type === 'int';
124+
}
125+
126+
public static function isFloatType(string $type): bool
127+
{
128+
return $type === 'float';
129+
}
130+
131+
public static function isBoolType(string $type): bool
132+
{
133+
return $type === 'bool';
134+
}
135+
136+
public static function isArrayType(string $type): bool
137+
{
138+
return $type === 'array';
139+
}
140+
141+
/**
142+
* 是否是标量(简单类型)
143+
*
144+
* @param string $type
145+
* @return bool
146+
*/
147+
public static function isScalarType(string $type): bool
148+
{
149+
return self::isStringType($type) || self::isIntType($type) || self::isFloatType($type) || self::isBoolType($type);
150+
}
151+
152+
153+
public static function isNumberType(string $type): bool
154+
{
155+
return self::isIntType($type) || self::isFloatType($type);
156+
}
157+
158+
public static function isClassType(array $property): bool
159+
{
160+
return $property['class_name'] && !self::isArrayType($property['type']);
161+
}
162+
163+
public static function isClassArrayType(array $property): bool
164+
{
165+
return $property['class_name'] && self::isArrayType($property['type']);
166+
}
167+
}

0 commit comments

Comments
 (0)