Skip to content

Commit fab0e3e

Browse files
authored
Call object mapper directly from function (#20)
1 parent 6a5c989 commit fab0e3e

File tree

7 files changed

+49
-195
lines changed

7 files changed

+49
-195
lines changed

.gitattributes

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
.github/ export-ignore
2-
doc/ export-ignore
32
tests/ export-ignore

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "jerodev/data-mapper",
3-
"description": "Maps JSON data to a typed PHP object",
3+
"description": "Maps raw data to a typed PHP object",
44
"type": "library",
55
"license": "MIT",
66
"authors": [

doc/class-mapper-function.md

Lines changed: 0 additions & 82 deletions
This file was deleted.

license.md

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

readme.md

Lines changed: 5 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
11
# Data Mapper
22
![run-tests](https://github.com/jerodev/data-mapper/workflows/run-tests/badge.svg)
33

4-
This package will map any raw data into a strong typed PHP object.
5-
6-
- [Installation](#installation)
7-
- [Basic mapping](#basic-mapping)
8-
- [Typing properties](#typing-properties)
9-
- [Custom mapping](#custom-mapping)
10-
- [Post mapping](#post-mapping)
11-
- [Configuration](#configuration)
12-
- [Under the hood](#under-the-hood)
4+
This package will map any raw data into a predefined strong-typed PHP object.
135

146
## Installation
157
The mapper has no external dependencies apart from PHP8.1 or higher. It can be installed using composer:
@@ -50,94 +42,8 @@ $entity = $mapper->map(User::class, [
5042

5143
This is a simple example, but the mapper can also map nested objects, arrays of objects, keyed arrays, and even multi-level arrays.
5244

53-
### Typing properties
54-
The type of the properties is checked from two places:
55-
1. The type of the property itself. This can be defined using typehints [introduced in PHP7.4](https://wiki.php.net/rfc/typed_properties_v2);
56-
2. [PHPDoc types](https://phpstan.org/writing-php-code/phpdoc-types) for properties and constructor parameters.
57-
58-
First the native type of the property is checked, if this is defined and can be mapped the type will be used.
59-
If no type is provided or the type is a generic array, the mapper will check the PHPDoc for type of the property.
60-
61-
When a property is typed using a [union type](https://wiki.php.net/rfc/union_types_v2), the mapper will try to map any
62-
of the provided types from first to last until one mapping succeeds. The only exception is that `null` is always tried
63-
last.
64-
65-
If no valid type was found for a property, the provided data will be set to the property directly without any
66-
conversion.
67-
68-
### Custom mapping
69-
Sometimes, classes have a constructor that cannot be mapped automatically. For these cases there is a
70-
[`MapsItself`](https://github.com/jerodev/data-mapper/blob/master/src/MapsItself.php) interface that defines one
71-
static function: `mapObject`.
72-
When the mapper comes across a class that implements this interface, instead of using the constructor, the mapper will
73-
call the `MapsItself` with the provided data and is expected to return an instance of the current class.
74-
75-
### Post mapping
76-
The mapper also comes with a post mapping attribute. When adding the [`#[PostMapping]` attribute](https://github.com/jerodev/data-mapper/blob/master/src/Attributes/PostMapping.php)
77-
to a class with a string parameter, this function will be called directly after mapping the object.
78-
79-
A class can have multiple of these attributes and the attributes will be called in the same order as they are defined.
80-
81-
## Configuration
82-
The mapper comes with a few configuration options that can be set using the [`MapperConfig`](https://github.com/jerodev/data-mapper/blob/master/src/MapperConfig.php)
83-
object and passed to the mappers' constructor. This is not required, if no configuration is passed, the default config
84-
is used.
45+
## Documentation
46+
More information about mapping, configuration and best practices can be found in [the documentation](https://docs.deviaene.eu/data-mapper/).
8547

86-
| Option | Type | Default | Description |
87-
|----------------------------|----------|----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
88-
| `allowUninitializedFields` | `bool` | `true` | If disabled, the mapper will fail if one of the class properties that does not have a default value was not present in the data array. |
89-
| `classMapperDirectory` | `string` | `/tmp/mappers` | This is the location the mapper will create cached mapper functions for objects.<br />The default location is a mappers function in the operating system temporary folder. |
90-
| `debug` | `bool` | `false` | Enabling debug will clear all cached mapper functions after mapping has completed. |
91-
| `enumTryFrom` | `bool` | `false` | Enabling this will use the `::tryFrom()` method instead of `::from()` to parse strings to enums. |
92-
| `strictNullMapping` | `bool` | `true` | If enabled, the mapper will throw an error when a `null` value is passed for a property that was not typed as nullable. |
93-
94-
## Under the hood
95-
For simple native types, the mapper will use casting to convert the data to the correct type.
96-
97-
When requesting an array type, the mapper will call itself with the type of the array elements for each of the elements in the
98-
array.
99-
100-
For object types, some magic happens. On the very first run for a certain class, the mapper will use reflection to
101-
gather information about the class and build a mapper function based on the properties of the class.
102-
The function will also take into account required and optional properties that are passed to the constructor.
103-
104-
The goal is to have as much and as simple mapping as possible in these generated functions without having to go back
105-
to the mapper, to reach the best performance. For more information refer to the [class mapper function docs](./doc/class-mapper-function.md).
106-
107-
As an example, this is one of the testing classes of this library and its generated mapper function:
108-
109-
```php
110-
#[PostMapping('post')]
111-
class UserDto
112-
{
113-
/** First name and last name */
114-
public string $name;
115-
116-
/** @var array<self> */
117-
public array $friends = [];
118-
public ?SuitEnum $favoriteSuit = null;
119-
120-
public function __construct(string $name)
121-
{
122-
$this->name = $name;
123-
}
124-
125-
public function post(): void
126-
{
127-
$this->name = \ucfirst($this->name);
128-
}
129-
}
130-
```
131-
132-
```php
133-
function jmapper_8cf8f45dc33c7f58ab728699ac3ebec3(Jerodev\DataMapper\Mapper $mapper, array $data)
134-
{
135-
$x = new Jerodev\DataMapper\Tests\_Mocks\UserDto((string) $data['name']);
136-
$x->friends = (\array_key_exists('friends', $data) ? \array_map(static fn ($x6462755ab00b1) => $mapper->map('Jerodev\DataMapper\Tests\_Mocks\UserDto', $x6462755ab00b1), $data['friends']) : []);
137-
$x->favoriteSuit = (\array_key_exists('favoriteSuit', $data) ? Jerodev\DataMapper\Tests\_Mocks\SuitEnum::from($data['favoriteSuit']) : NULL);
138-
139-
$x->post($data, $x);
140-
141-
return $x;
142-
}
143-
```
48+
## License
49+
This library is licensed under the MIT License (MIT). Please see [License File](license.md) for more information.

src/Mapper.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,11 @@
1313
class Mapper
1414
{
1515
private readonly DataTypeFactory $dataTypeFactory;
16-
private readonly ObjectMapper $objectMapper;
17-
public readonly MapperConfig $config;
16+
public readonly ObjectMapper $objectMapper;
1817

1918
public function __construct(
20-
?MapperConfig $config = null,
19+
public readonly MapperConfig $config = new MapperConfig(),
2120
) {
22-
$this->config = $config ?? new MapperConfig();
23-
2421
$this->dataTypeFactory = new DataTypeFactory();
2522
$this->objectMapper = new ObjectMapper(
2623
$this,

src/Objects/ObjectMapper.php

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ public function __construct(
2424
}
2525

2626
/**
27-
* @param DataType $type
27+
* @param DataType|string $type
2828
* @param array|string $data
2929
* @return object|null
3030
* @throws CouldNotResolveClassException
3131
*/
32-
public function map(DataType $type, array|string $data): ?object
32+
public function map(DataType|string $type, array|string $data): ?object
3333
{
34-
$class = $this->dataTypeFactory->classResolver->resolve($type->type);
34+
$class = $this->dataTypeFactory->classResolver->resolve(\is_string($type) ? $type : $type->type);
3535
if (\is_subclass_of($class, MapsItself::class)) {
3636
return \call_user_func([$class, 'mapSelf'], $data, $this->mapper);
3737
}
@@ -45,12 +45,16 @@ public function map(DataType $type, array|string $data): ?object
4545
return $class::from($data);
4646
}
4747

48-
$blueprint = $this->classBluePrinter->print($class);
49-
5048
$functionName = self::MAPPER_FUNCTION_PREFIX . \md5($class);
5149
$fileName = $this->mapperDirectory() . \DIRECTORY_SEPARATOR . $functionName . '.php';
5250
if (! \file_exists($fileName)) {
53-
\file_put_contents($fileName, $this->createObjectMappingFunction($blueprint, $functionName));
51+
\file_put_contents(
52+
$fileName,
53+
$this->createObjectMappingFunction(
54+
$this->classBluePrinter->print($class),
55+
$functionName,
56+
),
57+
);
5458
}
5559

5660
// Include the function containing file and call the function.
@@ -186,6 +190,11 @@ private function castInMapperFunction(string $propertyName, DataTypeCollection $
186190
if (\is_subclass_of($type->type, MapsItself::class)) {
187191
return "{$type->type}::mapSelf({$propertyName}, \$mapper)";
188192
}
193+
194+
$className = $this->dataTypeFactory->print($type, $bluePrint->fileName);
195+
if (\class_exists($className)) {
196+
return "\$mapper->objectMapper->map('{$className}', {$propertyName})";
197+
}
189198
}
190199

191200
return '$mapper->map(\'' . $this->dataTypeFactory->print($type, $bluePrint->fileName) . '\', ' . $propertyName . ')';
@@ -197,7 +206,13 @@ private function wrapDefault(string $value, string $arrayKey, mixed $defaultValu
197206
$value = "({$value})";
198207
}
199208

200-
return "(\\array_key_exists('{$arrayKey}', \$data) ? {$value} : " . \var_export($defaultValue, true) . ')';
209+
if (\is_object($defaultValue)) {
210+
$defaultRaw = 'new ' . $defaultValue::class . '()';
211+
} else {
212+
$defaultRaw = \var_export($defaultValue, true);
213+
}
214+
215+
return "(\\array_key_exists('{$arrayKey}', \$data) ? {$value} : {$defaultRaw})";
201216
}
202217

203218
private function wrapArrayKeyExists(string $expression, string $arrayKey): string

0 commit comments

Comments
 (0)