CacheResponseMiddleware   A
last analyzed

Complexity

Total Complexity 13

Size/Duplication

Total Lines 102
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 13
eloc 31
dl 0
loc 102
c 0
b 0
f 0
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A terminated() 0 9 1
A getCachePathForRequest() 0 3 1
A getTtl() 0 3 1
B requestReceived() 0 47 8
A getHashedPath() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Valkyrja Framework package.
7
 *
8
 * (c) Melech Mizrachi <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Valkyrja\Http\Middleware\Cache;
15
16
use Override;
0 ignored issues
show
Bug introduced by
The type Override was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use Throwable;
18
use Valkyrja\Filesystem\Manager\Contract\FilesystemContract;
19
use Valkyrja\Filesystem\Manager\InMemoryFilesystem;
20
use Valkyrja\Http\Message\Enum\StatusCode;
21
use Valkyrja\Http\Message\Request\Contract\ServerRequestContract;
22
use Valkyrja\Http\Message\Response\Contract\ResponseContract;
23
use Valkyrja\Http\Middleware\Contract\RequestReceivedMiddlewareContract;
24
use Valkyrja\Http\Middleware\Contract\TerminatedMiddlewareContract;
25
use Valkyrja\Http\Middleware\Handler\Contract\RequestReceivedHandlerContract;
26
use Valkyrja\Http\Middleware\Handler\Contract\TerminatedHandlerContract;
27
use Valkyrja\Support\Directory\Directory;
0 ignored issues
show
Bug introduced by
The type Valkyrja\Support\Directory\Directory was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
28
use Valkyrja\Support\Time\Time;
29
use Valkyrja\Throwable\Exception\RuntimeException;
30
31
use function base64_decode;
32
use function base64_encode;
33
use function md5;
34
use function serialize;
35
use function unserialize;
36
37
class CacheResponseMiddleware implements RequestReceivedMiddlewareContract, TerminatedMiddlewareContract
38
{
39
    public function __construct(
40
        protected FilesystemContract $filesystem = new InMemoryFilesystem(),
41
        protected bool $debug = false
42
    ) {
43
    }
44
45
    /**
46
     * @inheritDoc
47
     */
48
    #[Override]
49
    public function requestReceived(ServerRequestContract $request, RequestReceivedHandlerContract $handler): ServerRequestContract|ResponseContract
50
    {
51
        $filesystem = $this->filesystem;
52
53
        $filePath = $this->getCachePathForRequest($request);
54
55
        if (! $this->debug && $filesystem->exists($filePath)) {
56
            $timestamp = $filesystem->timestamp($filePath) ?? 0;
57
58
            if (Time::get() - $timestamp > $this->getTtl()) {
59
                $filesystem->delete($filePath);
60
61
                return $handler->requestReceived($request);
62
            }
63
64
            try {
65
                $cache        = $filesystem->read($filePath);
66
                $decodedCache = base64_decode($cache, true);
67
68
                if ($decodedCache === false) {
69
                    throw new RuntimeException('Failed to decode cache');
70
                }
71
72
                /** @var object $response */
73
                $response = unserialize(
74
                    $decodedCache,
75
                    [
76
                        'allowed_classes' => true,
77
                    ]
78
                );
79
80
                // Ensure a valid response before returning it
81
                if (
82
                    $response instanceof ResponseContract
83
                    && $response->getStatusCode()->value < StatusCode::INTERNAL_SERVER_ERROR->value
84
                ) {
85
                    return $response;
86
                }
87
            } catch (Throwable) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
88
            }
89
90
            // Remove the bad cache
91
            $filesystem->delete($filePath);
92
        }
93
94
        return $handler->requestReceived($request);
95
    }
96
97
    /**
98
     * @inheritDoc
99
     */
100
    #[Override]
101
    public function terminated(ServerRequestContract $request, ResponseContract $response, TerminatedHandlerContract $handler): void
102
    {
103
        $this->filesystem->write(
104
            $this->getCachePathForRequest($request),
105
            base64_encode(serialize($response))
106
        );
107
108
        $handler->terminated($request, $response);
109
    }
110
111
    /**
112
     * Get the ttl.
113
     *
114
     * @return int
115
     */
116
    protected function getTtl(): int
117
    {
118
        return 1800;
119
    }
120
121
    /**
122
     * Get a hashed version of the request path.
123
     *
124
     * @param ServerRequestContract $request
125
     *
126
     * @return string
127
     */
128
    protected function getHashedPath(ServerRequestContract $request): string
129
    {
130
        return md5($request->getUri()->getPath());
131
    }
132
133
    /**
134
     * Get the cache path for a request.
135
     */
136
    protected function getCachePathForRequest(ServerRequestContract $request): string
137
    {
138
        return Directory::cachePath('response/' . $this->getHashedPath($request));
139
    }
140
}
141