Completed
Pull Request — master (#299)
by David
67:30 queued 64:42
created

UserContextSubscriber   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 155
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 100%

Importance

Changes 6
Bugs 2 Features 2
Metric Value
wmc 18
c 6
b 2
f 2
lcom 1
cbo 8
dl 0
loc 155
ccs 59
cts 59
cp 1
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
B onKernelRequest() 0 33 5
D onKernelResponse() 0 39 10
A __construct() 0 18 2
A getSubscribedEvents() 0 7 1
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\HttpCache\UserContext\HashGenerator;
15
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
16
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
17
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
18
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
19
use Symfony\Component\HttpFoundation\Response;
20
use Symfony\Component\HttpKernel\HttpKernelInterface;
21
use Symfony\Component\HttpKernel\KernelEvents;
22
23
/**
24
 * Check requests and responses with the matcher.
25
 *
26
 * Abort context hash requests immediately and return the hash.
27
 * Add the vary information on responses to normal requests.
28
 *
29
 * @author Stefan Paschke <[email protected]>
30
 * @author Joel Wurtz <[email protected]>
31
 */
32
class UserContextSubscriber implements EventSubscriberInterface
33
{
34
    /**
35
     * @var RequestMatcherInterface
36
     */
37
    private $requestMatcher;
38
39
    /**
40
     * @var HashGenerator
41
     */
42
    private $hashGenerator;
43
44
    /**
45
     * @var string[]
46
     */
47
    private $userIdentifierHeaders;
48
49
    /**
50
     * @var string
51
     */
52
    private $hash;
53
54
    /**
55
     * @var string
56
     */
57
    private $hashHeader;
58
59
    /**
60
     * @var int
61
     */
62
    private $ttl;
63
64
    /**
65
     * @var bool Whether to automatically add the Vary header for the hash / user identifier if there was no hash.
66
     */
67
    private $addVaryOnHash;
68
69 25
    public function __construct(
70
        RequestMatcherInterface $requestMatcher,
71
        HashGenerator $hashGenerator,
72
        array $userIdentifierHeaders = array('Cookie', 'Authorization'),
73
        $hashHeader = 'X-User-Context-Hash',
74
        $ttl = 0,
75
        $addVaryOnHash = true
76
    ) {
77 25
        if (!count($userIdentifierHeaders)) {
78 1
            throw new \InvalidArgumentException('The user context must vary on some request headers');
79
        }
80 24
        $this->requestMatcher = $requestMatcher;
81 24
        $this->hashGenerator = $hashGenerator;
82 24
        $this->userIdentifierHeaders = $userIdentifierHeaders;
83 24
        $this->hashHeader = $hashHeader;
84 24
        $this->ttl = $ttl;
85 24
        $this->addVaryOnHash = $addVaryOnHash;
86 24
    }
87
88
    /**
89
     * Return the response to the context hash request with a header containing
90
     * the generated hash.
91
     *
92
     * If the ttl is bigger than 0, cache headers will be set for this response.
93
     *
94
     * @param GetResponseEvent $event
95
     */
96 20
    public function onKernelRequest(GetResponseEvent $event)
97
    {
98 20
        if ($event->getRequestType() != HttpKernelInterface::MASTER_REQUEST) {
99 3
            return;
100
        }
101
102 19
        if (!$this->requestMatcher->matches($event->getRequest())) {
103 17
            if ($event->getRequest()->headers->has($this->hashHeader)) {
104 2
                $this->hash = $this->hashGenerator->generateHash();
105 2
            }
106
107 17
            return;
108
        }
109
110 2
        $hash = $this->hashGenerator->generateHash();
111
112
        // status needs to be 200 as otherwise varnish will not cache the response.
113 2
        $response = new Response('', 200, array(
114 2
            $this->hashHeader => $hash,
115 2
            'Content-Type' => 'application/vnd.fos.user-context-hash',
116 2
        ));
117
118 2
        if ($this->ttl > 0) {
119 1
            $response->setClientTtl($this->ttl);
120 1
            $response->setVary($this->userIdentifierHeaders);
121 1
            $response->setPublic();
122 1
        } else {
123 1
            $response->setClientTtl(0);
124 1
            $response->headers->addCacheControlDirective('no-cache');
125
        }
126
127 2
        $event->setResponse($response);
128 2
    }
129
130
    /**
131
     * Add the context hash header to the headers to vary on if the header was
132
     * present in the request.
133
     *
134
     * @param FilterResponseEvent $event
135
     */
136 20
    public function onKernelResponse(FilterResponseEvent $event)
137
    {
138 20
        if ($event->getRequestType() != HttpKernelInterface::MASTER_REQUEST) {
139 3
            return;
140
        }
141
142 19
        $response = $event->getResponse();
143 19
        $request = $event->getRequest();
144
145 19
        $vary = $response->getVary();
146
147 19
        if ($request->headers->has($this->hashHeader)) {
148
            // hash has changed, session has most certainly changed, prevent setting incorrect cache
149 3
            if (!is_null($this->hash) && $this->hash !== $request->headers->get($this->hashHeader)) {
150 1
                $response->setClientTtl(0);
151 1
                $response->headers->addCacheControlDirective('no-cache');
152
153 1
                return;
154
            }
155
156 2
            if ($this->addVaryOnHash && !in_array($this->hashHeader, $vary)) {
157 2
                $vary[] = $this->hashHeader;
158 2
            }
159 18
        } elseif ($this->addVaryOnHash) {
160
            /*
161
             * Additional precaution: If for some reason we get requests without a user hash, vary
162
             * on user identifier headers to avoid the caching proxy mixing up caches between
163
             * users. For the hash lookup request, those Vary headers are already added in
164
             * onKernelRequest above.
165
             */
166 16
            foreach ($this->userIdentifierHeaders as $header) {
167 16
                if (!in_array($header, $vary)) {
168 16
                    $vary[] = $header;
169 16
                }
170 16
            }
171 16
        }
172
173 18
        $response->setVary($vary, true);
174 18
    }
175
176
    /**
177
     * {@inheritdoc}
178
     */
179 23
    public static function getSubscribedEvents()
180
    {
181
        return array(
182 23
            KernelEvents::RESPONSE => 'onKernelResponse',
183 23
            KernelEvents::REQUEST => array('onKernelRequest', 7),
184 23
        );
185
    }
186
}
187