Passed
Push — master ( 2bed29...649fc6 )
by Georges
01:41
created

Driver::decode()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 13
nc 4
nop 1
dl 0
loc 24
rs 9.8333
c 0
b 0
f 0
1
<?php
2
3
/**
4
 *
5
 * This file is part of phpFastCache.
6
 *
7
 * @license MIT License (MIT)
8
 *
9
 * For full copyright and license information, please see the docs/CREDITS.txt file.
10
 *
11
 * @author Khoa Bui (khoaofgod)  <[email protected]> https://www.phpfastcache.com
12
 * @author Georges.L (Geolim4)  <[email protected]>
13
 *
14
 */
15
declare(strict_types=1);
16
17
namespace Phpfastcache\Drivers\Couchdb;
18
19
use Doctrine\CouchDB\{CouchDBClient, CouchDBException};
20
use Phpfastcache\Cluster\AggregatablePoolInterface;
21
use Phpfastcache\Core\Pool\{DriverBaseTrait, ExtendedCacheItemPoolInterface};
22
use Phpfastcache\Entities\DriverStatistic;
23
use Phpfastcache\Exceptions\{PhpfastcacheDriverException, PhpfastcacheInvalidArgumentException, PhpfastcacheLogicException};
24
use Psr\Cache\CacheItemInterface;
25
26
27
/**
28
 * Class Driver
29
 * @package phpFastCache\Drivers
30
 * @property CouchdbClient $instance Instance of driver service
31
 * @property Config $config Config object
32
 * @method Config getConfig() Return the config object
33
 */
