299 lines
9.2 KiB
PHP

<?php
/**
*
* This file is part of phpFastCache.
*
* @license MIT License (MIT)
*
* For full copyright and license information, please see the docs/CREDITS.txt file.
*
* @author Khoa Bui (khoaofgod) <khoaofgod@gmail.com> https://www.phpfastcache.com
* @author Georges.L (Geolim4) <contact@geolim4.com>
*
*/
declare(strict_types=1);
namespace Phpfastcache\Drivers\Couchdb;
use Doctrine\CouchDB\{CouchDBClient, CouchDBException, HTTP\HTTPException};
use Phpfastcache\Cluster\AggregatablePoolInterface;
use Phpfastcache\Core\Pool\{DriverBaseTrait, ExtendedCacheItemPoolInterface};
use Phpfastcache\Entities\DriverStatistic;
use Phpfastcache\Exceptions\{PhpfastcacheDriverException, PhpfastcacheInvalidArgumentException, PhpfastcacheLogicException};
use Psr\Cache\CacheItemInterface;
/**
* Class Driver
* @package phpFastCache\Drivers
* @property CouchdbClient $instance Instance of driver service
* @property Config $config Config object
* @method Config getConfig() Return the config object
*/
class Driver implements ExtendedCacheItemPoolInterface, AggregatablePoolInterface
{
public const COUCHDB_DEFAULT_DB_NAME = 'phpfastcache'; // Public because used in config
use DriverBaseTrait;
/**
* @return bool
*/
public function driverCheck(): bool
{
return class_exists(CouchDBClient::class);
}
/**
* @return string
*/
public function getHelp(): string
{
return <<<HELP
<p>
To install the Couchdb HTTP client library via Composer:
<code>composer require "doctrine/couchdb" "@dev"</code>
</p>
HELP;
}
/**
* @return DriverStatistic
*/
public function getStats(): DriverStatistic
{
$info = $this->instance->getDatabaseInfo();
return (new DriverStatistic())
->setSize($info['sizes']['active'] ?? 0)
->setRawData($info)
->setData(implode(', ', array_keys($this->itemInstances)))
->setInfo('Couchdb version ' . $this->instance->getVersion() . "\n For more information see RawData.");
}
/**
* @return bool
* @throws PhpfastcacheLogicException
*/
protected function driverConnect(): bool
{
if ($this->instance instanceof CouchDBClient) {
throw new PhpfastcacheLogicException('Already connected to Couchdb server');
}
$clientConfig = $this->getConfig();
$url = ($clientConfig->isSsl() ? 'https://' : 'http://');
if ($clientConfig->getUsername()) {
$url .= $clientConfig->getUsername();
if ($clientConfig->getPassword()) {
$url .= ":{$clientConfig->getPassword()}";
}
$url .= '@';
}
$url .= $clientConfig->getHost();
$url .= ":{$clientConfig->getPort()}";
$url .= '/' . \urlencode($this->getDatabaseName());
$this->instance = CouchDBClient::create(
[
'dbname' => $this->getDatabaseName(),
'url' => $url,
'timeout' => $clientConfig->getTimeout(),
]
);
$this->createDatabase();
return true;
}
/**
* @return string
*/
protected function getDatabaseName(): string
{
return $this->getConfig()->getDatabase() ?: self::COUCHDB_DEFAULT_DB_NAME;
}
/**
* @return void
*/
protected function createDatabase()
{
try{
$this->instance->getDatabaseInfo($this->getDatabaseName());
} catch(HTTPException $e){
$this->instance->createDatabase($this->getDatabaseName());
}
}
protected function getCouchDbItemKey(CacheItemInterface $item)
{
return 'pfc_' . $item->getEncodedKey();
}
/**
* @param CacheItemInterface $item
* @return null|array
* @throws PhpfastcacheDriverException
*/
protected function driverRead(CacheItemInterface $item)
{
try {
$response = $this->instance->findDocument($this->getCouchDbItemKey($item));
} catch (CouchDBException $e) {
throw new PhpfastcacheDriverException('Got error while trying to get a document: ' . $e->getMessage(), 0, $e);
}
if ($response->status === 404 || empty($response->body[ExtendedCacheItemPoolInterface::DRIVER_DATA_WRAPPER_INDEX])) {
return null;
}
if ($response->status === 200) {
return $this->decode($response->body);
}
throw new PhpfastcacheDriverException('Got unexpected HTTP status: ' . $response->status);
}
/**
* @param CacheItemInterface $item
* @return bool
* @throws PhpfastcacheDriverException
* @throws PhpfastcacheInvalidArgumentException
*/
protected function driverWrite(CacheItemInterface $item): bool
{
/**
* Check for Cross-Driver type confusion
*/
if ($item instanceof Item) {
try {
$this->instance->putDocument(
$this->encodeDocument($this->driverPreWrap($item)),
$this->getCouchDbItemKey($item),
$this->getLatestDocumentRevision($this->getCouchDbItemKey($item))
);
} catch (CouchDBException $e) {
throw new PhpfastcacheDriverException('Got error while trying to upsert a document: ' . $e->getMessage(), 0, $e);
}
return true;
}
throw new PhpfastcacheInvalidArgumentException('Cross-Driver type confusion detected');
}
/**
* @return string|null
*/
protected function getLatestDocumentRevision($docId)
{
$path = '/' . \urlencode($this->getDatabaseName()) . '/' . urlencode($docId);
$response = $this->instance->getHttpClient()->request(
'HEAD',
$path,
null,
false
);
if (!empty($response->headers['etag'])) {
return trim($response->headers['etag'], " '\"\t\n\r\0\x0B");
}
return null;
}
/********************
*
* PSR-6 Extended Methods
*
*******************/
/**
* @param CacheItemInterface $item
* @return bool
* @throws PhpfastcacheDriverException
* @throws PhpfastcacheInvalidArgumentException
*/
protected function driverDelete(CacheItemInterface $item): bool
{
/**
* Check for Cross-Driver type confusion
*/
if ($item instanceof Item) {
try {
$this->instance->deleteDocument($this->getCouchDbItemKey($item), $this->getLatestDocumentRevision($this->getCouchDbItemKey($item)));
} catch (CouchDBException $e) {
throw new PhpfastcacheDriverException('Got error while trying to delete a document: ' . $e->getMessage(), 0, $e);
}
return true;
}
throw new PhpfastcacheInvalidArgumentException('Cross-Driver type confusion detected');
}
/**
* @return bool
* @throws PhpfastcacheDriverException
*/
protected function driverClear(): bool
{
try {
$this->instance->deleteDatabase($this->getDatabaseName());
$this->createDatabase();
} catch (CouchDBException $e) {
throw new PhpfastcacheDriverException('Got error while trying to delete and recreate the database: ' . $e->getMessage(), 0, $e);
}
return true;
}
/**
* @param array $data
* @return array
*/
protected function encodeDocument(array $data): array
{
$data[ExtendedCacheItemPoolInterface::DRIVER_DATA_WRAPPER_INDEX] = $this->encode($data[ExtendedCacheItemPoolInterface::DRIVER_DATA_WRAPPER_INDEX]);
return $data;
}
/**
* Specific document decoder for Couchdb
* since we dont store encoded version
* for performance purposes
*
* @param $value
* @return mixed
* @throws \Exception
*/
protected function decode($value)
{
$value[ExtendedCacheItemPoolInterface::DRIVER_DATA_WRAPPER_INDEX] = \unserialize($value[ExtendedCacheItemPoolInterface::DRIVER_DATA_WRAPPER_INDEX], ['allowed_classes' => true]);
$value[ExtendedCacheItemPoolInterface::DRIVER_EDATE_WRAPPER_INDEX] = new \DateTime(
$value[ExtendedCacheItemPoolInterface::DRIVER_EDATE_WRAPPER_INDEX]['date'],
new \DateTimeZone($value[ExtendedCacheItemPoolInterface::DRIVER_EDATE_WRAPPER_INDEX]['timezone'])
);
if(isset($value[ExtendedCacheItemPoolInterface::DRIVER_CDATE_WRAPPER_INDEX])){
$value[ExtendedCacheItemPoolInterface::DRIVER_CDATE_WRAPPER_INDEX] = new \DateTime(
$value[ExtendedCacheItemPoolInterface::DRIVER_CDATE_WRAPPER_INDEX]['date'],
new \DateTimeZone($value[ExtendedCacheItemPoolInterface::DRIVER_CDATE_WRAPPER_INDEX]['timezone'])
);
}
if(isset($value[ExtendedCacheItemPoolInterface::DRIVER_MDATE_WRAPPER_INDEX])){
$value[ExtendedCacheItemPoolInterface::DRIVER_MDATE_WRAPPER_INDEX] = new \DateTime(
$value[ExtendedCacheItemPoolInterface::DRIVER_MDATE_WRAPPER_INDEX]['date'],
new \DateTimeZone($value[ExtendedCacheItemPoolInterface::DRIVER_MDATE_WRAPPER_INDEX]['timezone'])
);
}
return $value;
}
}