Skip to content

Commit

Permalink
perf: Use our PropertyAccessLite instead of Symfony's.
Browse files Browse the repository at this point in the history
  • Loading branch information
priyadi committed Jan 17, 2024
1 parent 9f85c41 commit 31bc1e1
Show file tree
Hide file tree
Showing 5 changed files with 335 additions and 8 deletions.
10 changes: 9 additions & 1 deletion config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Rekalogika\Mapper\MethodMapper\ClassMethodTransformer;
use Rekalogika\Mapper\MethodMapper\SubMapper;
use Rekalogika\Mapper\ObjectCache\ObjectCacheFactory;
use Rekalogika\Mapper\PropertyAccessLite\PropertyAccessLite;
use Rekalogika\Mapper\Transformer\ArrayToObjectTransformer;
use Rekalogika\Mapper\Transformer\CopyTransformer;
use Rekalogika\Mapper\Transformer\DateTimeTransformer;
Expand Down Expand Up @@ -138,7 +139,7 @@
$services
->set(ObjectToObjectTransformer::class)
->args([
'$propertyAccessor' => service('property_accessor'),
'$propertyAccessor' => service('rekalogika.mapper.property_access_lite'),
'$typeResolver' => service('rekalogika.mapper.type_resolver'),
'$objectMappingResolver' => service('rekalogika.mapper.object_mapping_resolver'),
])
Expand Down Expand Up @@ -174,6 +175,11 @@
])
->tag('kernel.cache_warmer');

# propertyaccess lite

$services
->set('rekalogika.mapper.property_access_lite', PropertyAccessLite::class);

# type resolver

$services
Expand All @@ -195,6 +201,8 @@
service('rekalogika.mapper.property_info'),
service('rekalogika.mapper.property_info'),
service('rekalogika.mapper.property_info'),
service('property_info.reflection_extractor'),
service('property_info.reflection_extractor'),
]);

$services
Expand Down
27 changes: 20 additions & 7 deletions src/MapperFactory/MapperFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use Rekalogika\Mapper\MethodMapper\SubMapper;
use Rekalogika\Mapper\ObjectCache\ObjectCacheFactory;
use Rekalogika\Mapper\ObjectCache\ObjectCacheFactoryInterface;
use Rekalogika\Mapper\PropertyAccessLite\PropertyAccessLite;
use Rekalogika\Mapper\Transformer\ArrayToObjectTransformer;
use Rekalogika\Mapper\Transformer\Contracts\TransformerInterface;
use Rekalogika\Mapper\Transformer\CopyTransformer;
Expand All @@ -49,16 +50,16 @@
use Rekalogika\Mapper\TypeResolver\TypeResolverInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Console\Application;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoCacheExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer;
Expand Down Expand Up @@ -114,7 +115,7 @@ public function __construct(
private array $additionalTransformers = [],
private ?ReflectionExtractor $reflectionExtractor = null,
private ?PhpStanExtractor $phpStanExtractor = null,
private ?PropertyAccessor $propertyAccessor = null,
private ?PropertyAccessorInterface $propertyAccessor = null,
private ?NormalizerInterface $normalizer = null,
private ?DenormalizerInterface $denormalizer = null,
?CacheItemPoolInterface $propertyInfoExtractorCache = null,
Expand Down Expand Up @@ -153,6 +154,16 @@ private function getPhpStanExtractor(): PropertyTypeExtractorInterface
return $this->phpStanExtractor;
}

private function getPropertyReadInfoExtractor(): PropertyReadInfoExtractorInterface
{
return $this->getReflectionExtractor();
}

private function getPropertyWriteInfoExtractor(): PropertyWriteInfoExtractorInterface
{
return $this->getReflectionExtractor();
}

