Issues (34)

src/SectionField/Service/DefaultCache.php (2 issues)

1
<?php
2
3
declare (strict_types=1);
4
5
namespace Tardigrades\SectionField\Service;
6
7
use Psr\Cache\CacheItemInterface;
8
use Symfony\Component\Cache\Adapter\AdapterInterface;
9
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
10
use Tardigrades\SectionField\Generator\CommonSectionInterface;
11
use Tardigrades\SectionField\ValueObject\FullyQualifiedClassName;
12
13
/**
14
 * Class DefaultCache
15
 * @package Tardigrades\SectionField\Service
16
 */
17
class DefaultCache implements CacheInterface
18
{
19
    /** @var AdapterInterface */
20
    protected $cache;
21
22
    /** @var bool */
23
    protected $enabled;
24
25
    /** @var CacheItemInterface */
26
    private $item;
27
28
    /** @var string */
29
    private $sectionHandle;
30
31
    /** @var string */
32
    private $fieldKey;
33
34
    /** @var string */
35
    private $context;
36
37
    /** @var string[] */
38
    private $relationships;
39
40
    /**
41
     * DefaultCache constructor.
42
     *
43
     * @param TagAwareAdapterInterface $cache
44
     * @param bool $enabled
45
     */
46
    public function __construct(
47
        TagAwareAdapterInterface $cache,
48
        string $enabled = 'false'
49
    ) {
50
        $this->cache = $cache;
51
        $this->enabled = $enabled === 'false' ? false : true ;
52
        $this->item = null;
53
        $this->sectionHandle = null;
54
        $this->fieldKey = null;
55
        $this->relationships = null;
56
    }
57
58
    /**
59
     * {@inheritdoc}
60
     */
61
    public function start(
62
        FullyQualifiedClassName $fullyQualifiedClassName,
63
        array $requestedFields = null,
64
        string $context = null,
65
        string $id = null
66
    ): void {
67
        if ($this->enabled) {
68
            try {
69
                $this->item = $this->cache->getItem(
70
                    $this->getItemKey(
71
                        (string)$fullyQualifiedClassName,
72
                        $requestedFields,
73
                        $context,
74
                        $id
75
                    )
76
                );
77
                $this->context = sha1($context);
78
                $this->sectionHandle = sha1((string)$fullyQualifiedClassName);
79
                $this->fieldKey = $this->getFieldKey($requestedFields);
80
                $this->relationships = $this->getRelationships($fullyQualifiedClassName);
81
            } catch (\Exception $exception) {
82
                $this->enabled = false;
83
            }
84
        }
85
    }
86
87
    /**
88
     * {@inheritdoc}
89
     */
90
    public function set(array $data): void
91
    {
92
        if ($this->enabled) {
93
            $this->item->set($data);
94
            $this->item->tag($this->sectionHandle);
95
            $this->item->tag($this->fieldKey);
96
            $this->item->tag($this->context);
97
            foreach ($this->relationships as $relationship) {
98
                $this->item->tag(sha1($relationship));
99
            }
100
            $this->cache->save($this->item);
101
        }
102
    }
103
104
    /**
105
     * {@inheritdoc}
106
     */
107
    public function isHit(): bool
108
    {
109
        if (!$this->enabled) {
110
            return false;
111
        }
112
        return $this->item->isHit();
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function get(): array
119
    {
120
        if (!$this->enabled) {
121
            return [];
122
        }
123
        return $this->item->get();
124
    }
125
126
    /**
127
     * {@inheritdoc}
128
     */
129
    public function invalidateForSection(FullyQualifiedClassName $fullyQualifiedClassName): void
130
    {
131
        if ($this->enabled) {
132
            $this->cache->invalidateTags([sha1((string)$fullyQualifiedClassName)]);
0 ignored issues
show
The method invalidateTags() does not exist on Symfony\Component\Cache\Adapter\AdapterInterface. It seems like you code against a sub-type of Symfony\Component\Cache\Adapter\AdapterInterface such as Symfony\Component\Cache\...agAwareAdapterInterface or Symfony\Component\Cache\...raceableTagAwareAdapter. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

132
            $this->cache->/** @scrutinizer ignore-call */ 
133
                          invalidateTags([sha1((string)$fullyQualifiedClassName)]);
Loading history...
133
        }
134
    }
135
136
    /**
137
     * {@inheritdoc}
138
     */
139
    public function invalidateForContext(string $context): void
140
    {
141
        if ($this->enabled) {
142
            $this->cache->invalidateTags([sha1($context)]);
143
        }
144
    }
145
146
    /**
147
     * Get the key for the item so the key will be unique for this
148
     * specific cache item and can't be unexpectedly overridden.
149
     *
150
     * @param string $sectionHandle
151
     * @param array $requestedFields
152
     * @param string|null $context
153
     * @param string|null $id
154
     * @return string
155
     */
156
    private function getItemKey(
157
        string $sectionHandle,
158
        array $requestedFields = null,
159
        string $context = null,
160
        string $id = null
161
    ): string {
162
        return sha1($sectionHandle) .
163
            $this->getFieldKey($requestedFields) .
164
            $this->getContextKey($context) .
165
            $this->getIdKey($id);
166
    }
167
168
    /**
169
     * Make sure the context is as a key and sha1.
170
     * In some cases the context could be a fully qualified class name
171
     * and therefore contain invalid characters for use in a key
172
     *
173
     * @param string|null $context
174
     * @return string
175
     */
176
    private function getContextKey(string $context = null): string
177
    {
178
        return !is_null($context) ? ('.' . sha1($context)) : '.no-context';
179
    }
180
181
    /**
182
     * A lot of calls contain the fields one want's to have in return.
183
     * Make sure this is also added to the item key.
184
     *
185
     * @param array $requestedFields
186
     * @return string
187
     */
188
    private function getFieldKey(array $requestedFields = null): string
189
    {
190
        if (is_null($requestedFields)) {
191
            return 'no-field-key';
192
        }
193
        return $fieldKey = '.' . sha1(implode(',', $requestedFields));
0 ignored issues
show
The assignment to $fieldKey is dead and can be removed.
Loading history...
194
    }
195
196
    /**
197
     * Cache a specific entry? Add the id.
198
     *
199
     * @param string|null $id
200
     * @return string
201
     */
202
    private function getIdKey(string $id = null): string
203
    {
204
        return !is_null($id) ? ('.' . $id) : '.no-id';
205
    }
206
207
    /**
208
     * Entries can have relationships, make sure to tag them.
209
     *
210
     * @param FullyQualifiedClassName $fullyQualifiedClassName
211
     * @return array
212
     * @throws NotASexyFieldEntityException
213
     */
214
    private function getRelationships(FullyQualifiedClassName $fullyQualifiedClassName): array
215
    {
216
        $fields = (string)$fullyQualifiedClassName;
217
        if (!is_subclass_of($fields, CommonSectionInterface::class)) {
218
            throw new NotASexyFieldEntityException;
219
        }
220
        /** @var CommonSectionInterface $fields */
221
        $relationships = [];
222
        foreach ($fields::fieldInfo() as $field) {
223
            if (!is_null($field['relationship'])) {
224
                $relationships[] = $field['relationship']['class'];
225
            }
226
        }
227
        return $relationships;
228
    }
229
}
230