324 lines
11 KiB
PHP
324 lines
11 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> http://www.phpfastcache.com
|
|
* @author Georges.L (Geolim4) <contact@geolim4.com>
|
|
* @author Fabio Covolo Mazzo (fabiocmazzo) <fabiomazzo@gmail.com>
|
|
*
|
|
*/
|
|
|
|
namespace phpFastCache\Drivers\Mongodb;
|
|
|
|
use LogicException;
|
|
use MongoDB\BSON\Binary;
|
|
use MongoDB\BSON\UTCDateTime;
|
|
use MongoDB\Client;
|
|
use MongoDB\Collection;
|
|
use MongoDB\DeleteResult;
|
|
use MongoDB\Driver\Command;
|
|
use MongoDB\Driver\Exception\Exception as MongoDBException;
|
|
use phpFastCache\Core\Pool\DriverBaseTrait;
|
|
use phpFastCache\Core\Pool\ExtendedCacheItemPoolInterface;
|
|
use phpFastCache\Entities\DriverStatistic;
|
|
use phpFastCache\Exceptions\phpFastCacheDriverCheckException;
|
|
use phpFastCache\Exceptions\phpFastCacheDriverException;
|
|
use phpFastCache\Exceptions\phpFastCacheInvalidArgumentException;
|
|
use Psr\Cache\CacheItemInterface;
|
|
|
|
/**
|
|
* Class Driver
|
|
* @package phpFastCache\Drivers
|
|
* @property MongodbManager $instance Instance of driver service
|
|
*/
|
|
class Driver implements ExtendedCacheItemPoolInterface
|
|
{
|
|
use DriverBaseTrait;
|
|
|
|
/**
|
|
* @var Database
|
|
*/
|
|
public $database;
|
|
|
|
/**
|
|
* @var Collection
|
|
*/
|
|
public $collection;
|
|
|
|
/**
|
|
* Driver constructor.
|
|
* @param array $config
|
|
* @throws phpFastCacheDriverCheckException
|
|
*/
|
|
public function __construct(array $config = [])
|
|
{
|
|
$this->setup($config);
|
|
|
|
if (!$this->driverCheck()) {
|
|
throw new phpFastCacheDriverCheckException(sprintf(self::DRIVER_CHECK_FAILURE, $this->getDriverName()));
|
|
} else {
|
|
$this->driverConnect();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function driverCheck()
|
|
{
|
|
// Get if the (new) extension is installed by checking its class
|
|
$mongoExtensionExists = class_exists('MongoDB\Driver\Manager');
|
|
|
|
if (!$mongoExtensionExists && class_exists('MongoClient')) {
|
|
trigger_error('This driver is used to support the pecl MongoDb extension with mongo-php-library.
|
|
For MongoDb with Mongo PECL support use Mongo Driver.', E_USER_ERROR);
|
|
}
|
|
|
|
return $mongoExtensionExists && class_exists('MongoDB\Collection');
|
|
}
|
|
|
|
/**
|
|
* @param \Psr\Cache\CacheItemInterface $item
|
|
* @return mixed
|
|
* @throws phpFastCacheInvalidArgumentException
|
|
* @throws phpFastCacheDriverException
|
|
*/
|
|
protected function driverWrite(CacheItemInterface $item)
|
|
{
|
|
/**
|
|
* Check for Cross-Driver type confusion
|
|
*/
|
|
if ($item instanceof Item) {
|
|
try {
|
|
$set = [
|
|
self::DRIVER_DATA_WRAPPER_INDEX => new Binary($this->encode($item->get()), Binary::TYPE_GENERIC),
|
|
self::DRIVER_TAGS_WRAPPER_INDEX => new Binary($this->encode($item->getTags()), Binary::TYPE_GENERIC),
|
|
self::DRIVER_EDATE_WRAPPER_INDEX => ($item->getTtl() > 0 ? new UTCDateTime((time() + $item->getTtl()) * 1000) : new UTCDateTime(time() * 1000)),
|
|
];
|
|
|
|
if(!empty($this->config[ 'itemDetailedDate' ])){
|
|
$set += [
|
|
self::DRIVER_MDATE_WRAPPER_INDEX => ($item->getModificationDate() ? new UTCDateTime(($item->getModificationDate()->getTimestamp()) * 1000) : new UTCDateTime(time() * 1000)),
|
|
self::DRIVER_CDATE_WRAPPER_INDEX => ($item->getCreationDate() ? new UTCDateTime(($item->getCreationDate()->getTimestamp()) * 1000) : new UTCDateTime(time() * 1000)),
|
|
];
|
|
}
|
|
|
|
$result = (array)$this->getCollection()->updateOne(
|
|
['_id' => $item->getEncodedKey()],
|
|
['$set' => $set],
|
|
['upsert' => true, 'multiple' => false]
|
|
);
|
|
} catch (MongoDBException $e) {
|
|
throw new phpFastCacheDriverException('Got an exception while trying to write data to MongoDB server', null, $e);
|
|
}
|
|
|
|
return isset($result[ 'ok' ]) ? $result[ 'ok' ] == 1 : true;
|
|
} else {
|
|
throw new phpFastCacheInvalidArgumentException('Cross-Driver type confusion detected');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param \Psr\Cache\CacheItemInterface $item
|
|
* @return null|array
|
|
*/
|
|
protected function driverRead(CacheItemInterface $item)
|
|
{
|
|
$document = $this->getCollection()->findOne(['_id' => $item->getEncodedKey()]);
|
|
|
|
if ($document) {
|
|
$return = [
|
|
self::DRIVER_DATA_WRAPPER_INDEX => $this->decode($document[ self::DRIVER_DATA_WRAPPER_INDEX ]->getData()),
|
|
self::DRIVER_TAGS_WRAPPER_INDEX => $this->decode($document[ self::DRIVER_TAGS_WRAPPER_INDEX ]->getData()),
|
|
self::DRIVER_EDATE_WRAPPER_INDEX => (new \DateTime())->setTimestamp($document[ self::DRIVER_EDATE_WRAPPER_INDEX ]->toDateTime()->getTimestamp()),
|
|
];
|
|
|
|
if(!empty($this->config[ 'itemDetailedDate' ])){
|
|
$return += [
|
|
self::DRIVER_MDATE_WRAPPER_INDEX => (new \DateTime())->setTimestamp($document[ self::DRIVER_MDATE_WRAPPER_INDEX ]->toDateTime()->getTimestamp()),
|
|
self::DRIVER_CDATE_WRAPPER_INDEX => (new \DateTime())->setTimestamp($document[ self::DRIVER_CDATE_WRAPPER_INDEX ]->toDateTime()->getTimestamp()),
|
|
];
|
|
}
|
|
|
|
return $return;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param \Psr\Cache\CacheItemInterface $item
|
|
* @return bool
|
|
* @throws phpFastCacheInvalidArgumentException
|
|
*/
|
|
protected function driverDelete(CacheItemInterface $item)
|
|
{
|
|
/**
|
|
* Check for Cross-Driver type confusion
|
|
*/
|
|
if ($item instanceof Item) {
|
|
/**
|
|
* @var DeleteResult $deletionResult
|
|
*/
|
|
$deletionResult = $this->getCollection()->deleteOne(['_id' => $item->getEncodedKey()]);
|
|
|
|
return $deletionResult->isAcknowledged();
|
|
} else {
|
|
throw new phpFastCacheInvalidArgumentException('Cross-Driver type confusion detected');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
protected function driverClear()
|
|
{
|
|
return $this->collection->deleteMany([])->isAcknowledged();
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
* @throws MongodbException
|
|
* @throws LogicException
|
|
*/
|
|
protected function driverConnect()
|
|
{
|
|
if ($this->instance instanceof \MongoDB\Driver\Manager) {
|
|
throw new LogicException('Already connected to Mongodb server');
|
|
} else {
|
|
$timeout = isset($this->config[ 'timeout' ]) ? $this->config[ 'timeout' ] * 1000 : 3000;
|
|
$collectionName = isset($this->config[ 'collectionName' ]) ? $this->config[ 'collectionName' ] : 'Cache';
|
|
$databaseName = isset($this->config[ 'databaseName' ]) ? $this->config[ 'databaseName' ] : 'phpFastCache';
|
|
|
|
$this->instance = $this->instance ?: (new Client($this->buildConnectionURI($databaseName), ['connectTimeoutMS' => $timeout]));
|
|
$this->database = $this->database ?: $this->instance->selectDatabase($databaseName);
|
|
|
|
if (!$this->collectionExists($collectionName)) {
|
|
$this->database->createCollection($collectionName);
|
|
}
|
|
|
|
$this->collection = $this->database->selectCollection($collectionName);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if a collection name exists on the Mongo database.
|
|
*
|
|
* @param string $collectionName The collection name to check.
|
|
*
|
|
* @return bool True if the collection exists, false if not.
|
|
*/
|
|
protected function collectionExists($collectionName)
|
|
{
|
|
foreach ($this->database->listCollections() as $collection) {
|
|
if ($collection->getName() == $collectionName) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Builds the connection URI from the given parameters.
|
|
*
|
|
* @param string $databaseName
|
|
* @return string The connection URI.
|
|
*/
|
|
protected function buildConnectionURI($databaseName = '')
|
|
{
|
|
$host = isset($this->config[ 'host' ]) ? $this->config[ 'host' ] : '127.0.0.1';
|
|
$port = isset($this->config[ 'port' ]) ? $this->config[ 'port' ] : '27017';
|
|
$password = isset($this->config[ 'password' ]) ? $this->config[ 'password' ] : '';
|
|
$username = isset($this->config[ 'username' ]) ? $this->config[ 'username' ] : '';
|
|
|
|
$parts = [
|
|
'mongodb://',
|
|
($username ?: ''),
|
|
($password ? ":{$password}" : ''),
|
|
($username ? '@' : ''),
|
|
$host,
|
|
($port != '27017' ? ":{$port}" : ''),
|
|
($databaseName ? "/{$databaseName}" : '')
|
|
];
|
|
|
|
return implode('', $parts);
|
|
}
|
|
|
|
/**
|
|
* @return Collection
|
|
*/
|
|
protected function getCollection()
|
|
{
|
|
return $this->collection;
|
|
}
|
|
|
|
/********************
|
|
*
|
|
* PSR-6 Extended Methods
|
|
*
|
|
*******************/
|
|
|
|
/**
|
|
* @return DriverStatistic
|
|
*/
|
|
public function getStats()
|
|
{
|
|
$serverStats = $this->instance->getManager()->executeCommand('phpFastCache', new Command([
|
|
'serverStatus' => 1,
|
|
'recordStats' => 0,
|
|
'repl' => 0,
|
|
'metrics' => 0,
|
|
]))->toArray()[ 0 ];
|
|
|
|
$collectionStats = $this->instance->getManager()->executeCommand('phpFastCache', new Command([
|
|
'collStats' => (isset($this->config[ 'collectionName' ]) ? $this->config[ 'collectionName' ] : 'Cache'),
|
|
'verbose' => true,
|
|
]))->toArray()[ 0 ];
|
|
|
|
$array_filter_recursive = function ($array, callable $callback = null) use (&$array_filter_recursive) {
|
|
$array = $callback($array);
|
|
|
|
if (is_object($array) || is_array($array)) {
|
|
foreach ($array as &$value) {
|
|
$value = call_user_func($array_filter_recursive, $value, $callback);
|
|
}
|
|
}
|
|
|
|
return $array;
|
|
};
|
|
|
|
$callback = function ($item) {
|
|
/**
|
|
* Remove unserializable properties
|
|
*/
|
|
if ($item instanceof \MongoDB\BSON\UTCDateTime) {
|
|
return (string)$item;
|
|
}
|
|
return $item;
|
|
};
|
|
|
|
$serverStats = $array_filter_recursive($serverStats, $callback);
|
|
$collectionStats = $array_filter_recursive($collectionStats, $callback);
|
|
|
|
$stats = (new DriverStatistic())
|
|
->setInfo('MongoDB version ' . $serverStats->version . ', Uptime (in days): ' . round($serverStats->uptime / 86400,
|
|
1) . "\n For more information see RawData.")
|
|
->setSize($collectionStats->size)
|
|
->setData(implode(', ', array_keys($this->itemInstances)))
|
|
->setRawData([
|
|
'serverStatus' => $serverStats,
|
|
'collStats' => $collectionStats,
|
|
]);
|
|
|
|
return $stats;
|
|
}
|
|
}
|