Passed
Pull Request — 1.x (#99)
by Akihito
10:41
created

QueryRepository::setMaxAge()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 14
cts 14
cp 1
rs 9.2248
c 0
b 0
f 0
cc 5
nc 5
nop 2
crap 5
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\QueryRepository;
6
7
use BEAR\QueryRepository\Exception\ExpireAtKeyNotExists;
8
use BEAR\RepositoryModule\Annotation\Cacheable;
9
use BEAR\RepositoryModule\Annotation\HttpCache;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, BEAR\QueryRepository\HttpCache. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
10
use BEAR\Resource\AbstractUri;
11
use BEAR\Resource\ResourceObject;
12
use Doctrine\Common\Annotations\Reader;
13
use ReflectionClass;
14
15
use function get_class;
16
use function is_array;
17
use function sprintf;
18
use function strtotime;
19
use function time;
20
21
final class QueryRepository implements QueryRepositoryInterface
22
{
23
    /** @var ResourceStorageInterface */
24
    private $storage;
25
26
    /** @var Reader */
27
    private $reader;
28
29
    /** @var Expiry */
30
    private $expiry;
31
32
    /** @var HeaderSetter */
33
    private $headerSetter;
34
35
    /** @var RepositoryLoggerInterface */
36
    private $logger;
37 29
38
    public function __construct(
39
        RepositoryLoggerInterface $logger,
40
        HeaderSetter $headerSetter,
41
        ResourceStorageInterface $storage,
42
        Reader $reader,
43 29
        Expiry $expiry
44 29
    ) {
45 29
        $this->headerSetter = $headerSetter;
46 29
        $this->reader = $reader;
47 29
        $this->storage = $storage;
48
        $this->expiry = $expiry;
49
        $this->logger = $logger;
50
    }
51
52
    /**
53
     * {@inheritdoc}
54 27
     */
55
    public function put(ResourceObject $ro)
56 27
    {
57 27
        $this->logger->log('put-query-repository uri:%s', $ro->uri);
58 27
        $this->storage->deleteEtag($ro->uri);
59 27
        $ro->toString();
60 27
        $cacheable = $this->getCacheableAnnotation($ro);
61 26
        $httpCache = $this->getHttpCacheAnnotation($ro);
62 26
        $ttl = $this->getExpiryTime($ro, $cacheable);
63
        ($this->headerSetter)($ro, $ttl, $httpCache);
64 26
        if (isset($ro->headers[Header::ETAG])) {
65 26
            $etag = $ro->headers[Header::ETAG];
66 2
            $surrogateKeys = $ro->headers[Header::SURROGATE_KEY] ?? '';
67
            $this->storage->saveEtag($ro->uri, $etag, $surrogateKeys, $ttl);
68
        }
69 24
70
        if ($cacheable instanceof Cacheable && $cacheable->type === 'view') {
71
            return $this->storage->saveView($ro, $ttl);
72
        }
73
74
        return $this->storage->saveValue($ro, $ttl);
75 25
    }
76
77 25
    /**
78 25
     * {@inheritdoc}
79 23
     */
80
    public function get(AbstractUri $uri): ?ResourceState
81 15
    {
82 15
        $state = $this->storage->get($uri);
83
84 15
        if ($state === null) {
85
            return null;
86
        }
87
88
        $state->headers[Header::AGE] = (string) (time() - strtotime($state->headers[Header::LAST_MODIFIED]));
89
90 12
        return $state;
91
    }
92 12
93
    /**
94 12
     * {@inheritdoc}
95
     */
96
    public function purge(AbstractUri $uri)
97
    {
98
        $this->logger->log('purge-query-repository uri:%s', $uri);
99
100 27
        return $this->storage->deleteEtag($uri);
101
    }
102 27
103 27
    private function getHttpCacheAnnotation(ResourceObject $ro): ?HttpCache
104 27
    {
105
        return $this->reader->getClassAnnotation(new ReflectionClass($ro), HttpCache::class);
106
    }
107
108
    private function getCacheableAnnotation(ResourceObject $ro): ?Cacheable
109
    {
110
        return $this->reader->getClassAnnotation(new ReflectionClass($ro), Cacheable::class);
111
    }
112
113
    private function getExpiryTime(ResourceObject $ro, ?Cacheable $cacheable = null): int
114
    {
115 27
        if ($cacheable === null) {
116
            return 0;
117 27
        }
118 27
119 27
        if ($cacheable->expiryAt) {
120
            return $this->getExpiryAtSec($ro, $cacheable);
121
        }
122
123
        return $cacheable->expirySecond ? $cacheable->expirySecond : $this->expiry->getTime($cacheable->expiry);
124
    }
125
126
    private function getExpiryAtSec(ResourceObject $ro, Cacheable $cacheable): int
127
    {
128
        if (! is_array($ro->body) || ! isset($ro->body[$cacheable->expiryAt])) {
129
            $msg = sprintf('%s::%s', get_class($ro), $cacheable->expiryAt);
130
131
            throw new ExpireAtKeyNotExists($msg);
132
        }
133
134
        /** @var string $expiryAt */
135
        $expiryAt = $ro->body[$cacheable->expiryAt];
136
137
        return strtotime($expiryAt) - time();
138
    }
139
}
140