From b959fa77371f9004d38f0c78f3db430cc3eb3f9e Mon Sep 17 00:00:00 2001 From: Priyadi Iman Nurcahyo <1102197+priyadi@users.noreply.github.com> Date: Fri, 11 Oct 2024 18:09:55 +0700 Subject: [PATCH] perf: proxy warming (#221) --- CHANGELOG.md | 1 + config/services.php | 2 + .../WarmableProxyFactoryInterface.php | 22 ++++++++ .../WarmableProxyRegistryInterface.php | 19 +++++++ src/MapperFactory.php | 4 +- src/Proxy/Implementation/ProxyFactory.php | 27 +++++++++- src/Proxy/Implementation/ProxyRegistry.php | 54 +++++++++++++++++-- .../ObjectToStringTransformer.php | 2 +- .../CachingObjectToObjectMetadataFactory.php | 9 +++- 9 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 src/CacheWarmer/WarmableProxyFactoryInterface.php create mode 100644 src/CacheWarmer/WarmableProxyRegistryInterface.php diff --git a/CHANGELOG.md b/CHANGELOG.md index f7e8964a..69811b1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 1.10.1 * refactor: spin off `resolveTargetClass()` to separate class +* perf: proxy warming ## 1.10.0 diff --git a/config/services.php b/config/services.php index 6575e332..f1a0377b 100644 --- a/config/services.php +++ b/config/services.php @@ -340,6 +340,7 @@ ->args([ service('.inner'), service($createCache($services, 'object_to_object_metadata_factory')), + service('rekalogika.mapper.proxy.factory'), param('kernel.debug'), ]); @@ -473,6 +474,7 @@ ->set('rekalogika.mapper.proxy.registry', ProxyRegistry::class) ->args([ '$proxyDirectory' => '%kernel.cache_dir%/rekalogika-mapper/proxy', + '$preWarmedProxyDirectory' => '%kernel.build_dir%/rekalogika-mapper/pre-warmed-proxy', ]); $services diff --git a/src/CacheWarmer/WarmableProxyFactoryInterface.php b/src/CacheWarmer/WarmableProxyFactoryInterface.php new file mode 100644 index 00000000..910a6c2d --- /dev/null +++ b/src/CacheWarmer/WarmableProxyFactoryInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Mapper\CacheWarmer; + +interface WarmableProxyFactoryInterface +{ + /** + * @param class-string $class + */ + public function warmingCreateProxy(string $class): void; +} diff --git a/src/CacheWarmer/WarmableProxyRegistryInterface.php b/src/CacheWarmer/WarmableProxyRegistryInterface.php new file mode 100644 index 00000000..02e7b9ab --- /dev/null +++ b/src/CacheWarmer/WarmableProxyRegistryInterface.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Mapper\CacheWarmer; + +interface WarmableProxyRegistryInterface +{ + public function warmingRegisterProxy(string $class, string $sourceCode): void; +} diff --git a/src/MapperFactory.php b/src/MapperFactory.php index ce371a38..109b1965 100644 --- a/src/MapperFactory.php +++ b/src/MapperFactory.php @@ -869,7 +869,9 @@ protected function getProxyGenerator(): ProxyGeneratorInterface protected function getProxyRegistry(): ProxyRegistryInterface { if (null === $this->proxyRegistry) { - $this->proxyRegistry = new ProxyRegistry('/tmp/rekalogika-mapper'); + $this->proxyRegistry = new ProxyRegistry( + proxyDirectory: '/tmp/rekalogika-mapper', + ); } return $this->proxyRegistry; diff --git a/src/Proxy/Implementation/ProxyFactory.php b/src/Proxy/Implementation/ProxyFactory.php index f240a1cb..bf92a13f 100644 --- a/src/Proxy/Implementation/ProxyFactory.php +++ b/src/Proxy/Implementation/ProxyFactory.php @@ -13,6 +13,8 @@ namespace Rekalogika\Mapper\Proxy\Implementation; +use Rekalogika\Mapper\CacheWarmer\WarmableProxyFactoryInterface; +use Rekalogika\Mapper\CacheWarmer\WarmableProxyRegistryInterface; use Rekalogika\Mapper\Exception\LogicException; use Rekalogika\Mapper\Proxy\ProxyFactoryInterface; use Rekalogika\Mapper\Proxy\ProxyGeneratorInterface; @@ -23,13 +25,36 @@ /** * @internal */ -final readonly class ProxyFactory implements ProxyFactoryInterface +final readonly class ProxyFactory implements + ProxyFactoryInterface, + WarmableProxyFactoryInterface { public function __construct( private ProxyRegistryInterface $proxyRegistry, private ProxyGeneratorInterface $proxyGenerator, ) {} + /** + * @param class-string $class + */ + public function warmingCreateProxy(string $class): void + { + $targetProxyClass = ProxyNamer::generateProxyClassName($class); + + $sourceCode = $this->proxyGenerator + ->generateProxyCode($class, $targetProxyClass); + + $proxyRegistry = $this->proxyRegistry; + + if (!$proxyRegistry instanceof WarmableProxyRegistryInterface) { + throw new LogicException( + 'The proxy registry must implement WarmingProxyRegistryInterface.', + ); + } + + $proxyRegistry->warmingRegisterProxy($targetProxyClass, $sourceCode); + } + /** * @template T of object * @param class-string $class diff --git a/src/Proxy/Implementation/ProxyRegistry.php b/src/Proxy/Implementation/ProxyRegistry.php index a342e2c0..f62a0128 100644 --- a/src/Proxy/Implementation/ProxyRegistry.php +++ b/src/Proxy/Implementation/ProxyRegistry.php @@ -13,19 +13,25 @@ namespace Rekalogika\Mapper\Proxy\Implementation; +use Rekalogika\Mapper\CacheWarmer\WarmableProxyRegistryInterface; +use Rekalogika\Mapper\Exception\LogicException; use Rekalogika\Mapper\Proxy\ProxyAutoloaderInterface; use Rekalogika\Mapper\Proxy\ProxyRegistryInterface; /** * @internal */ -final class ProxyRegistry implements ProxyRegistryInterface, ProxyAutoloaderInterface +final class ProxyRegistry implements + ProxyRegistryInterface, + ProxyAutoloaderInterface, + WarmableProxyRegistryInterface { /** @var ?\Closure(string): void */ private ?\Closure $autoloader = null; public function __construct( private readonly string $proxyDirectory, + private readonly ?string $preWarmedProxyDirectory = null, ) { // ensure directory exists if (!is_dir($this->proxyDirectory)) { @@ -33,12 +39,35 @@ public function __construct( } } + public function warmingRegisterProxy(string $class, string $sourceCode): void + { + $preWarmedProxyDirectory = $this->preWarmedProxyDirectory; + + if (null === $preWarmedProxyDirectory) { + throw new LogicException('Pre-warmed proxy directory is not set.'); + } + + if (!is_dir($preWarmedProxyDirectory)) { + mkdir($preWarmedProxyDirectory, 0755, true); + } + + $this->doRegisterProxy($class, $sourceCode, $preWarmedProxyDirectory); + } + #[\Override] public function registerProxy(string $class, string $sourceCode): void { + $this->doRegisterProxy($class, $sourceCode, $this->proxyDirectory); + } + + private function doRegisterProxy( + string $class, + string $sourceCode, + string $directory, + ): void { $proxyFile = \sprintf( '%s/%s', - $this->proxyDirectory, + $directory, self::getProxyFileName($class), ); @@ -58,12 +87,29 @@ public function registerAutoloader(): void } $proxyDirectory = $this->proxyDirectory; + $preWarmedProxyDirectory = $this->preWarmedProxyDirectory; + + $this->autoloader = static function (string $class) use ($proxyDirectory, $preWarmedProxyDirectory): void { + $proxyFileName = self::getProxyFileName($class); + + if ($preWarmedProxyDirectory !== null) { + $preWarmedProxyFile = \sprintf( + '%s/%s', + $preWarmedProxyDirectory, + $proxyFileName, + ); + + if (file_exists($preWarmedProxyFile)) { + require $preWarmedProxyFile; + + return; + } + } - $this->autoloader = static function (string $class) use ($proxyDirectory): void { $proxyFile = \sprintf( '%s/%s', $proxyDirectory, - self::getProxyFileName($class), + $proxyFileName, ); if (file_exists($proxyFile)) { diff --git a/src/Transformer/Implementation/ObjectToStringTransformer.php b/src/Transformer/Implementation/ObjectToStringTransformer.php index 8e62cefd..ddb14139 100644 --- a/src/Transformer/Implementation/ObjectToStringTransformer.php +++ b/src/Transformer/Implementation/ObjectToStringTransformer.php @@ -42,7 +42,7 @@ public function transform( } elseif (TypeCheck::isFloat($targetType)) { return (float) (string) $source; } elseif (TypeCheck::isBool($targetType)) { - return (bool) (string) $source; + return (bool) (string) $source; } else { return (string) $source; } diff --git a/src/Transformer/ObjectToObjectMetadata/Implementation/CachingObjectToObjectMetadataFactory.php b/src/Transformer/ObjectToObjectMetadata/Implementation/CachingObjectToObjectMetadataFactory.php index 86132429..60eb0b5b 100644 --- a/src/Transformer/ObjectToObjectMetadata/Implementation/CachingObjectToObjectMetadataFactory.php +++ b/src/Transformer/ObjectToObjectMetadata/Implementation/CachingObjectToObjectMetadataFactory.php @@ -16,6 +16,7 @@ use Psr\Cache\CacheItemPoolInterface; use Rekalogika\Mapper\CacheWarmer\WarmableCacheInterface; use Rekalogika\Mapper\CacheWarmer\WarmableObjectToObjectMetadataFactoryInterface; +use Rekalogika\Mapper\CacheWarmer\WarmableProxyFactoryInterface; use Rekalogika\Mapper\Exception\RuntimeException; use Rekalogika\Mapper\Transformer\ObjectToObjectMetadata\ObjectToObjectMetadata; use Rekalogika\Mapper\Transformer\ObjectToObjectMetadata\ObjectToObjectMetadataFactoryInterface; @@ -36,6 +37,7 @@ final class CachingObjectToObjectMetadataFactory implements public function __construct( private readonly ObjectToObjectMetadataFactoryInterface $decorated, private readonly CacheItemPoolInterface $cacheItemPool, + private readonly WarmableProxyFactoryInterface $proxyFactory, private readonly bool $debug, ) {} @@ -137,13 +139,16 @@ public function warmingCreateObjectToObjectMetadata( string $sourceClass, string $targetClass, ): ObjectToObjectMetadata { - $result = $this->decorated-> - createObjectToObjectMetadata($sourceClass, $targetClass); + $result = $this->decorated->createObjectToObjectMetadata($sourceClass, $targetClass); if (!$this->cacheItemPool instanceof WarmableCacheInterface) { return $result; } + // warm proxy + $this->proxyFactory->warmingCreateProxy($result->getTargetClass()); + + // warm cache $cacheKey = hash('xxh128', $sourceClass . $targetClass); $cacheItem = $this->cacheItemPool->getWarmedUpItem($cacheKey); $cacheItem->set($result);