Skip to content

Commit adb960c

Browse files
author
Kirill Nesmeyanov
committed
Improve mapping logic
1 parent 71190c5 commit adb960c

File tree

6 files changed

+129
-19
lines changed

6 files changed

+129
-19
lines changed

src/Mapping/Driver/AttributeDriver.php

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use TypeLang\Mapper\Mapping\MapName;
1313
use TypeLang\Mapper\Mapping\MapType;
1414
use TypeLang\Mapper\Mapping\Metadata\ClassMetadata;
15+
use TypeLang\Mapper\Mapping\Metadata\ExpressionMetadata;
1516
use TypeLang\Mapper\Mapping\Metadata\TypeMetadata;
1617
use TypeLang\Mapper\Mapping\NormalizeAsArray;
1718
use TypeLang\Mapper\Mapping\SkipWhen;
@@ -90,9 +91,12 @@ protected function load(
9091
$attribute = $this->findPropertyAttribute($property, MapType::class);
9192

9293
if ($attribute !== null) {
93-
$type = $this->createType($attribute->type, $property, $types, $parser);
94-
95-
$metadata->setTypeInfo($type);
94+
$metadata->setTypeInfo($this->createType(
95+
type: $attribute->type,
96+
property: $property,
97+
types: $types,
98+
parser: $parser,
99+
));
96100
}
97101

98102
// -----------------------------------------------------------------
@@ -112,11 +116,12 @@ protected function load(
112116
$attribute = $this->findPropertyAttribute($property, SkipWhen::class);
113117

114118
if ($attribute !== null) {
115-
$expression = $this->createExpression($attribute->expr, [
116-
'this',
117-
]);
118-
119-
$metadata->setSkipCondition($expression);
119+
$metadata->setSkipCondition(new ExpressionMetadata(
120+
expression: $this->createExpression($attribute->expr, [
121+
$attribute->context,
122+
]),
123+
context: $attribute->context,
124+
));
120125
}
121126
}
122127
}

src/Mapping/Metadata/ClassMetadata.php

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,26 @@
1010
use TypeLang\Parser\Node\Stmt\TypeStatement;
1111

1212
/**
13+
* Represents an abstraction over general information about a class.
14+
*
1315
* @template T of object
1416
*/
1517
final class ClassMetadata extends Metadata
1618
{
1719
/**
20+
* Contains a list of class fields available for
21+
* normalization and denormalization.
22+
*
1823
* @var array<non-empty-string, PropertyMetadata>
1924
*/
2025
private array $properties = [];
2126

27+
/**
28+
* Contains a {@see bool} flag that is responsible for converting the
29+
* object into an associative {@see array} during normalization.
30+
*
31+
* If {@see null}, then the system setting should be used.
32+
*/
2233
private ?bool $normalizeAsArray = null;
2334

2435
/**
@@ -40,6 +51,8 @@ public function __construct(
4051
/**
4152
* Dynamically creates AST class representation.
4253
*
54+
* Required to print type information in exceptions.
55+
*
4356
* @api
4457
*
4558
* @codeCoverageIgnore
@@ -70,7 +83,7 @@ public function getTypeStatement(Context $context): TypeStatement
7083
}
7184

7285
/**
73-
* Returns class name.
86+
* Returns full qualified class name.
7487
*
7588
* @return class-string<T>
7689
*/
@@ -80,6 +93,17 @@ public function getName(): string
8093
}
8194

8295
/**
96+
* Returns information about the normalization method of an object.
97+
*
98+
* - Returns {@see true} if the object should be normalized as
99+
* an associative {@see array}.
100+
*
101+
* - Returns {@see false} if the object should be normalized as an
102+
* anonymous {@see object}.
103+
*
104+
* - Returns {@see null} if the system settings for this option
105+
* should be used.
106+
*
83107
* @api
84108
*/
85109
public function isNormalizeAsArray(): ?bool
@@ -88,6 +112,8 @@ public function isNormalizeAsArray(): ?bool
88112
}
89113

90114
/**
115+
* Forces the object normalization option.
116+
*
91117
* @api
92118
*/
93119
public function shouldNormalizeAsArray(?bool $enabled = null): void
@@ -96,6 +122,9 @@ public function shouldNormalizeAsArray(?bool $enabled = null): void
96122
}
97123

98124
/**
125+
* Adds {@see PropertyMetadata} property information to
126+
* the {@see ClassMetadata} instance.
127+
*
99128
* @api
100129
*/
101130
public function addProperty(PropertyMetadata $property): void
@@ -104,6 +133,10 @@ public function addProperty(PropertyMetadata $property): void
104133
}
105134

