Skip to content

Commit

Permalink
Add a caching layer for TypeResolver
Browse files Browse the repository at this point in the history
  • Loading branch information
priyadi committed Jan 11, 2024
1 parent 67a3b15 commit 85133ba
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 32 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* Use property info caching in non-framework usage
* Add mapping caching
* Change console command to use `rekalogika` prefix
* Add a caching layer for `TypeResolver`

## 0.5.2

Expand Down
16 changes: 13 additions & 3 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
use Rekalogika\Mapper\Transformer\StringToBackedEnumTransformer;
use Rekalogika\Mapper\Transformer\TraversableToArrayAccessTransformer;
use Rekalogika\Mapper\Transformer\TraversableToTraversableTransformer;
use Rekalogika\Mapper\TypeResolver\CachingTypeResolver;
use Rekalogika\Mapper\TypeResolver\TypeResolver;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\PropertyInfo\PropertyInfoCacheExtractor;
Expand Down Expand Up @@ -158,15 +159,24 @@
])
->tag('kernel.cache_warmer');

# type resolver

$services
->set('rekalogika.mapper.type_resolver', TypeResolver::class);

$services
->set('rekalogika.mapper.type_resolver.caching', CachingTypeResolver::class)
->decorate('rekalogika.mapper.type_resolver')
->args([
service('rekalogika.mapper.type_resolver.caching.inner'),
]);

# other services

$services
->set('rekalogika.mapper.object_cache_factory', ObjectCacheFactory::class)
->args([service('rekalogika.mapper.type_resolver')]);

$services
->set('rekalogika.mapper.type_resolver', TypeResolver::class);

