Skip to content

Commit

Permalink
FieldContext, MappedObjectContext: initialize Type lazily
Browse files Browse the repository at this point in the history
  • Loading branch information
mabar committed Jan 1, 2025
1 parent 12d62dd commit e9631c9
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 18 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- all rules initialize `Type` lazily (performance optimization)
- `ArrayOfRule`
- check during metadata parsing that default value is an array when `mergeDefaults` is enabled
- `FieldContext`, `MappedObjectContext`
- initializes `Type` lazily (performance optimization)
- `ArrayShapeType`
- `getFields()` always returns the same instances

## [0.2.0](https://github.com/orisai/object-mapper/compare/0.1.0...0.2.0) - 2024-06-22

Expand Down
20 changes: 16 additions & 4 deletions src/Context/FieldContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Orisai\ObjectMapper\Context;

use Closure;
use Orisai\ObjectMapper\Meta\MetaLoader;
use Orisai\ObjectMapper\Meta\Shared\DefaultValueMeta;
use Orisai\ObjectMapper\Processing\Options;
Expand All @@ -13,7 +14,10 @@
final class FieldContext extends BaseFieldContext
{

private Type $type;
/** @var Closure(): Type */
private Closure $typeCreator;

private ?Type $type = null;

private DefaultValueMeta $default;

Expand All @@ -23,30 +27,38 @@ final class FieldContext extends BaseFieldContext
private ReflectionProperty $property;

/**
* @param Closure(): Type $typeCreator
* @param int|string $fieldName
*/
public function __construct(
MetaLoader $metaLoader,
RuleManager $ruleManager,
Processor $processor,
Options $options,
Type $type,
Closure $typeCreator,
DefaultValueMeta $default,
bool $initializeObjects,
$fieldName,
ReflectionProperty $property
)
{
parent::__construct($metaLoader, $ruleManager, $processor, $options, $initializeObjects);
$this->type = $type;
$this->typeCreator = $typeCreator;
$this->default = $default;
$this->fieldName = $fieldName;
$this->property = $property;
}

public function getType(): Type
{
return $this->type;
if ($this->type !== null) {
return $this->type;
}

$type = ($this->typeCreator)();
unset($this->typeCreator);

return $this->type = $type;
}

public function hasDefaultValue(): bool
Expand Down
22 changes: 18 additions & 4 deletions src/Context/MappedObjectContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Orisai\ObjectMapper\Context;

use Closure;
use Orisai\ObjectMapper\Meta\MetaLoader;
use Orisai\ObjectMapper\Processing\Options;
use Orisai\ObjectMapper\Processing\Processor;
Expand All @@ -11,24 +12,37 @@
final class MappedObjectContext extends BaseFieldContext
{

private MappedObjectType $type;
/** @var Closure(): MappedObjectType */
private Closure $typeCreator;

private ?MappedObjectType $type = null;

/**
* @param Closure(): MappedObjectType $typeCreator
*/
public function __construct(
MetaLoader $metaLoader,
RuleManager $ruleManager,
Processor $processor,
Options $options,
MappedObjectType $type,
Closure $typeCreator,
bool $initializeObjects
)
{
parent::__construct($metaLoader, $ruleManager, $processor, $options, $initializeObjects);
$this->type = $type;
$this->typeCreator = $typeCreator;
}

public function getType(): MappedObjectType
{
return $this->type;
if ($this->type !== null) {
return $this->type;
}

$type = ($this->typeCreator)();
unset($this->typeCreator);

return $this->type = $type;
}

}
19 changes: 13 additions & 6 deletions src/Processing/DefaultProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Orisai\ObjectMapper\Processing;

use Closure;
use Nette\Utils\Helpers;
use Orisai\Exceptions\Logic\InvalidState;
use Orisai\ObjectMapper\Args\Args;
Expand Down Expand Up @@ -29,6 +30,7 @@
use Orisai\ObjectMapper\Rules\RuleManager;
use Orisai\ObjectMapper\Types\MappedObjectType;
use Orisai\ObjectMapper\Types\MessageType;
use Orisai\ObjectMapper\Types\Type;
use ReflectionProperty;
use function array_diff;
use function array_key_exists;
Expand Down Expand Up @@ -109,11 +111,11 @@ private function processBase($data, string $class, ?Options $options, bool $init
{
$options ??= new Options();
$options = $options->withProcessedClass($class);
$type = $this->createMappedObjectType($class, $options);
$typeCreator = fn (): MappedObjectType => $this->createMappedObjectType($class, $options);
$meta = $this->metaCache[$class] ??= $this->metaLoader->load($class);
$holder = $this->createHolder($class, $meta->getClass());

$mappedObjectContext = $this->createMappedObjectContext($options, $type, $initializeObjects);
$mappedObjectContext = $this->createMappedObjectContext($options, $typeCreator, $initializeObjects);
$callContext = $this->createProcessorRunContext($class, $meta, $holder);

$processedData = $this->processData($data, $mappedObjectContext, $callContext);
Expand Down Expand Up @@ -181,9 +183,12 @@ private function createMappedObjectType(string $class, Options $options): Mapped
);
}

/**
* @param Closure(): MappedObjectType $typeCreator
*/
private function createMappedObjectContext(
Options $options,
MappedObjectType $type,
Closure $typeCreator,
bool $initializeObjects
): MappedObjectContext
{
Expand All @@ -192,7 +197,7 @@ private function createMappedObjectContext(
$this->ruleManager,
$this,
$options,
$type,
$typeCreator,
$initializeObjects,
);
}
Expand Down Expand Up @@ -466,13 +471,14 @@ private function createFieldContext(
): FieldContext
{
$parentType = $mappedObjectContext->getType();
$typeCreator = static fn (): Type => $parentType->getField($fieldName);

return new FieldContext(
$this->metaLoader,
$this->ruleManager,
$this,
$mappedObjectContext->getOptions()->createClone(),
$parentType->getFields()[$fieldName],
$typeCreator,
$meta->getDefault(),
$mappedObjectContext->shouldInitializeObjects(),
$fieldName,
Expand Down Expand Up @@ -710,8 +716,9 @@ public function processSkippedFields(
$skippedFieldsContext = $this->skippedMap->getSkippedFieldsContext($object);

$type = $skippedFieldsContext->getType();
$typeCreator = static fn (): MappedObjectType => $type;
$options ??= $skippedFieldsContext->getOptions();
$mappedObjectContext = $this->createMappedObjectContext($options, $type, true);
$mappedObjectContext = $this->createMappedObjectContext($options, $typeCreator, true);
$skippedFields = $skippedFieldsContext->getSkippedFields();

$meta = $this->metaLoader->load($class);
Expand Down
3 changes: 2 additions & 1 deletion src/Tester/TesterDependencies.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Orisai\ObjectMapper\Processing\Processor;
use Orisai\ObjectMapper\Rules\DefaultRuleManager;
use Orisai\ObjectMapper\Types\MessageType;
use Orisai\ObjectMapper\Types\Type;
use ReflectionProperty;

final class TesterDependencies
Expand Down Expand Up @@ -82,7 +83,7 @@ public function createFieldContext(
$this->ruleManager,
$this->processor,
$options !== null ? $options->createClone() : new Options(),
new MessageType('test'),
static fn (): Type => new MessageType('test'),
$defaultValueMeta ?? DefaultValueMeta::fromNothing(),
$initializeObjects,
'test',
Expand Down
24 changes: 24 additions & 0 deletions src/Types/ArrayShapeType.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Orisai\ObjectMapper\Types;

use Closure;
use Orisai\Exceptions\Logic\InvalidState;
use Orisai\ObjectMapper\Exception\WithTypeAndValue;

class ArrayShapeType implements Type
Expand Down Expand Up @@ -37,6 +38,28 @@ public function overwriteInvalidField($field, WithTypeAndValue $typeAndValue): v
$this->invalidFields[$field] = $typeAndValue;
}

/**
* @param int|string $field
*
* @internal
*/
public function getField($field): Type
{
$type = $this->fields[$field] ?? null;

if ($type === null) {
throw InvalidState::create()
->withMessage("Cannot get field '$field' because it was never set.");
}

if ($type instanceof Closure) {
$type = $type();
$this->fields[$field] = $type;
}

return $type;
}

/**
* @return array<int|string, Type>
*/
Expand All @@ -46,6 +69,7 @@ public function getFields(): array
foreach ($this->fields as $field => $type) {
if ($type instanceof Closure) {
$type = $type();
$this->fields[$field] = $type;
}

$fields[$field] = $type;
Expand Down
26 changes: 23 additions & 3 deletions tests/Unit/Types/ArrayShapeTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

namespace Tests\Orisai\ObjectMapper\Unit\Types;

use Orisai\Exceptions\Logic\InvalidState;
use Orisai\ObjectMapper\Exception\ValueDoesNotMatch;
use Orisai\ObjectMapper\Processing\Value;
use Orisai\ObjectMapper\Types\ArrayShapeType;
use Orisai\ObjectMapper\Types\MessageType;
use Orisai\ObjectMapper\Types\SimpleValueType;
use Orisai\ObjectMapper\Types\Type;
use PHPUnit\Framework\TestCase;

final class ArrayShapeTypeTest extends TestCase
Expand Down Expand Up @@ -103,19 +106,36 @@ public function testFields(): void
public function testFieldFromClosure(): void
{
$type = new ArrayShapeType();
$type->addField('field', static fn (): MessageType => new MessageType('test'));
$type->addField('field', static fn (): Type => new MessageType('test'));
$type->addField('field2', static fn (): Type => new SimpleValueType('string'));

$expectedFields = [
'field' => new MessageType('test'),
'field' => $t1 = new MessageType('test'),
'field2' => $t2 = new SimpleValueType('string'),
];

self::assertEquals($t1, $type->getField('field'));
self::assertSame($type->getField('field'), $type->getField('field'));

$fields = $type->getFields();
self::assertEquals($expectedFields, $fields);
self::assertNotSame($expectedFields, $fields);

$fields2 = $type->getFields();
self::assertEquals($fields, $fields2);
self::assertNotSame($fields, $fields2);
self::assertSame($fields, $fields2);

self::assertEquals($t2, $type->getField('field2'));
}

public function testGetUnknownField(): void
{
$type = new ArrayShapeType();

$this->expectException(InvalidState::class);
$this->expectExceptionMessage("Cannot get field 'unknown' because it was never set.");

$type->getField('unknown');
}

}

0 comments on commit e9631c9

Please sign in to comment.