Skip to content

Commit

Permalink
Merge d46c72a into 07fc209
Browse files Browse the repository at this point in the history
  • Loading branch information
Yoann-TYT committed Dec 16, 2020
2 parents 07fc209 + d46c72a commit e13f910
Show file tree
Hide file tree
Showing 14 changed files with 425 additions and 4 deletions.
13 changes: 13 additions & 0 deletions src/Configuration/InvalidatePath.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,26 @@
/**
* @Annotation
*/
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
class InvalidatePath extends ConfigurationAnnotation
{
/**
* @var array
*/
private $paths;

public function __construct(
$data = []
) {
$values = [];
if (is_string($data)) {
$values['value'] = $data;
} else {
$values = $data;
}

parent::__construct($values);
}
/**
* Handle path given without explicit key.
*
Expand Down
17 changes: 17 additions & 0 deletions src/Configuration/InvalidateRoute.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
/**
* @Annotation
*/
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
class InvalidateRoute extends ConfigurationAnnotation
{
/**
Expand All @@ -30,6 +31,22 @@ class InvalidateRoute extends ConfigurationAnnotation
*/
private $params;

public function __construct(
$data = [],
$params = []
) {
$values = [];
if (is_string($data)) {
$values['value'] = $data;
} else {
$values = $data;
}

$values['params'] = $values['params'] ?? $params;

parent::__construct($values);
}

/**
* Handle route name given without explicit key.
*
Expand Down
17 changes: 17 additions & 0 deletions src/Configuration/Tag.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,29 @@
/**
* @Annotation
*/
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
class Tag extends ConfigurationAnnotation
{
private $tags;

private $expression;

public function __construct(
$data = [],
$expression = null
) {
$values = [];
if (is_string($data)) {
$values['value'] = $data;
} else {
$values = $data;
}

$values['expression'] = $values['expression'] ?? $expression;

parent::__construct($values);
}

/**
* Handle tags given without explicit key.
*
Expand Down
4 changes: 4 additions & 0 deletions src/DependencyInjection/FOSHttpCacheExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ public function load(array $configs, ContainerBuilder $container)

$loader->load('flash_message.xml');
}

if (method_exists(\ReflectionProperty::class, 'getAttributes')) {
$loader->load('catch_attributes.xml');
}
}

private function loadCacheable(ContainerBuilder $container, array $config)
Expand Down
81 changes: 81 additions & 0 deletions src/EventListener/CatchAttributesListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php


namespace FOS\HttpCacheBundle\EventListener;

use FOS\HttpCacheBundle\CacheManager;
use FOS\HttpCacheBundle\Http\RuleMatcherInterface;
use FOS\HttpCacheBundle\Http\SymfonyResponseTagger;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface;
use Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpKernel\KernelEvents;

if (Kernel::MAJOR_VERSION >= 5) {
class_alias(RequestEvent::class, 'FOS\HttpCacheBundle\EventListener\AttributeRequestEvent');
} else {
class_alias(GetResponseEvent::class, 'FOS\HttpCacheBundle\EventListener\AttributeRequestEvent');
}

class CatchAttributesListener implements EventSubscriberInterface
{
/**
* @var ControllerResolver
*/
private $controllerResolver;

public function __construct(ControllerResolver $controllerResolver) {
$this->controllerResolver = $controllerResolver;
}

public function onKernelRequest(AttributeRequestEvent $event)
{
$request = $event->getRequest();

if (
method_exists(\ReflectionProperty::class, 'getAttributes') &&
$controller = $this->controllerResolver->getController($request)
) {
$class = new \ReflectionClass($controller[0]);
$method = $class->getMethod($controller[1]);
$attributes = [];
$addAttributes = function($instance) use (&$attributes) {
if (
$instance instanceof ConfigurationInterface &&
in_array(
$instance->getAliasName(), [
'tag', 'invalidate_path', 'invalidate_route'
])
) {
$attributes['_'.$instance->getAliasName()][] = $instance;
}
};

foreach ($class->getAttributes() as $classAttribute) {
$addAttributes($classAttribute->newInstance());
}
foreach ($method->getAttributes() as $methodAttribute) {
$addAttributes($methodAttribute->newInstance());

}

foreach($attributes as $key => $attr) {
$request->attributes->set(
$key,
array_merge($attr, $request->attributes->get($key, []))
);
}
}
}

public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => 'onKernelRequest',
];
}
}
16 changes: 16 additions & 0 deletions src/Resources/config/catch_attributes.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="fos_http_cache.controller_resolver" class="Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver">
<argument type="service" id="service_container" />
</service>

