CacheResponseMiddleware::requestReceived()   B
last analyzed

Complexity

Conditions 8
Paths 9

Size

Total Lines 47
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 23
dl 0
loc 47
rs 8.4444
c 1
b 0
f 0
cc 8
nc 9
nop 2
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\Exception\RuntimeException;
19
use Valkyrja\Filesystem\Contract\Filesystem;
20
use Valkyrja\Filesystem\InMemoryFilesystem;
21
use Valkyrja\Http\Message\Enum\StatusCode;
22
use Valkyrja\Http\Message\Request\Contract\ServerRequest;
23
use Valkyrja\Http\Message\Response\Contract\Response;
24
use Valkyrja\Http\Middleware\Contract\RequestReceivedMiddleware;
25
use Valkyrja\Http\Middleware\Contract\TerminatedMiddleware;
26
use Valkyrja\Http\Middleware\Handler\Contract\RequestReceivedHandler;
27
use Valkyrja\Http\Middleware\Handler\Contract\TerminatedHandler;
28
use Valkyrja\Support\Directory;
0 ignored issues
show
Bug introduced by
The type Valkyrja\Support\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...
29
use Valkyrja\Support\Time;
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
/**
38
 * Class CacheResponseMiddleware.
39
 *
40
 * @author Melech Mizrachi
41
 */
42
class CacheResponseMiddleware implements RequestReceivedMiddleware, TerminatedMiddleware
43
{
44
    public function __construct(
45
        protected Filesystem $filesystem = new InMemoryFilesystem(),
46
        protected bool $debug = false
47
    ) {
48
    }
49
50
    /**
51
     * @inheritDoc
52
     */
53
    #[Override]
54
    public function requestReceived(ServerRequest $request, RequestReceivedHandler $handler): ServerRequest|Response
55
    {
56
        $filesystem = $this->filesystem;
57
58
        $filePath = $this->getCachePathForRequest($request);
59
60
        if (! $this->debug && $filesystem->exists($filePath)) {
61
            $timestamp = $filesystem->timestamp($filePath) ?? 0;
62
63
            if (Time::get() - $timestamp > $this->getTtl()) {
64
                $filesystem->delete($filePath);
65
66
                return $handler->requestReceived($request);
67
            }
68
69
            try {
70
                $cache        = $filesystem->read($filePath);
71
                $decodedCache = base64_decode($cache, true);
72
73
                if ($decodedCache === false) {
74
                    throw new RuntimeException('Failed to decode cache');
75
                }
76
77
                /** @var object $response */
78
                $response = unserialize(
79
                    $decodedCache,
80
                    [
81
                        'allowed_classes' => true,
82
                    ]
83
                );
84
85
                // Ensure a valid response before returning it
86
                if (
87
                    $response instanceof Response
88
                    && $response->getStatusCode()->value < StatusCode::INTERNAL_SERVER_ERROR->value
89
                ) {
90
                    return $response;
91
                }
92
            } catch (Throwable) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
93
            }
94
95
            // Remove the bad cache
96
            $filesystem->delete($filePath);
97
        }
98
99
        return $handler->requestReceived($request);
100
    }
101
102
    /**
103
     * @inheritDoc
104
     */
105
    #[Override]
106
    public function terminated(ServerRequest $request, Response $response, TerminatedHandler $handler): void
107
    {
108
        $this->filesystem->write(
109
            $this->getCachePathForRequest($request),
110
            base64_encode(serialize($response))
111
        );
112
113
        $handler->terminated($request, $response);
114
    }
115
116
    /**
117
     * Get the ttl.
118
     *
119
     * @return int
120
     */
121
    protected function getTtl(): int
122
    {
123
        return 1800;
124
    }
125
126
    /**
127
     * Get a hashed version of the request path.
128
     *
129
     * @param ServerRequest $request
130
     *
131
     * @return string
132
     */
133
    protected function getHashedPath(ServerRequest $request): string
134
    {
135
        return md5($request->getUri()->getPath());
136
    }
137
138
    /**
139
     * Get the cache path for a request.
140
     */
141
    protected function getCachePathForRequest(ServerRequest $request): string
142
    {
143
        return Directory::cachePath('response/' . $this->getHashedPath($request));
144
    }
145
}
146