34
class Driver implements ExtendedCacheItemPoolInterface, AggregatablePoolInterface
35
{
36
    public const COUCHDB_DEFAULT_DB_NAME = 'phpfastcache'; // Public because used in config
37
38
    use DriverBaseTrait;
39
40
    /**
41
     * @return bool
42
     */
43
    public function driverCheck(): bool
44
    {
45
        return class_exists(CouchDBClient::class);
46
    }
47
48
    /**
49
     * @return string
50
     */
51
    public function getHelp(): string
52
    {
53
        return <<<HELP
54
<p>
55
To install the Couchdb HTTP client library via Composer:
56
<code>composer require "doctrine/couchdb" "@dev"</code>
57
</p>
58
HELP;
59
    }
60
61
    /**
62
     * @return DriverStatistic
63
     */
64
    public function getStats(): DriverStatistic
65
    {
66
        $info = $this->instance->getDatabaseInfo();
67
68
        return (new DriverStatistic())
69
            ->setSize($info['sizes']['active'])
70
            ->setRawData($info)
71
            ->setData(implode(', ', array_keys($this->itemInstances)))
72
            ->setInfo('Couchdb version ' . $this->instance->getVersion() . "\n For more information see RawData.");
73
    }
74
75
    /**
76
     * @return bool
77
     * @throws PhpfastcacheLogicException
78
     */
79
    protected function driverConnect(): bool
80
    {
81
        if ($this->instance instanceof CouchDBClient) {
0 ignored issues
show
introduced by
$this->instance is always a sub-type of Doctrine\CouchDB\CouchDBClient.
Loading history...
82
            throw new PhpfastcacheLogicException('Already connected to Couchdb server');
83
        }
84
85
        $clientConfig = $this->getConfig();
86
87
        $url = ($clientConfig->isSsl() ? 'https://' : 'http://');
88
        if ($clientConfig->getUsername()) {
89
            $url .= $clientConfig->getUsername();
90
            if ($clientConfig->getPassword()) {
91
                $url .= ":{$clientConfig->getPassword()}";
92
            }
93
            $url .= '@';
94
        }
95
        $url .= $clientConfig->getHost();
96
        $url .= ":{$clientConfig->getPort()}";
97
        $url .= '/' . $this->getDatabaseName();
98
99
        $this->instance = CouchDBClient::create(
100
            [
101
                'dbname' => $this->getDatabaseName(),
102
                'url' => $url,
103
                'timeout' => $clientConfig->getTimeout(),
104
            ]
105
        );
106
107
        $this->createDatabase();
108
109
        return true;
110
    }
111
112
    /**
113
     * @return string
114
     */
115
    protected function getDatabaseName(): string
116
    {
117
        return $this->getConfig()->getDatabase() ?: self::COUCHDB_DEFAULT_DB_NAME;
118
    }
119
120
    /**
121
     * @return void
122
     */
123
    protected function createDatabase()
124
    {
125
        if (!in_array($this->getDatabaseName(), $this->instance->getAllDatabases(), true)) {
126
            $this->instance->createDatabase($this->getDatabaseName());
127
        }
128
    }
129
130
    protected function getCouchDbItemKey(CacheItemInterface $item)
131
    {
132
        return 'pfc_' . $item->getEncodedKey();
133
    }
134
135
    /**
136
     * @param CacheItemInterface $item
137
     * @return null|array
138
     * @throws PhpfastcacheDriverException
139
     */
140
    protected function driverRead(CacheItemInterface $item)
141
    {
142
        try {
143
            $response = $this->instance->findDocument($this->getCouchDbItemKey($item));
144
        } catch (CouchDBException $e) {
145
            throw new PhpfastcacheDriverException('Got error while trying to get a document: ' . $e->getMessage(), 0, $e);
146
        }
147
148
        if ($response->status === 404 || empty($response->body[ExtendedCacheItemPoolInterface::DRIVER_DATA_WRAPPER_INDEX])) {
149
            return null;
150
        }
151
152
        if ($response->status === 200) {
153
            return $this->decode($response->body);
154
        }
155
156
        throw new PhpfastcacheDriverException('Got unexpected HTTP status: ' . $response->status);
157
    }
158
159
    /**
160
     * @param CacheItemInterface $item
161
     * @return bool
162
     * @throws PhpfastcacheDriverException
163
     * @throws PhpfastcacheInvalidArgumentException
164
     */
165
    protected function driverWrite(CacheItemInterface $item): bool
166
    {
167
        /**
168
         * Check for Cross-Driver type confusion
169
         */
170
        if ($item instanceof Item) {
171
            try {
172
                $this->instance->putDocument(
173
                    $this->encodeDocument($this->driverPreWrap($item)),
174
                    $this->getCouchDbItemKey($item),
175
                    $this->getLatestDocumentRevision($this->getCouchDbItemKey($item))
176
                );
177
            } catch (CouchDBException $e) {
178
                throw new PhpfastcacheDriverException('Got error while trying to upsert a document: ' . $e->getMessage(), 0, $e);
179
            }
180
            return true;
181
        }
182
183
        throw new PhpfastcacheInvalidArgumentException('Cross-Driver type confusion detected');
184
    }
185
186
    /**
187
     * @return string|null
188
     */
189
    protected function getLatestDocumentRevision($docId)
190
    {
191
        $path = '/' . $this->getDatabaseName() . '/' . urlencode($docId);
192
193
        $response = $this->instance->getHttpClient()->request(
194
            'HEAD',
195
            $path,
196
            null,
197
            false
198
        );
199
        if (!empty($response->headers['etag'])) {
200
            return trim($response->headers['etag'], " '\"\t\n\r\0\x0B");
201
        }
202
203
        return null;
204
    }
205
206
    /********************
207
     *
208
     * PSR-6 Extended Methods
209
     *
210
     *******************/
211
212
    /**
213
     * @param CacheItemInterface $item
214
     * @return bool
215
     * @throws PhpfastcacheDriverException
216
     * @throws PhpfastcacheInvalidArgumentException
217
     */
218
    protected function driverDelete(CacheItemInterface $item): bool
219
    {
220
        /**
221
         * Check for Cross-Driver type confusion
222
         */
223
        if ($item instanceof Item) {
224
            try {
225
                $this->instance->deleteDocument($this->getCouchDbItemKey($item), $this->getLatestDocumentRevision($this->getCouchDbItemKey($item)));
226
            } catch (CouchDBException $e) {
227
                throw new PhpfastcacheDriverException('Got error while trying to delete a document: ' . $e->getMessage(), 0, $e);
228
            }
229
            return true;
230
        }
231
232
        throw new PhpfastcacheInvalidArgumentException('Cross-Driver type confusion detected');
233
    }
234
235
    /**
236
     * @return bool
237
     * @throws PhpfastcacheDriverException
238
     */
239
    protected function driverClear(): bool
240
    {
241
        try {
242
            $this->instance->deleteDatabase($this->getDatabaseName());
243
            $this->createDatabase();
244
        } catch (CouchDBException $e) {
245
            throw new PhpfastcacheDriverException('Got error while trying to delete and recreate the database: ' . $e->getMessage(), 0, $e);
246
        }
247
248
        return true;
249
    }
250
251
    /**
252
     * @param array $data
253
     * @return array
254
     */
255
    protected function encodeDocument(array $data): array
256
    {
257
        $data[ExtendedCacheItemPoolInterface::DRIVER_DATA_WRAPPER_INDEX] = $this->encode($data[ExtendedCacheItemPoolInterface::DRIVER_DATA_WRAPPER_INDEX]);
258
259
        return $data;
260
    }
261
262
    /**
263
     * Specific document decoder for Couchdb
264
     * since we dont store encoded version
265
     * for performance purposes
266
     *
267
     * @param $value
268
     * @return mixed
269
     * @throws \Exception
270
     */
271
    protected function decode($value)
272
    {
273
        $value[ExtendedCacheItemPoolInterface::DRIVER_DATA_WRAPPER_INDEX] = \unserialize($value[ExtendedCacheItemPoolInterface::DRIVER_DATA_WRAPPER_INDEX], ['allowed_classes' => true]);
274
275
        $value[ExtendedCacheItemPoolInterface::DRIVER_EDATE_WRAPPER_INDEX] = new \DateTime(
276
            $value[ExtendedCacheItemPoolInterface::DRIVER_EDATE_WRAPPER_INDEX]['date'],
277
            new \DateTimeZone($value[ExtendedCacheItemPoolInterface::DRIVER_EDATE_WRAPPER_INDEX]['timezone'])
278
        );
279
280
        if(isset($value[ExtendedCacheItemPoolInterface::DRIVER_CDATE_WRAPPER_INDEX])){
281
            $value[ExtendedCacheItemPoolInterface::DRIVER_CDATE_WRAPPER_INDEX] = new \DateTime(
282
                $value[ExtendedCacheItemPoolInterface::DRIVER_CDATE_WRAPPER_INDEX]['date'],
283
                new \DateTimeZone($value[ExtendedCacheItemPoolInterface::DRIVER_CDATE_WRAPPER_INDEX]['timezone'])
284
            );
285
        }
286
287
        if(isset($value[ExtendedCacheItemPoolInterface::DRIVER_MDATE_WRAPPER_INDEX])){
288
            $value[ExtendedCacheItemPoolInterface::DRIVER_MDATE_WRAPPER_INDEX] = new \DateTime(
289
                $value[ExtendedCacheItemPoolInterface::DRIVER_MDATE_WRAPPER_INDEX]['date'],
290
                new \DateTimeZone($value[ExtendedCacheItemPoolInterface::DRIVER_MDATE_WRAPPER_INDEX]['timezone'])
291
            );
292
        }
293
294
        return $value;
295
    }
296
}
297