private function getPropertyInfoExtractor(): PropertyInfoExtractorInterface&PropertyInitializableExtractorInterface
{
if ($this->propertyInfoExtractor === null) {
Expand Down Expand Up @@ -188,7 +199,7 @@ private function getPropertyInfoExtractor(): PropertyInfoExtractorInterface&Prop
private function getConcretePropertyAccessor(): PropertyAccessorInterface
{
if (null === $this->propertyAccessor) {
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
$this->propertyAccessor = new PropertyAccessLite();
}

return $this->propertyAccessor;
Expand Down Expand Up @@ -258,9 +269,9 @@ protected function getObjectToObjectTransformer(): TransformerInterface
{
if (null === $this->objectToObjectTransformer) {
$this->objectToObjectTransformer = new ObjectToObjectTransformer(
$this->getPropertyAccessor(),
$this->getTypeResolver(),
$this->getObjectMappingResolver(),
propertyAccessor: $this->getPropertyAccessor(),
typeResolver: $this->getTypeResolver(),
objectMappingResolver: $this->getObjectMappingResolver(),
);
}

Expand Down Expand Up @@ -395,6 +406,8 @@ protected function getObjectMappingResolver(): ObjectMappingResolverInterface
$this->getPropertyInfoExtractor(),
$this->getPropertyInfoExtractor(),
$this->getPropertyInfoExtractor(),
$this->getPropertyReadInfoExtractor(),
$this->getPropertyWriteInfoExtractor(),
);
}

Expand Down
146 changes: 146 additions & 0 deletions src/PropertyAccessLite/PropertyAccessLite.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?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\PropertyAccessLite;

use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\PropertyAccess\PropertyPathInterface;

final class PropertyAccessLite implements PropertyAccessorInterface
{
/**
* @param object|array<array-key,mixed> $objectOrArray
*/
public function getValue(
object|array $objectOrArray,
string|PropertyPathInterface $propertyPath,
): mixed {
assert(is_string($propertyPath));
assert(\is_object($objectOrArray));

$getter = 'get' . ucfirst($propertyPath);

try {
/** @psalm-suppress MixedMethodCall */
return $objectOrArray->{$getter}();
} catch (\Throwable $e) {
if (!\str_starts_with($e->getMessage(), 'Call to undefined method')) {
if (\str_starts_with($e->getMessage(), 'Typed property')) {
throw new UninitializedPropertyException(\sprintf(
'Property "%s" is not initialized in object "%s"',
$propertyPath,
\get_class($objectOrArray),
), 0, $e);
}

throw $e;
}
}

try {
return $objectOrArray->{$propertyPath};
// @phpstan-ignore-next-line
} catch (\Throwable $e) {
if (\str_starts_with($e->getMessage(), 'Typed property')) {
throw new UninitializedPropertyException(\sprintf(
'Property "%s" is not initialized in object "%s"',
$propertyPath,
\get_class($objectOrArray),
), 0, $e);
} elseif (\str_starts_with($e->getMessage(), 'Cannot access private property')) {
throw new NoSuchPropertyException(\sprintf(
'Property "%s" is not public in object "%s"',
$propertyPath,
\get_class($objectOrArray),
), 0, $e);
} elseif (\str_starts_with($e->getMessage(), 'Undefined property')) {
throw new NoSuchPropertyException(\sprintf(
'Property "%s" is not defined in object "%s"',
$propertyPath,
\get_class($objectOrArray),
), 0, $e);
}

throw $e;
}
}

/**
* @param object|array<array-key,mixed> $objectOrArray
*/
public function setValue(
object|array &$objectOrArray,
string|PropertyPathInterface $propertyPath,
mixed $value
): void {
assert(is_string($propertyPath));
assert(\is_object($objectOrArray));

$setter = 'set' . ucfirst($propertyPath);

try {
/** @psalm-suppress MixedMethodCall */
$objectOrArray->{$setter}($value);
return;
} catch (\Throwable $e) {
if (!\str_starts_with($e->getMessage(), 'Call to undefined method')) {
throw $e;
}
}

try {
if (!\property_exists($objectOrArray, $propertyPath)) {
throw new NoSuchPropertyException(\sprintf(
'Property "%s" is not defined in object "%s"',
$propertyPath,
\get_class($objectOrArray),
));
}
$objectOrArray->{$propertyPath} = $value;
} catch (NoSuchPropertyException $e) {
throw $e;
} catch (\Throwable $e) {
if (\str_starts_with($e->getMessage(), 'Cannot access private property')) {
throw new NoSuchPropertyException(\sprintf(
'Property "%s" is not public in object "%s"',
$propertyPath,
\get_class($objectOrArray),
), 0, $e);
}

throw $e;
}
}

/**
* @param object|array<array-key,mixed> $objectOrArray
*/
public function isWritable(
object|array $objectOrArray,
string|PropertyPathInterface $propertyPath
): bool {
throw new \RuntimeException('Not implemented');
}

/**
* @param object|array<array-key,mixed> $objectOrArray
*/
public function isReadable(
object|array $objectOrArray,
string|PropertyPathInterface $propertyPath
): bool {
throw new \RuntimeException('Not implemented');
}
}
59 changes: 59 additions & 0 deletions tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?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\Tests\Fixtures\AccessMethods;

class ObjectWithVariousAccessMethods
{
private string $privatePropertyWithGetterSetter = 'privateProperty';
// @phpstan-ignore-next-line
private string $privatePropertyWithoutGetterSetter = 'privatePropertyWithoutGetterSetter';
public string $publicPropertyWithGetterSetter = 'publicProperty';
public string $publicPropertyWithoutGetterSetter = 'publicPropertyWithoutGetterSetter';


public bool $publicPropertySetterAccessed = false;
public bool $publicPropertyGetterAccessed = false;

public string $unsetPublicProperty;

Check failure on line 28 in tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php

View workflow job for this annotation

GitHub Actions / Symfony 6.4.*, highest deps, PHP 8.2, ubuntu-latest

MissingConstructor

tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php:28:19: MissingConstructor: Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods has an uninitialized property Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods::$unsetPublicProperty, but no constructor (see https://psalm.dev/073)

Check failure on line 28 in tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php

View workflow job for this annotation

GitHub Actions / Symfony 6.4.*, lowest deps, PHP 8.2, ubuntu-latest

MissingConstructor

tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php:28:19: MissingConstructor: Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods has an uninitialized property Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods::$unsetPublicProperty, but no constructor (see https://psalm.dev/073)

Check failure on line 28 in tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php

View workflow job for this annotation

GitHub Actions / Symfony 7.*, highest deps, PHP 8.2, ubuntu-latest

MissingConstructor

tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php:28:19: MissingConstructor: Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods has an uninitialized property Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods::$unsetPublicProperty, but no constructor (see https://psalm.dev/073)

Check failure on line 28 in tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php

View workflow job for this annotation

GitHub Actions / Symfony 7.*, lowest deps, PHP 8.2, ubuntu-latest

MissingConstructor

tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php:28:19: MissingConstructor: Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods has an uninitialized property Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods::$unsetPublicProperty, but no constructor (see https://psalm.dev/073)

Check failure on line 28 in tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php

View workflow job for this annotation

GitHub Actions / Symfony 6.4.*, highest deps, PHP 8.3, ubuntu-latest

MissingConstructor

tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php:28:19: MissingConstructor: Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods has an uninitialized property Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods::$unsetPublicProperty, but no constructor (see https://psalm.dev/073)

Check failure on line 28 in tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php

View workflow job for this annotation

GitHub Actions / Symfony 6.4.*, lowest deps, PHP 8.3, ubuntu-latest

MissingConstructor

tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php:28:19: MissingConstructor: Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods has an uninitialized property Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods::$unsetPublicProperty, but no constructor (see https://psalm.dev/073)

Check failure on line 28 in tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php

View workflow job for this annotation

GitHub Actions / Symfony 7.*, highest deps, PHP 8.3, ubuntu-latest

MissingConstructor

tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php:28:19: MissingConstructor: Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods has an uninitialized property Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods::$unsetPublicProperty, but no constructor (see https://psalm.dev/073)

Check failure on line 28 in tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php

View workflow job for this annotation

GitHub Actions / Symfony 7.*, lowest deps, PHP 8.3, ubuntu-latest

MissingConstructor

tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php:28:19: MissingConstructor: Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods has an uninitialized property Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods::$unsetPublicProperty, but no constructor (see https://psalm.dev/073)

// @phpstan-ignore-next-line
private string $unsetPrivatePropertyWithGetter;

Check failure on line 31 in tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php

View workflow job for this annotation

GitHub Actions / Symfony 6.4.*, highest deps, PHP 8.2, ubuntu-latest

MissingConstructor

tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php:31:20: MissingConstructor: Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods has an uninitialized property Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods::$unsetPrivatePropertyWithGetter, but no constructor (see https://psalm.dev/073)

Check failure on line 31 in tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php

View workflow job for this annotation

GitHub Actions / Symfony 6.4.*, lowest deps, PHP 8.2, ubuntu-latest

MissingConstructor

tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php:31:20: MissingConstructor: Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods has an uninitialized property Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods::$unsetPrivatePropertyWithGetter, but no constructor (see https://psalm.dev/073)

Check failure on line 31 in tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php

View workflow job for this annotation

GitHub Actions / Symfony 7.*, highest deps, PHP 8.2, ubuntu-latest

MissingConstructor

tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php:31:20: MissingConstructor: Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods has an uninitialized property Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods::$unsetPrivatePropertyWithGetter, but no constructor (see https://psalm.dev/073)

Check failure on line 31 in tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php

View workflow job for this annotation

GitHub Actions / Symfony 7.*, lowest deps, PHP 8.2, ubuntu-latest

MissingConstructor

tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php:31:20: MissingConstructor: Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods has an uninitialized property Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods::$unsetPrivatePropertyWithGetter, but no constructor (see https://psalm.dev/073)

Check failure on line 31 in tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php

View workflow job for this annotation

GitHub Actions / Symfony 6.4.*, highest deps, PHP 8.3, ubuntu-latest

MissingConstructor

tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php:31:20: MissingConstructor: Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods has an uninitialized property Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods::$unsetPrivatePropertyWithGetter, but no constructor (see https://psalm.dev/073)

Check failure on line 31 in tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php

View workflow job for this annotation

GitHub Actions / Symfony 6.4.*, lowest deps, PHP 8.3, ubuntu-latest

MissingConstructor

tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php:31:20: MissingConstructor: Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods has an uninitialized property Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods::$unsetPrivatePropertyWithGetter, but no constructor (see https://psalm.dev/073)

Check failure on line 31 in tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php

View workflow job for this annotation

GitHub Actions / Symfony 7.*, highest deps, PHP 8.3, ubuntu-latest

MissingConstructor

tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php:31:20: MissingConstructor: Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods has an uninitialized property Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods::$unsetPrivatePropertyWithGetter, but no constructor (see https://psalm.dev/073)

Check failure on line 31 in tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php

View workflow job for this annotation

GitHub Actions / Symfony 7.*, lowest deps, PHP 8.3, ubuntu-latest

MissingConstructor

tests/Fixtures/AccessMethods/ObjectWithVariousAccessMethods.php:31:20: MissingConstructor: Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods has an uninitialized property Rekalogika\Mapper\Tests\Fixtures\AccessMethods\ObjectWithVariousAccessMethods::$unsetPrivatePropertyWithGetter, but no constructor (see https://psalm.dev/073)

public function getPrivateProperty(): string
{
return $this->privatePropertyWithGetterSetter;
}

public function setPrivateProperty(string $privateProperty): void
{
$this->privatePropertyWithGetterSetter = $privateProperty;
}

public function getPublicProperty(): string
{
$this->publicPropertyGetterAccessed = true;
return $this->publicPropertyWithGetterSetter;
}

public function setPublicProperty(string $publicProperty): void
{
$this->publicPropertySetterAccessed = true;
$this->publicPropertyWithGetterSetter = $publicProperty;
}

public function getUnsetPrivatePropertyWithGetter(): string
{
return $this->unsetPrivatePropertyWithGetter;
}
}
Loading

0 comments on commit 31bc1e1

Please sign in to comment.