<service id="fos_http_cache.event_listener.catch_attributes" class="FOS\HttpCacheBundle\EventListener\CatchAttributesListener">
<argument type="service" id="fos_http_cache.controller_resolver" />
<tag name="kernel.event_subscriber" />
</service>
</services>
</container>
84 changes: 84 additions & 0 deletions tests/Functional/EventListener/InvalidationListenerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,90 @@ public function testErrorIsNotInvalidated()
$client->request('POST', '/invalidate/error');
}

/**
* @requires PHP 8.0
*/
public function testInvalidateRouteWithPHPAttributes()
{
$client = static::createClient();

$mock = \Mockery::mock(CacheManager::class);
$mock->shouldReceive('supports')
->zeroOrMoreTimes()
->andReturnTrue()
;
$mock->shouldReceive('invalidateRoute')
->once()
->with('test_noncached', [])
;
$mock->shouldReceive('invalidateRoute')
->once()
->with('test_cached', ['id' => 'myhardcodedid'])
;
$mock->shouldReceive('invalidateRoute')
->once()
->with('tag_one', ['id' => 42])
;
$mock->shouldReceive('flush')
->once()
->andReturn(3)
;
$client->getContainer()->set('fos_http_cache.cache_manager', $mock);

$client->request('POST', '/php8/invalidate/route/42');
}

/**
* @dataProvider getStatusCodesThatTriggerInvalidation
* @requires PHP 8.0
*/
public function testInvalidatePathWithPHPAttributes($statusCode)
{
$client = static::createClient();

$mock = \Mockery::mock(CacheManager::class);
$mock->shouldReceive('supports')
->zeroOrMoreTimes()
->andReturnTrue()
;
$mock->shouldReceive('invalidatePath')
->once()
->with('/php8/cached')
;
$mock->shouldReceive('invalidatePath')
->once()
->with(sprintf('/php8/invalidate/path/%s', $statusCode))
;
$mock->shouldReceive('flush')
->once()
->andReturn(2)
;
$client->getContainer()->set('fos_http_cache.cache_manager', $mock);

$client->request('POST', sprintf('/php8/invalidate/path/%s', $statusCode));
}

/**
* @requires PHP 8.0
*/
public function testErrorIsNotInvalidatedWithPHPAttributes()
{
$client = static::createClient();

$mock = \Mockery::mock(CacheManager::class);
$mock->shouldReceive('supports')
->zeroOrMoreTimes()
->andReturnTrue()
;
$mock->shouldReceive('flush')
->once()
->andReturn(0)
;
$client->getContainer()->set('fos_http_cache.cache_manager', $mock);

$client->request('POST', '/php8/invalidate/error');
}

public function getStatusCodesThatTriggerInvalidation()
{
return [[200], [204], [302]];
Expand Down
17 changes: 17 additions & 0 deletions tests/Functional/EventListener/TagListenerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,23 @@ public function testAnnotationTagsAreSet()
$this->assertEquals('item-123', $response->headers->get('X-Cache-Tags'));
}

/**
* @requires PHP 8.0
*/
public function testAttributeTagsAreSet()
{
$client = static::createClient();

$client->request('GET', '/php8/tag/list');
$response = $client->getResponse();
$this->assertEquals('all-items,item-123', $response->headers->get('X-Cache-Tags'));

$client->request('GET', '/php8/tag/123');
$response = $client->getResponse();
$this->assertEquals(200, $response->getStatusCode(), $response->getContent());
$this->assertEquals('item-123', $response->headers->get('X-Cache-Tags'));
}

public function testAnnotationTagsAreInvalidated()
{
$client = static::createClient();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

/*
* This file is part of the FOSHttpCacheBundle package.
*
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace FOS\HttpCacheBundle\Tests\Functional\Fixtures\Controller;

use FOS\HttpCacheBundle\Configuration\InvalidatePath;
use FOS\HttpCacheBundle\Configuration\InvalidateRoute;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;

if (!\class_exists(AbstractController::class)) {
\class_alias(Controller::class, AbstractController::class);
}

class InvalidationAttributeController extends AbstractController
{
#[InvalidateRoute('test_noncached')]
#[InvalidateRoute('test_cached', params: ['id' => 'myhardcodedid'])]
#[InvalidateRoute('tag_one', params: ['id' => ['expression' => 'id']])]
public function itemAction($id)
{
return new Response("Done $id");
}

#[InvalidatePath('/php8/cached')]
public function otherAction($statusCode)
{
return new Response('Done.', $statusCode);
}

#[InvalidatePath('/php8/somepath')]
public function errorAction()
{
return new Response('Forbidden', 403);
}
}

0 comments on commit e13f910

Please sign in to comment.