Completed
Push — master ( 99e856...e5db02 )
by David
29s queued 27s
created

src/SymfonyCache/PurgeTagsListener.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/*
4
 * This file is part of the FOSHttpCache 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\HttpCache\SymfonyCache;
13
14
use Symfony\Component\HttpFoundation\Response;
15
use Symfony\Component\OptionsResolver\OptionsResolver;
16
use Toflar\Psr6HttpCacheStore\Psr6StoreInterface;
17
18
/**
19
 * Purge tags handler for the Symfony built-in HttpCache.
20
 *
21
 * @author Yanick Witschi <[email protected]>
22
 *
23
 * {@inheritdoc}
24
 */
25
class PurgeTagsListener extends AccessControlledListener
26
{
27
    const DEFAULT_TAGS_METHOD = 'PURGETAGS';
28
29
    const DEFAULT_TAGS_HEADER = 'X-Cache-Tags';
30
31
    /**
32
     * The purge tags method to use.
33
     *
34
     * @var string
35
     */
36
    private $tagsMethod;
37
38
    /**
39
     * The purge tags header to use.
40
     *
41
     * @var string
42
     */
43
    private $tagsHeader;
44
45
    /**
46
     * When creating the purge listener, you can configure an additional option.
47
     *
48
     * - tags_method: HTTP method that identifies purge tags requests.
49
     * - tags_header: HTTP header that contains cache tags to invalidate.
50
     *
51
     * @param array $options Options to overwrite the default options
52
     *
53
     * @throws \InvalidArgumentException if unknown keys are found in $options
54
     *
55
     * @see AccessControlledListener::__construct
56
     */
57
    public function __construct(array $options = [])
58
    {
59
        if (!interface_exists(Psr6StoreInterface::class)) {
60
            throw new \Exception('Cache tag invalidation only works with the toflar/psr6-symfony-http-cache-store package. See "Symfony HttpCache Configuration" in the documentation.');
61
        }
62
        parent::__construct($options);
63
64
        $options = $this->getOptionsResolver()->resolve($options);
65
66
        $this->tagsMethod = $options['tags_method'];
67
        $this->tagsHeader = $options['tags_header'];
68
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73
    public static function getSubscribedEvents()
74
    {
75
        return [
76
            Events::PRE_INVALIDATE => 'handlePurgeTags',
77
        ];
78
    }
79
80
    /**
81
     * Look at unsafe requests and handle purge tags requests.
82
     *
83
     * Prevents access when the request comes from a non-authorized client.
84
     *
85
     * @param CacheEvent $event
86
     */
87
    public function handlePurgeTags(CacheEvent $event)
88
    {
89
        $request = $event->getRequest();
90
        if ($this->tagsMethod !== $request->getMethod()) {
91
            return;
92
        }
93
94
        if (!$this->isRequestAllowed($request)) {
95
            $event->setResponse(new Response('', 400));
96
97
            return;
98
        }
99
100
        $response = new Response();
101
        $store = $event->getKernel()->getStore();
102
103 View Code Duplication
        if (!$store instanceof Psr6StoreInterface) {
104
            $response->setStatusCode(400);
105
            $response->setContent('Store must be an instance of '.Psr6StoreInterface::class.'. Please check your proxy configuration.');
106
107
            $event->setResponse($response);
108
109
            return;
110
        }
111
112
        if (!$request->headers->has($this->tagsHeader)) {
113
            $response->setStatusCode(200, 'Not found');
114
115
            $event->setResponse($response);
116
117
            return;
118
        }
119
120
        $tags = [];
121
122
        foreach ($request->headers->get($this->tagsHeader, '', false) as $v) {
0 ignored issues
show
The expression $request->headers->get($...>tagsHeader, '', false) of type string|array<integer,string>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
123
            foreach (explode(',', $v) as $tag) {
124
                $tags[] = $tag;
125
            }
126
        }
127
128
        if ($store->invalidateTags($tags)) {
129
            $response->setStatusCode(200, 'Purged');
130
        } else {
131
            $response->setStatusCode(200, 'Not found');
132
        }
133
134
        $event->setResponse($response);
135
    }
136
137
    /**
138
     * Add the purge_method option.
139
     *
140
     * @return OptionsResolver
141
     */
142 View Code Duplication
    protected function getOptionsResolver()
143
    {
144
        $resolver = parent::getOptionsResolver();
145
        $resolver->setDefaults([
146
            'tags_method' => static::DEFAULT_TAGS_METHOD,
147
            'tags_header' => static::DEFAULT_TAGS_HEADER,
148
        ]);
149
        $resolver->setAllowedTypes('tags_method', 'string');
150
        $resolver->setAllowedTypes('tags_header', 'string');
151
152
        return $resolver;
153
    }
154
}
155