Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions src/FrameworkBridge/Symfony/CacheWarmer/ProxyCacheWarmer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace OpenClassrooms\ServiceProxy\FrameworkBridge\Symfony\CacheWarmer;

use ProxyManager\Proxy\LazyLoadingInterface;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
use Symfony\Component\VarExporter\LazyObjectInterface;

final class ProxyCacheWarmer implements CacheWarmerInterface
{
/**
* @var iterable<object>
*/
private iterable $proxies;

/**
* @param iterable<object> $proxies
*/
public function __construct(iterable $proxies)
{
$this->proxies = $proxies;
}

/**
* {@inheritdoc}
*/
public function warmUp(string $cacheDir): array
{
foreach ($this->proxies as $proxy) {
if ($proxy instanceof LazyLoadingInterface && !$proxy->isProxyInitialized()) {
$proxy->initializeProxy();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add continue

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is LazyLoadingInterface doesn't exist

}

if (class_exists(LazyObjectInterface::class)
&& $proxy instanceof LazyObjectInterface
&& !$proxy->isLazyObjectInitialized()) {
$proxy->initializeLazyObject();
}
}

return [];
}

/**
* {@inheritdoc}
*/
public function isOptional()
{
return true;
}
}
9 changes: 9 additions & 0 deletions src/FrameworkBridge/Symfony/Config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

declare(strict_types=1);

use OpenClassrooms\ServiceProxy\FrameworkBridge\Symfony\CacheWarmer\ProxyCacheWarmer;
use OpenClassrooms\ServiceProxy\FrameworkBridge\Symfony\Messenger\Transport\Serialization\MessageSerializer;
use OpenClassrooms\ServiceProxy\FrameworkBridge\Symfony\Subscriber\ServiceProxySubscriber;
use OpenClassrooms\ServiceProxy\Handler\Impl\Cache\SymfonyCacheHandler;
Expand Down Expand Up @@ -74,4 +75,12 @@
service('annotation_reader')->nullOnInvalid(),
])
->tag('kernel.event_subscriber');

$services->set(ProxyCacheWarmer::class)
->args([
tagged_iterator('openclassrooms.service_proxy'),
])
->tag('kernel.cache_warmer', [
'priority' => 128,
]);
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
use Symfony\Component\DependencyInjection\Compiler\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

final class ServiceProxyPass implements CompilerPassInterface
Expand All @@ -30,23 +29,28 @@ private function buildServiceProxies(): void
$serviceProxyIds = [];
$taggedServices = $this->container->findTaggedServiceIds('openclassrooms.service_proxy');
foreach ($taggedServices as $taggedServiceId => $tagParameters) {
$this->buildServiceProxyFactoryDefinition($taggedServiceId);
$this->overrideServiceDefinition($taggedServiceId);
$serviceProxyIds[] = $taggedServiceId;
$this->compiler->log($this, "Add proxy for {$taggedServiceId} service.");
$this->compiler->log($this, "Override service definition for {$taggedServiceId} service.");
}

$this->container->setParameter('openclassrooms.service_proxy.service_proxy_ids', $serviceProxyIds);
}

private function buildServiceProxyFactoryDefinition(string $taggedServiceName): void
private function overrideServiceDefinition(string $taggedServiceName): void
{
$definition = $this->container->findDefinition($taggedServiceName);
$factoryDefinition = new Definition($definition->getClass());
$factoryDefinition->setFactory([new Reference(ProxyFactory::class), 'createProxy']);
$factoryDefinition->setArguments([$definition]);
$this->container->setDefinition($taggedServiceName, $factoryDefinition);
$factoryDefinition->setPublic($definition->isPublic());
$factoryDefinition->setLazy($definition->isLazy());
$factoryDefinition->setTags($definition->getTags());

if ($definition->getFactory() !== null) {
$this->compiler->log($this, "Service {$taggedServiceName} is not compatible with service proxy");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make an error


throw new \RuntimeException(
"Unable to override {$taggedServiceName}, remove the factory definition or the Interceptable interface."
);
}

$definition->setFactory([new Reference(ProxyFactory::class), 'createInstance']);

$definition->setArguments([$definition->getClass(), ...$definition->getArguments()]);
}
}
15 changes: 3 additions & 12 deletions src/FrameworkBridge/Symfony/Subscriber/ServiceProxySubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use OpenClassrooms\ServiceProxy\Interceptor\Contract\StartUpInterceptor;
use OpenClassrooms\ServiceProxy\Model\Request\Instance;
use OpenClassrooms\ServiceProxy\Model\Request\Method;
use ProxyManager\Proxy\ValueHolderInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;

Expand Down Expand Up @@ -73,24 +72,16 @@ public function startUp(RequestEvent $event): void
}

