Skip to content

Commit 869aca2

Browse files
authored
Introduce scenarios to allow multiple normalized representations of the data. (#19)
* Feature: Introduce scenarios to allow multiple normalized representations of the model data.
1 parent fe77bcc commit 869aca2

22 files changed

+792
-87
lines changed

docs/VOM.md

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,6 +1282,195 @@ class RegexpExtractorProperty
12821282
$model = $objectMapper->denormalize('image1.jpg,tag:foobar,visibility:hidden', RegexpExtractorProperty::class);
12831283
```
12841284

1285+
## Scenarios
1286+
1287+
If there is more than one representation of your normalized data, the `scenario` argument allows to switch between different mappings.
1288+
1289+
```php
1290+
use Zolex\VOM\Mapping as VOM;
1291+
1292+
#[VOM\Model]
1293+
class ModelWithScenarios
1294+
{
1295+
#[VOM\Property]
1296+
#[VOM\Property('[FIRST_NAME]', scenario: 'second')]
1297+
#[VOM\Property('[name][first]'), scenario: 'third'])]
1298+
public string $firstName;
1299+
1300+
#[VOM\Property]
1301+
#[VOM\Property('[LAST_NAME]', scenario: 'second')]
1302+
#[VOM\Property('[name][last]'), scenario: 'third'])]
1303+
public string $lastName;
1304+
}
1305+
```
1306+
1307+
The scenario can be passed in the context of the `serialize`, `deserialize`, `normalize` and `denormalize` methods of the ObjectMapper.
1308+
1309+
```php
1310+
$data = [
1311+
'firstName' => 'Peter',
1312+
'lastName' => 'Parker'
1313+
];
1314+
$objectMapper->denormalize($data, ModelWithScenarios::class);
1315+
1316+
$data = [
1317+
'FIRST_NAME' => 'Peter',
1318+
'LAST_NAME' => 'Parker'
1319+
];
1320+
$objectMapper->denormalize($data, ModelWithScenarios::class, null, ['scenario' => 'second']);
1321+
1322+
$data = [
1323+
'name' => [
1324+
'first' => 'Peter',
1325+
'last' => 'Parker'
1326+
]
1327+
];
1328+
$objectMapper->denormalize($data, ModelWithScenarios::class, null, ['scenario' => 'third']);
1329+
```
1330+
1331+
The `scenario` argument can also be used on the arguments of constructors, factories and denormalizers as well as on normalizer methods.
1332+
1333+
**Constructor Scenarios**
1334+
1335+
```php
1336+
use Zolex\VOM\Mapping as VOM;
1337+
1338+
#[VOM\Model]
1339+
class ModelWithScenarios
1340+
{
1341+
public function __construct(
1342+
#[VOM\Argument]
1343+
#[VOM\Argument('[FIRST_NAME]', scenario: 'second')]
1344+
string $firstName,
1345+
1346+
#[VOM\Argument]
1347+
#[VOM\Argument('[LAST_NAME]', scenario: 'second')]
1348+
string $lastName,
1349+
) {
1350+
// ...
1351+
}
1352+
}
1353+
```
1354+
1355+
**Factory Scenarios**
1356+
1357+
```php
1358+
use Zolex\VOM\Mapping as VOM;
1359+
1360+
#[VOM\Model]
1361+
class ModelWithScenarios
1362+
{
1363+
#[VOM\Factory]
1364+
public function create(
1365+
#[VOM\Argument]
1366+
#[VOM\Argument('[FIRST_NAME]', scenario: 'second')]
1367+
string $firstName,
1368+
1369+
#[VOM\Argument]
1370+
#[VOM\Argument('[LAST_NAME]', scenario: 'second')]
1371+
string $lastName,
1372+
): self {
1373+
// ...
1374+
}
1375+
}
1376+
```
1377+
1378+
**Denormalizer Scenarios**
1379+
1380+
```php
1381+
use Zolex\VOM\Mapping as VOM;
1382+
1383+
#[VOM\Model]
1384+
class ModelWithScenarios
1385+
{
1386+
#[VOM\Denormalizer]
1387+
public function denormalizeValues(
1388+
#[VOM\Argument]
1389+
#[VOM\Argument('[FIRST_NAME]', scenario: 'second')]
1390+
string $firstName,
1391+
1392+
#[VOM\Argument]
1393+
#[VOM\Argument('[LAST_NAME]', scenario: 'second')]
1394+
string $lastName,
1395+
) {
1396+
// ...
1397+
}
1398+
}
1399+
```
1400+
1401+
**Normalizer Scenarios**
1402+
1403+
```php
1404+
use Zolex\VOM\Mapping as VOM;
1405+
1406+
#[VOM\Model]
1407+
class ModelWithScenarios
1408+
{
1409+
private string $firstName = 'Peter';
1410+
private string $lastName = 'Parker';
1411+
1412+
#[VOM\Normalizer(accessor: '[firstName]')]
1413+
public function normalizeFirstNameForDefaultScenario(): string {
1414+
return $this->firstName;
1415+
}
1416+
1417+
#[VOM\Normalizer(accessor: '[FIRST_NAME]', scenario: 'second')]
1418+
public function normalizeFirstNameForSecondScenario(): string {
1419+
return strtoupper($this->firstName);
1420+
}
1421+
1422+
#[VOM\Normalizer(accessor: '[lastName]')]
1423+
public function normalizeLastNameForDefaultScenario(): string {
1424+
return return $this->lastName;
1425+
}
1426+
1427+
#[VOM\Normalizer(accessor: '[LAST_NAME]', scenario: 'second')]
1428+
public function normalizeFirstNameForSecondScenario(): string {
1429+
return strtoupper($this->lastName);
1430+
}
1431+
}
1432+
```
1433+
1434+
> [!NOTE]
1435+
> For fine-grained control, scenarios can also be combined with the `Groups` feature.
1436+
1437+
```php
1438+
use \Symfony\Component\Serializer\Attribute\Groups;
1439+
use Zolex\VOM\Mapping as VOM;
1440+
1441+
#[VOM\Model]
1442+
class ModelWithScenarios
1443+
{
1444+
private string $firstName = 'Peter';
1445+
private string $lastName = 'Parker';
1446+
1447+
#[Groups('firstname')]
1448+
#[VOM\Normalizer(accessor: '[firstName]')]
1449+
public function getFirstNameForDefaultScenario(): string {
1450+
return $this->firstName;
1451+
}
1452+
1453+
#[Groups('firstname')]
1454+
#[VOM\Normalizer(accessor: '[FIRST_NAME]', scenario: 'second')]
1455+
public function getFirstNameForSecondScenario(): string {
1456+
return strtoupper($this->firstName);
1457+
}
1458+
1459+
#[Groups('lastname')]
1460+
#[VOM\Normalizer(accessor: '[lastName]')]
1461+
public function getLastNameForDefaultScenario(): string {
1462+
return return $this->lastName;
1463+
}
1464+
1465+
#[Groups('lastname')]
1466+
#[VOM\Normalizer(accessor: '[LAST_NAME]', scenario: 'second')]
1467+
public function getFirstNameForSecondScenario(): string {
1468+
return strtoupper($this->lastName);
1469+
}
1470+
}
1471+
```
1472+
1473+
12851474
## Interfaces and Abstract Classes
12861475

12871476
When dealing with objects that are fairly similar or share properties, you can use interfaces or abstract classes.

src/Mapping/AbstractProperty.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
namespace Zolex\VOM\Mapping;
1515

16+
use Zolex\VOM\Metadata\ModelMetadata;
17+
1618
abstract class AbstractProperty
1719
{
1820
public function __construct(
@@ -27,6 +29,7 @@ public function __construct(
2729
private ?array $map = null,
2830
private bool $serialized = false,
2931
private ?string $extractor = null,
32+
private ?string $scenario = ModelMetadata::DEFAULT_SCENARIO,
3033
) {
3134
}
3235

@@ -99,4 +102,9 @@ public function getExtractor(): ?string
99102
{
100103
return $this->extractor;
101104
}
105+
106+
public function getScenario(): string
107+
{
108+
return $this->scenario;
109+
}
102110
}

src/Mapping/Argument.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
*
2323
* {@see ModelMetadataFactory::createPropertyMetadata()}
2424
*/
25-
#[\Attribute(\Attribute::TARGET_PARAMETER | \Attribute::TARGET_PROPERTY)]
25+
#[\Attribute(\Attribute::TARGET_PARAMETER | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
2626
final class Argument extends AbstractProperty
2727
{
2828
}

src/Mapping/Denormalizer.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,19 @@
1616
#[\Attribute(\Attribute::TARGET_METHOD)]
1717
final class Denormalizer
1818
{
19-
public function __construct(private readonly bool $allowNonScalarArguments = false)
20-
{
19+
public function __construct(
20+
private readonly bool $allowNonScalarArguments = false,
21+
private readonly ?string $scenario = null,
22+
) {
2123
}
2224

2325
public function allowNonScalarArguments(): bool
2426
{
2527
return $this->allowNonScalarArguments;
2628
}
29+
30+
public function getScenario(): ?string
31+
{
32+
return $this->scenario;
33+
}
2734
}

src/Mapping/Factory.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,19 @@
1616
#[\Attribute(\Attribute::TARGET_METHOD)]
1717
final class Factory
1818
{
19-
public function __construct(private readonly int $priority = 0)
20-
{
19+
public function __construct(
20+
private readonly int $priority = 0,
21+
private readonly ?string $scenario = null,
22+
) {
2123
}
2224

2325
public function getPriority(): int
2426
{
2527
return $this->priority;
2628
}
29+
30+
public function getScenario(): ?string
31+
{
32+
return $this->scenario;
33+
}
2734
}

src/Mapping/Normalizer.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,19 @@
1616
#[\Attribute(\Attribute::TARGET_METHOD)]
1717
final class Normalizer
1818
{
19-
public function __construct(private readonly ?string $accessor = null)
20-
{
19+
public function __construct(
20+
private readonly ?string $accessor = null,
21+
private readonly ?string $scenario = null,
22+
) {
2123
}
2224

2325
public function getAccessor(): ?string
2426
{
2527
return $this->accessor;
2628
}
29+
30+
public function getScenario(): ?string
31+
{
32+
return $this->scenario;
33+
}
2734
}

src/Mapping/Property.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
namespace Zolex\VOM\Mapping;
1515

16-
#[\Attribute(\Attribute::TARGET_PROPERTY)]
16+
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
1717
final class Property extends AbstractProperty
1818
{
1919
}

src/Metadata/AbstractCallableMetadata.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ public function getLongMethodName(): string
4343
return \sprintf('%s::%s()', $this->class, $this->method);
4444
}
4545

46-
public function getArguments(): array
46+
public function getArguments(string $scenario = ModelMetadata::DEFAULT_SCENARIO): array
4747
{
48-
return $this->arguments;
48+
return $this->arguments[$scenario] ?? [];
4949
}
5050
}

0 commit comments

Comments
 (0)