Skip to content

Commit

Permalink
perf: proxy warming (#221)
Browse files Browse the repository at this point in the history
  • Loading branch information
priyadi authored Oct 11, 2024
1 parent a4cfdc5 commit b959fa7
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 1.10.1

* refactor: spin off `resolveTargetClass()` to separate class
* perf: proxy warming

## 1.10.0

Expand Down
2 changes: 2 additions & 0 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@
->args([
service('.inner'),
service($createCache($services, 'object_to_object_metadata_factory')),
service('rekalogika.mapper.proxy.factory'),
param('kernel.debug'),
]);

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

interface WarmableProxyFactoryInterface
{
/**
* @param class-string $class
*/
public function warmingCreateProxy(string $class): void;
}
19 changes: 19 additions & 0 deletions src/CacheWarmer/WarmableProxyRegistryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?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\CacheWarmer;

interface WarmableProxyRegistryInterface
{
public function warmingRegisterProxy(string $class, string $sourceCode): void;
}
4 changes: 3 additions & 1 deletion src/MapperFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
27 changes: 26 additions & 1 deletion src/Proxy/Implementation/ProxyFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<T> $class
Expand Down
54 changes: 50 additions & 4 deletions src/Proxy/Implementation/ProxyRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,61 @@

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)) {
mkdir($this->proxyDirectory, 0755, true);
}
}

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),
);

Expand All @@ -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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
) {}

Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit b959fa7

Please sign in to comment.