diff --git a/src/DependencyInjection/Compiler/MemoizeProxyCreatorCompilerPass.php b/src/DependencyInjection/Compiler/MemoizeProxyCreatorCompilerPass.php index 1cb0df9..e3ca7ca 100644 --- a/src/DependencyInjection/Compiler/MemoizeProxyCreatorCompilerPass.php +++ b/src/DependencyInjection/Compiler/MemoizeProxyCreatorCompilerPass.php @@ -13,6 +13,7 @@ use ReflectionParameter; use ReflectionType; use ReflectionUnionType; +use Rikudou\MemoizeBundle\Attribute\Memoizable; use Rikudou\MemoizeBundle\Attribute\Memoize; use Rikudou\MemoizeBundle\Attribute\NoMemoize; use Rikudou\MemoizeBundle\Cache\InMemoryCachePool; @@ -27,17 +28,46 @@ final class MemoizeProxyCreatorCompilerPass implements CompilerPassInterface { private ContainerBuilder $container; + /** + * @var array}> + */ + private array $additionalServicesConfig = []; + public function process(ContainerBuilder $container): void { if (!$container->getParameter('rikudou.memoize.enabled')) { return; } $this->container = $container; + + assert(is_array($container->getParameter('rikudou.internal.memoize.additional_services'))); + + /** @var array}> $additionalServices */ + $additionalServices = array_map(function (array $service) use ($container): array { + $definition = $container->getDefinition($service['service_id']); + if (!$class = $definition->getClass()) { + throw new RuntimeException("There is no class for service '{$service['service_id']}'"); + } + $service['class_name'] = $class; + + return $service; + }, $container->getParameter('rikudou.internal.memoize.additional_services')); + + $services = []; + foreach ($additionalServices as $additionalService) { + foreach ($additionalService['methods'] as $key => $method) { + $additionalService['methods'][$method['name']] = $method; + unset($additionalService['methods'][$key]); + } + $this->additionalServicesConfig[$additionalService['class_name']] = $additionalService; + $services[] = $additionalService['service_id']; + } + $this->cleanupDirectory(); $cacheServiceName = $container->getParameter('rikudou.memoize.cache_service'); assert(is_string($cacheServiceName)); - $services = array_keys($container->findTaggedServiceIds('rikudou.memoize.memoizable_service')); + $services = [...$services, ...array_keys($container->findTaggedServiceIds('rikudou.memoize.memoizable_service'))]; foreach ($services as $service) { $definition = $container->getDefinition($service); @@ -239,6 +269,7 @@ private function shouldMemoize(ReflectionMethod $method): bool if ($method->getReturnType() !== null && $this->getType($method->getReturnType()) === 'never') { return false; } + return ( $this->getAttribute($method, Memoize::class) !== null || $this->getAttribute($method->getDeclaringClass(), Memoize::class) !== null @@ -257,11 +288,44 @@ private function shouldMemoize(ReflectionMethod $method): bool private function getAttribute(ReflectionMethod|ReflectionClass $target, string $attribute): ?object { $attributes = $target->getAttributes($attribute); - if (!count($attributes)) { - return null; + if (count($attributes)) { + return $attributes[array_key_first($attributes)]->newInstance(); + } + + // todo make this better + if ($attribute === Memoize::class) { + $class = $target instanceof ReflectionClass ? $target->getName() : $target->getDeclaringClass()->getName(); + if (!isset($this->additionalServicesConfig[$class])) { + return null; + } + $config = $this->additionalServicesConfig[$class]; + + if ($target instanceof ReflectionClass) { + if (count($config['methods'])) { + return null; + } + + $seconds = $config['memoize_seconds']; + } else { + if (!isset($config['methods'][$target->getName()])) { + return null; + } + + $seconds = $config['methods'][$target->getName()]['memoize_seconds'] ?? $config['memoize_seconds']; + } + + // @phpstan-ignore-next-line + return new Memoize($seconds); + } elseif ($attribute === Memoizable::class) { + assert($target instanceof ReflectionClass); + + // @phpstan-ignore-next-line + return isset($this->additionalServicesConfig[$target->getName()]) + ? new Memoizable() + : null; } - return $attributes[array_key_first($attributes)]->newInstance(); + return null; } /** diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index c439744..3ed93d9 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -27,6 +27,34 @@ public function getConfigTreeBuilder(): TreeBuilder ->info('The default cache service to use. If default_memoize_seconds is set to -1 this setting is ignored and internal service is used.') ->defaultValue('cache.app') ->end() + ->arrayNode('memoize_services') + ->info('List additional services that you want to be memoized.') + ->arrayPrototype() + ->children() + ->scalarNode('service_id') + ->info('The service ID.') + ->end() + ->scalarNode('memoize_seconds') + ->info('The seconds to memoize the service for. Defaults to null which means the value of default_memoize_seconds.') + ->defaultNull() + ->end() + ->arrayNode('methods') + ->info('List of methods to memoize. Leave empty to memoize all of them.') + ->arrayPrototype() + ->children() + ->scalarNode('name') + ->info('The method name.') + ->end() + ->scalarNode('memoize_seconds') + ->info('The seconds to memoize the method for. Defaults to null which means the value of memoize_seconds from the parent service.') + ->defaultNull() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() ->end(); return $treeBuilder; diff --git a/src/DependencyInjection/RikudouMemoizeExtension.php b/src/DependencyInjection/RikudouMemoizeExtension.php index c47246e..abe354c 100644 --- a/src/DependencyInjection/RikudouMemoizeExtension.php +++ b/src/DependencyInjection/RikudouMemoizeExtension.php @@ -36,5 +36,28 @@ static function (ChildDefinition $definition): void { $container->setParameter('rikudou.memoize.cache_service', $configs['cache_service']); $container->setParameter('rikudou.memoize.default_memoize_seconds', $configs['default_memoize_seconds']); $container->setParameter('rikudou.memoize.enabled', $configs['enabled']); + $container->setParameter( + 'rikudou.internal.memoize.additional_services', + $this->filterAdditionalServices($configs['memoize_services'], $configs['default_memoize_seconds']), + ); + } + + /** + * @param array}> $services + * + * @return array}> + */ + private function filterAdditionalServices(array $services, int $defaultMemoizeSeconds): array + { + return array_map(function (array $service) use ($defaultMemoizeSeconds): array { + $service['memoize_seconds'] ??= $defaultMemoizeSeconds; + $service['methods'] = array_map(function (array $method) use ($service): array { + $method['memoize_seconds'] ??= $service['memoize_seconds']; + + return $method; + }, array_filter($service['methods'], fn (array $method) => $method['name'] !== null)); + + return $service; + }, array_filter($services, fn (array $service) => $service['service_id'] !== null)); } }