106135
/**
136+
* Returns {@see PropertyMetadata} information about the property.
137+
*
138+
* If it was previously absent, it creates a new one.
139+
*
107140
* @api
108141
*
109142
* @param non-empty-string $name
@@ -114,6 +147,10 @@ public function getPropertyOrCreate(string $name): PropertyMetadata
114147
}
115148

116149
/**
150+
* Returns {@see PropertyMetadata} information about a property,
151+
* or returns {@see null} if no such property has been registered
152+
* in the {@see ClassMetadata} instance.
153+
*
117154
* @api
118155
*
119156
* @param non-empty-string $name
@@ -124,6 +161,10 @@ public function findProperty(string $name): ?PropertyMetadata
124161
}
125162

126163
/**
164+
* Returns {@see true} if the {@see PropertyMetadata} property information
165+
* was registered in the {@see ClassMetadata} instance
166+
* and {@see false} otherwise.
167+
*
127168
* @api
128169
*
129170
* @param non-empty-string $name
@@ -134,6 +175,8 @@ public function hasProperty(string $name): bool
134175
}
135176

136177
/**
178+
* Returns a list of registered {@see PropertyMetadata} properties.
179+
*
137180
* @return list<PropertyMetadata>
138181
*/
139182
public function getProperties(): array
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\Mapper\Mapping\Metadata;
6+
7+
use Symfony\Component\ExpressionLanguage\ParsedExpression;
8+
9+
final class ExpressionMetadata extends Metadata
10+
{
11+
/**
12+
* @var non-empty-string
13+
*/
14+
public const DEFAULT_CONTEXT_VARIABLE_NAME = 'this';
15+
16+
/**
17+
* @param non-empty-string $context
18+
*/
19+
public function __construct(
20+
private readonly ParsedExpression $expression,
21+
private readonly string $context = self::DEFAULT_CONTEXT_VARIABLE_NAME,
22+
?int $createdAt = null,
23+
) {
24+
parent::__construct($createdAt);
25+
}
26+
27+
/**
28+
* @api
29+
*
30+
* @return non-empty-string
31+
*/
32+
public function getContextVariableName(): string
33+
{
34+
return $this->context;
35+
}
36+
37+
/**
38+
* @api
39+
*/
40+
public function getExpression(): ParsedExpression
41+
{
42+
return $this->expression;
43+
}
44+
}

src/Mapping/Metadata/PropertyMetadata.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
namespace TypeLang\Mapper\Mapping\Metadata;
66

7-
use Symfony\Component\ExpressionLanguage\ParsedExpression;
87
use TypeLang\Mapper\Runtime\Context;
98
use TypeLang\Parser\Node\Stmt\NamedTypeNode;
109
use TypeLang\Parser\Node\Stmt\Shape\NamedFieldNode;
@@ -21,7 +20,7 @@ final class PropertyMetadata extends Metadata
2120

2221
private bool $hasDefaultValue = false;
2322

24-
private ?ParsedExpression $skipWhen = null;
23+
private ?ExpressionMetadata $skipWhen = null;
2524

2625
/**
2726
* @param non-empty-string $export
@@ -158,7 +157,7 @@ public function findTypeInfo(): ?TypeMetadata
158157
/**
159158
* @api
160159
*/
161-
public function setSkipCondition(ParsedExpression $expression): void
160+
public function setSkipCondition(ExpressionMetadata $expression): void
162161
{
163162
$this->skipWhen = $expression;
164163
}
@@ -180,14 +179,15 @@ public function hasSkipCondition(): bool
180179
}
181180

182181
/**
183-
* Note: The prefix "find" is used to indicate that the {@see ParsedExpression}
184-
* definition may be optional and method may return {@see null}.
185-
* The prefix "get" is used when the value is forced to be obtained
186-
* and should throw an exception if the type definition is missing.
182+
* Note: The prefix "find" is used to indicate that the
183+
* {@see ExpressionMetadata} definition may be optional and method
184+
* may return {@see null}. The prefix "get" is used when the value
185+
* is forced to be obtained and should throw an exception if the type
186+
* definition is missing.
187187
*
188188
* @api
189189
*/
190-
public function findSkipCondition(): ?ParsedExpression
190+
public function findSkipCondition(): ?ExpressionMetadata
191191
{
192192
return $this->skipWhen;
193193
}

src/Mapping/SkipWhen.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,30 @@
55
namespace TypeLang\Mapper\Mapping;
66

77
use JetBrains\PhpStorm\Language;
8+
use TypeLang\Mapper\Mapping\Metadata\ExpressionMetadata;
89

910
#[\Attribute(\Attribute::TARGET_PROPERTY)]
1011
class SkipWhen
1112
{
1213
public function __construct(
1314
/**
15+
* Contains an expression for which the field will be skipped
16+
* during normalization.
17+
*
18+
* Requires "`symfony/expression-language`" package to be installed.
19+
*
20+
* @link https://symfony.com/doc/current/components/expression_language.html
21+
*
1422
* @var non-empty-string
1523
*/
1624
#[Language('JavaScript')]
1725
public readonly string $expr,
26+
/**
27+
* Contains the name of the variable that will hold the reference
28+
* of the object.
29+
*
30+
* @var non-empty-string
31+
*/
32+
public readonly string $context = ExpressionMetadata::DEFAULT_CONTEXT_VARIABLE_NAME,
1833
) {}
1934
}

src/Type/ObjectType/ObjectTypeNormalizer.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,12 @@ protected function normalizeObject(object $object, Context $context): array
9898
$skip = $meta->findSkipCondition();
9999

100100
if ($skip !== null) {
101-
$nodes = $skip->getNodes();
101+
$expression = $skip->getExpression();
102+
$variable = $skip->getContextVariableName();
102103

103-
if ((bool) $nodes->evaluate([], ['this' => $object])) {
104+
$nodes = $expression->getNodes();
105+
106+
if ((bool) $nodes->evaluate([], [$variable => $object])) {
104107
continue;
105108
}
106109
}

0 commit comments

Comments
 (0)