Completed
Push — master ( 488b9a...5d1f62 )
by David
15s queued 12s
created

TagListener::getExpressionLanguage()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 12
Ratio 100 %

Code Coverage

Tests 3
CRAP Score 3

Importance

Changes 0
Metric Value
dl 12
loc 12
ccs 3
cts 3
cp 1
rs 9.8666
c 0
b 0
f 0
cc 3
nc 3
nop 0
crap 3
1
<?php
2
3
/*
4
 * This file is part of the FOSHttpCacheBundle package.
5
 *
6
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace FOS\HttpCacheBundle\EventListener;
13
14
use FOS\HttpCacheBundle\CacheManager;
15
use FOS\HttpCacheBundle\Configuration\Tag;
16
use FOS\HttpCacheBundle\Http\RuleMatcherInterface;
17
use FOS\HttpCacheBundle\Http\SymfonyResponseTagger;
18
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
19
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
20
use Symfony\Component\HttpFoundation\Request;
21
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
22
use Symfony\Component\HttpKernel\Event\ResponseEvent;
23
use Symfony\Component\HttpKernel\HttpKernelInterface;
24
use Symfony\Component\HttpKernel\Kernel;
25
use Symfony\Component\HttpKernel\KernelEvents;
26
27 1 View Code Duplication
if (Kernel::MAJOR_VERSION >= 5) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
28 1
    class_alias(ResponseEvent::class, 'FOS\HttpCacheBundle\EventListener\TagResponseEvent');
29
} else {
30
    class_alias(FilterResponseEvent::class, 'FOS\HttpCacheBundle\EventListener\TagResponseEvent');
31
}
32
33
/**
34
 * Event handler for the cache tagging tags.
35
 *
36
 * @author David de Boer <[email protected]>
37
 */
38
class TagListener extends AbstractRuleListener implements EventSubscriberInterface
39
{
40
    /**
41
     * @var CacheManager
42
     */
43
    private $cacheManager;
44
45
    /**
46
     * @var SymfonyResponseTagger
47
     */
48
    private $symfonyResponseTagger;
49
50
    /**
51
     * @var ExpressionLanguage|null
52
     */
53
    private $expressionLanguage;
54
55
    /**
56
     * @var RuleMatcherInterface
57
     */
58
    private $mustInvalidateRule;
59
60
    /**
61
     * @var RuleMatcherInterface
62
     */
63
    private $cacheableRule;
64
65
    /**
66
     * Constructor.
67
     */
68 View Code Duplication
    public function __construct(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
69
        CacheManager $cacheManager,
70
        SymfonyResponseTagger $tagHandler,
71
        RuleMatcherInterface $cacheableRule,
72 29
        RuleMatcherInterface $mustInvalidateRule,
73
        ExpressionLanguage $expressionLanguage = null
74
    ) {
75
        $this->cacheManager = $cacheManager;
76
        $this->symfonyResponseTagger = $tagHandler;
77
        $this->cacheableRule = $cacheableRule;
78
        $this->mustInvalidateRule = $mustInvalidateRule;
79 29
        $this->expressionLanguage = $expressionLanguage;
80 29
    }
81 29
82 29
    /**
83 29
     * Process the _tags request attribute, which is set when using the Tag
84 29
     * annotation.
85
     *
86
     * - For a safe (GET or HEAD) request, the tags are set on the response.
87
     * - For a non-safe request, the tags will be invalidated.
88
     */
89
    public function onKernelResponse(TagResponseEvent $event)
90
    {
91
        $request = $event->getRequest();
92
        $response = $event->getResponse();
93 28
94
        if (!$this->cacheableRule->matches($request, $response)
95 28
            && !$this->mustInvalidateRule->matches($request, $response)
96 28
        ) {
97
            return;
98 28
        }
99 28
100
        $tags = $this->getAnnotationTags($request);
101 5
102
        $configuredTags = $this->matchRule($request);
103
        if ($configuredTags) {
104 24
            $tags = array_merge($tags, $configuredTags['tags']);
105
            foreach ($configuredTags['expressions'] as $expression) {
106 24
                $tags[] = $this->evaluateTag($expression, $request);
107 24
            }
108 5
        }
109 5
110 5
        if ($this->cacheableRule->matches($request, $response)) {
111
            // For safe requests (GET and HEAD), set cache tags on response
112
            $this->symfonyResponseTagger->addTags($tags);
113
            if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) {
114 24
                $this->symfonyResponseTagger->tagSymfonyResponse($response);
115
            }
116 16
        } elseif (count($tags)
117 16
            && $this->mustInvalidateRule->matches($request, $response)
118 16
        ) {
119
            $this->cacheManager->invalidateTags($tags);
120 8
        }
121 8
    }
122
123 4
    /**
124
     * {@inheritdoc}
125 24
     */
126
    public static function getSubscribedEvents()
127
    {
128
        return [
129
            KernelEvents::RESPONSE => 'onKernelResponse',
130 2
        ];
131
    }
132
133 2
    /**
134
     * Get the tags from the annotations on the controller that was used in the
135
     * request.
136
     *
137
     * @return array List of tags affected by the request
138
     */
139
    private function getAnnotationTags(Request $request)
140
    {
141
        // Check for _tag request attribute that is set when using @Tag
142
        // annotation
143
        /** @var $tagConfigurations Tag[] */
144
        if (!$tagConfigurations = $request->attributes->get('_tag')) {
145 24
            return [];
146
        }
147
148
        $tags = [];
149
        foreach ($tagConfigurations as $tagConfiguration) {
150 24
            if (null !== $tagConfiguration->getExpression()) {
151 13
                $tags[] = $this->evaluateTag(
152
                    $tagConfiguration->getExpression(),
153
                    $request
154 11
                );
155 11
            } else {
156 11
                $tags = array_merge($tags, $tagConfiguration->getTags());
157 3
            }
158 3
        }
159
160
        return $tags;
161
    }
162 9
163
    /**
164
     * Evaluate a tag that contains expressions.
165
     *
166 11
     * @param string $expression
167
     *
168
     * @return string Evaluated tag
0 ignored issues
show
Documentation introduced by
Should the return type not be array?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
169
     */
170
    private function evaluateTag($expression, Request $request)
171
    {
172
        $values = $request->attributes->all();
173
        // if there is an attribute called "request", it needs to be accessed through the request.
174
        $values['request'] = $request;
175
176
        return $this->getExpressionLanguage()->evaluate($expression, $values);
177 8
    }
178
179 8 View Code Duplication
    private function getExpressionLanguage(): ExpressionLanguage
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
180
    {
181 8
        if (!$this->expressionLanguage) {
182
            // the expression comes from controller annotations, we can't detect whether they use expressions while building the configuration
183 8
            if (!class_exists(ExpressionLanguage::class)) {
184
                throw new \RuntimeException('Using the tag annotation requires the '.ExpressionLanguage::class.' to be available.');
185
            }
186
            $this->expressionLanguage = new ExpressionLanguage();
187
        }
188
189
        return $this->expressionLanguage;
190
    }
191
}
192