$services
->set('rekalogika.mapper.main_transformer', MainTransformer::class)
->args([
Expand Down
21 changes: 18 additions & 3 deletions src/MainTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,27 @@ public function transform(
$objectCache = $context[self::OBJECT_CACHE];
}

// init vars
// gets simple target types from the provided target type

$targetType = $this->typeResolver->getSimpleTypes($targetType);
$sourceType = $this->typeResolver->guessTypeFromVariable($source);
if ($targetType instanceof Type) {
$targetType = [$targetType];
}

$simpleTargetTypes = [];

foreach ($targetType as $singleTargetType) {
foreach ($this->typeResolver->getSimpleTypes($singleTargetType) as $simpleType) {
$simpleTargetTypes[] = $simpleType;
}
}

// guess the source type

$sourceType = $this->typeResolver->guessTypeFromVariable($source);

// iterate simple target types and find the suitable transformer

foreach ($simpleTargetTypes as $singleTargetType) {
$transformers = $this->getTransformers($sourceType, $singleTargetType);

foreach ($transformers as $transformer) {
Expand Down
3 changes: 2 additions & 1 deletion src/MapperFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
use Rekalogika\Mapper\Transformer\StringToBackedEnumTransformer;
use Rekalogika\Mapper\Transformer\TraversableToArrayAccessTransformer;
use Rekalogika\Mapper\Transformer\TraversableToTraversableTransformer;
use Rekalogika\Mapper\TypeResolver\CachingTypeResolver;
use Rekalogika\Mapper\TypeResolver\TypeResolver;
use Rekalogika\Mapper\TypeResolver\TypeResolverInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
Expand Down Expand Up @@ -358,7 +359,7 @@ protected function getTraversableToTraversableTransformer(): TransformerInterfac
protected function getTypeResolver(): TypeResolverInterface
{
if (null === $this->typeResolver) {
$this->typeResolver = new TypeResolver();
$this->typeResolver = new CachingTypeResolver(new TypeResolver());
}

return $this->typeResolver;
Expand Down
118 changes: 118 additions & 0 deletions src/TypeResolver/CachingTypeResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

declare(strict_types=1);

/*
* This file is part of rekalogika/mapper package.
*
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/

namespace Rekalogika\Mapper\TypeResolver;

use Rekalogika\Mapper\Model\MixedType;
use Symfony\Component\PropertyInfo\Type;

class CachingTypeResolver implements TypeResolverInterface
{
public function __construct(
private TypeResolverInterface $decorated,
) {
/** @psalm-suppress PropertyTypeCoercion */
$this->typeStringCache = new \WeakMap();

/** @psalm-suppress PropertyTypeCoercion */
$this->simpleTypesCache = new \WeakMap();

/** @psalm-suppress PropertyTypeCoercion */
$this->isSimpleTypeCache = new \WeakMap();
}

// cheap. so we don't cache

public function guessTypeFromVariable(mixed $variable): Type
{
return $this->decorated->guessTypeFromVariable($variable);
}

// can be expensive in a loop. we cache using a weakmap

/**
* @var \WeakMap<Type|MixedType,string>
*/
private \WeakMap $typeStringCache;

public function getTypeString(Type|MixedType $type): string
{
if ($result = $this->typeStringCache[$type] ?? null) {
return $result;
}

$typeString = $this->decorated->getTypeString($type);
$this->typeStringCache->offsetSet($type, $typeString);

return $typeString;
}

// can be expensive in a loop. we cache using a weakmap

/**
* @var \WeakMap<Type,array<array-key,Type>>
*/
private \WeakMap $simpleTypesCache;

public function getSimpleTypes(Type $type): array
{
if ($result = $this->simpleTypesCache[$type] ?? null) {
return $result;
}

$simpleTypes = $this->decorated->getSimpleTypes($type);
$this->simpleTypesCache->offsetSet($type, $simpleTypes);

return $simpleTypes;
}

// can be expensive in a loop. we cache using a weakmap

/**
* @var \WeakMap<Type,bool>
*/
private \WeakMap $isSimpleTypeCache;

public function isSimpleType(Type $type): bool
{
if ($result = $this->isSimpleTypeCache[$type] ?? null) {
return $result;
}

$isSimpleType = $this->decorated->isSimpleType($type);
$this->isSimpleTypeCache->offsetSet($type, $isSimpleType);

return $isSimpleType;
}

// expensive, but impossible to cache using a weakmap, so we use an array

/**
* @var array<string,array<int,string>>
*/
private array $applicableTypeStringsCache = [];

public function getApplicableTypeStrings(Type|MixedType $type): array
{
$typeString = $this->getTypeString($type);

if (isset($this->applicableTypeStringsCache[$typeString])) {
return $this->applicableTypeStringsCache[$typeString];
}

$applicableTypeStrings = $this->decorated->getApplicableTypeStrings($type);
$this->applicableTypeStringsCache[$typeString] = $applicableTypeStrings;

return $applicableTypeStrings;
}
}
18 changes: 4 additions & 14 deletions src/TypeResolver/TypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,31 +74,21 @@ public function isSimpleType(Type $type): bool
/**
* Gets all the possible simple types from a Type
*
* @param Type|array<array-key,Type> $type
* @param Type $type
* @return array<array-key,Type>
*/
public function getSimpleTypes(Type|array $type): array
public function getSimpleTypes(Type $type): array
{
return TypeUtil::getSimpleTypes($type);
}

public function getApplicableTypeStrings(array|Type|MixedType $type): array
public function getApplicableTypeStrings(Type|MixedType $type): array
{
if ($type instanceof MixedType) {
$type = ['mixed'];
return $type;
}

if ($type instanceof Type) {
$type = [$type];
}

$typeStrings = [];

foreach ($type as $type) {
$typeStrings = array_merge($typeStrings, TypeUtil::getAllTypeStrings($type, true));
}

return $typeStrings;
return TypeUtil::getAllTypeStrings($type, true);
}
}
14 changes: 10 additions & 4 deletions src/TypeResolver/TypeResolverInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,21 @@ interface TypeResolverInterface
*/
public function guessTypeFromVariable(mixed $variable): Type;

/**
* Gets the string representation of a Type.
*
* @param Type|MixedType $type
* @return string
*/
public function getTypeString(Type|MixedType $type): string;

/**
* Gets all the possible simple types from a Type
*
* @param Type|array<array-key,Type> $type
* @param Type $type
* @return array<array-key,Type>
*/
public function getSimpleTypes(Type|array $type): array;
public function getSimpleTypes(Type $type): array;

/**
* Simple Type is a type that is not nullable, and does not have more
Expand All @@ -49,8 +55,8 @@ public function isSimpleType(Type $type): bool;
*
* Note: IteratorAggregate extends Traversable
*
* @param array<int,Type>|Type|MixedType $type
* @param Type|MixedType $type
* @return array<int,string>
*/
public function getApplicableTypeStrings(array|Type|MixedType $type): array;
public function getApplicableTypeStrings(Type|MixedType $type): array;
}
10 changes: 3 additions & 7 deletions src/Util/TypeUtil.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,17 +67,13 @@ public static function isSimpleType(Type $type): bool
/**
* Gets all the possible simple types from a Type
*
* @param Type|array<array-key,Type> $type
* @param Type $type
* @return array<array-key,Type>
*/
#[Friend(TypeResolver::class)]
public static function getSimpleTypes(Type|array $type, bool $withParents = false): array
public static function getSimpleTypes(Type $type, bool $withParents = false): array
{
if (!is_array($type)) {
$type = [$type];
}

return self::getTypePermutations($type, withParents: $withParents);
return self::getTypePermutations([$type], withParents: $withParents);
}

/**
Expand Down

0 comments on commit 85133ba

Please sign in to comment.