/**
* @return iterable<Instance>
* @return iterable<Instance<object>>
*/
public function getInstances(): iterable
{
foreach ($this->proxies as $proxy) {
$object = $proxy;
if ($proxy instanceof ValueHolderInterface) {
$object = $proxy->getWrappedValueHolderValue();
if ($object === null) {
continue;
}
}
$instanceRef = new \ReflectionObject($object);
$methods = $instanceRef->getMethods(\ReflectionMethod::IS_PUBLIC);
$instanceRef = new \ReflectionClass($proxy);
$methods = $instanceRef->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED);
foreach ($methods as $methodRef) {
$methodAnnotations = $this->annotationReader->getMethodAnnotations($methodRef);
$instance = Instance::create(
$proxy,
$instanceRef,
Method::create(
$methodRef,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

declare(strict_types=1);

namespace OpenClassrooms\ServiceProxy\Generator\AccessInterceptorGenerator;

use Laminas\Code\Generator\ClassGenerator;
use Laminas\Code\Generator\MethodGenerator;
use Laminas\Code\Reflection\MethodReflection;
use OpenClassrooms\ServiceProxy\Generator\AccessInterceptorGenerator\Method\InterceptedMethod;
use OpenClassrooms\ServiceProxy\Generator\AccessInterceptorGenerator\Method\SetMethodPrefixInterceptors;
use OpenClassrooms\ServiceProxy\Generator\AccessInterceptorGenerator\Method\SetMethodSuffixInterceptors;
use OpenClassrooms\ServiceProxy\Proxy\AccessInterceptorsInterface;
use ProxyManager\Exception\InvalidProxiedClassException;
use ProxyManager\Generator\Util\ClassGeneratorUtils;
use ProxyManager\ProxyGenerator\AccessInterceptor\PropertyGenerator\MethodPrefixInterceptors;
use ProxyManager\ProxyGenerator\AccessInterceptor\PropertyGenerator\MethodSuffixInterceptors;
use ProxyManager\ProxyGenerator\Assertion\CanProxyAssertion;
use ProxyManager\ProxyGenerator\ProxyGeneratorInterface;

class AccessInterceptorGenerator implements ProxyGeneratorInterface
{
/**
* {@inheritDoc}
*
* @param array{'methods'?: array<string>} $proxyOptions
*
* @throws \InvalidArgumentException
* @throws InvalidProxiedClassException
*/
public function generate(\ReflectionClass $originalClass, ClassGenerator $classGenerator, array $proxyOptions = [])
{
if (!\array_key_exists('methods', $proxyOptions)) {
throw new \InvalidArgumentException(sprintf('Missing methods options for %s.', __CLASS__));
}

CanProxyAssertion::assertClassCanBeProxied($originalClass, false);

$classGenerator->setExtendedClass($originalClass->getName());
$classGenerator->setImplementedInterfaces([AccessInterceptorsInterface::class]);
$classGenerator->addPropertyFromGenerator($prefixInterceptors = new MethodPrefixInterceptors());
$classGenerator->addPropertyFromGenerator($suffixInterceptors = new MethodSuffixInterceptors());

array_map(
static function (MethodGenerator $generatedMethod) use ($originalClass, $classGenerator): void {
ClassGeneratorUtils::addMethodIfNotFinal($originalClass, $classGenerator, $generatedMethod);
},
array_merge(
array_map(
$this->buildMethodInterceptor($prefixInterceptors, $suffixInterceptors),
array_map(
static fn (string $method) => $originalClass->getMethod($method),
$proxyOptions['methods']
)
),
[
new SetMethodPrefixInterceptors($prefixInterceptors),
new SetMethodSuffixInterceptors($suffixInterceptors),
]
)
);
}

private function buildMethodInterceptor(
MethodPrefixInterceptors $prefixInterceptors,
MethodSuffixInterceptors $suffixInterceptors
): callable {
return static function (\ReflectionMethod $method) use (
$prefixInterceptors,
$suffixInterceptors
): InterceptedMethod {
return InterceptedMethod::generateMethod(
new MethodReflection($method->getDeclaringClass()->getName(), $method->getName()),
$prefixInterceptors,
$suffixInterceptors
);
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace OpenClassrooms\ServiceProxy\Generator\AccessInterceptorGenerator\Factory;

use OpenClassrooms\ServiceProxy\Generator\AccessInterceptorGenerator\AccessInterceptorGenerator;
use ProxyManager\Configuration;
use ProxyManager\Factory\AbstractBaseFactory;

class AccessInterceptorFactory extends AbstractBaseFactory
{
private AccessInterceptorGenerator $generator;

public function __construct(?Configuration $configuration = null)
{
parent::__construct($configuration);

$this->generator = new AccessInterceptorGenerator();
}

/**
* @template T of object
*
* @param class-string<T> $class
* @param array<mixed> $args
* @param array<string, \Closure> $prefixInterceptors an array (indexed by method name) of interceptor closures to be called
* before method logic is executed
* @param array<string, \Closure> $suffixInterceptors an array (indexed by method name) of interceptor closures to be called
* after method logic is executed
*
* @return T
*/
public function createInstance(
string $class,
array $args,
array $prefixInterceptors = [],
array $suffixInterceptors = []
): object {
$methods = array_merge(
array_keys($prefixInterceptors),
array_keys($suffixInterceptors)
);
$methods = array_unique($methods);

$proxyClassName = $this->generateProxy($class, [
'methods' => $methods,
]);

$instance = new $proxyClassName(...$args);

$instance->setPrefixInterceptors($prefixInterceptors);
$instance->setSuffixInterceptors($suffixInterceptors);

return $instance;
}

public function getGenerator(): AccessInterceptorGenerator
{
return $this->generator;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace OpenClassrooms\ServiceProxy\Generator\Method;
namespace OpenClassrooms\ServiceProxy\Generator\AccessInterceptorGenerator\Method;

use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\PropertyGenerator;
Expand All @@ -20,7 +20,6 @@ final class InterceptedMethod extends MethodGenerator
*/
public static function generateMethod(
MethodReflection $originalMethod,
PropertyGenerator $valueHolderProperty,
PropertyGenerator $prefixInterceptors,
PropertyGenerator $suffixInterceptors
): self {
Expand All @@ -32,10 +31,9 @@ public static function generateMethod(
}

$method->setBody(InterceptorGenerator::createInterceptedMethodBody(
'$returnValue = $this->' . $valueHolderProperty->getName() . '->'
'$returnValue = parent::'
. $originalMethod->getName() . '(' . implode(', ', $forwardedParams) . ');',
$method,
$valueHolderProperty,
$prefixInterceptors,
$suffixInterceptors,
$originalMethod
Expand Down
Loading