198 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
		
		
			
		
	
	
			198 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| 
								 | 
							
								<?php
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								declare(strict_types=1);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								namespace Doctrine\Common\Cache;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use Couchbase\Bucket;
							 | 
						||
| 
								 | 
							
								use Couchbase\Document;
							 | 
						||
| 
								 | 
							
								use Couchbase\Exception;
							 | 
						||
| 
								 | 
							
								use RuntimeException;
							 | 
						||
| 
								 | 
							
								use function phpversion;
							 | 
						||
| 
								 | 
							
								use function serialize;
							 | 
						||
| 
								 | 
							
								use function sprintf;
							 | 
						||
| 
								 | 
							
								use function substr;
							 | 
						||
| 
								 | 
							
								use function time;
							 | 
						||
| 
								 | 
							
								use function unserialize;
							 | 
						||
| 
								 | 
							
								use function version_compare;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Couchbase ^2.3.0 cache provider.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								final class CouchbaseBucketCache extends CacheProvider
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    private const MINIMUM_VERSION = '2.3.0';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    private const KEY_NOT_FOUND = 13;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    private const MAX_KEY_LENGTH = 250;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    private const THIRTY_DAYS_IN_SECONDS = 2592000;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /** @var Bucket */
							 | 
						||
| 
								 | 
							
								    private $bucket;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    public function __construct(Bucket $bucket)
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        if (version_compare(phpversion('couchbase'), self::MINIMUM_VERSION) < 0) {
							 | 
						||
| 
								 | 
							
								            // Manager is required to flush cache and pull stats.
							 | 
						||
| 
								 | 
							
								            throw new RuntimeException(sprintf('ext-couchbase:^%s is required.', self::MINIMUM_VERSION));
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        $this->bucket = $bucket;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * {@inheritdoc}
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    protected function doFetch($id)
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        $id = $this->normalizeKey($id);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        try {
							 | 
						||
| 
								 | 
							
								            $document = $this->bucket->get($id);
							 | 
						||
| 
								 | 
							
								        } catch (Exception $e) {
							 | 
						||
| 
								 | 
							
								            return false;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if ($document instanceof Document && $document->value !== false) {
							 | 
						||
| 
								 | 
							
								            return unserialize($document->value);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return false;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * {@inheritdoc}
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    protected function doContains($id)
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        $id = $this->normalizeKey($id);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        try {
							 | 
						||
| 
								 | 
							
								            $document = $this->bucket->get($id);
							 | 
						||
| 
								 | 
							
								        } catch (Exception $e) {
							 | 
						||
| 
								 | 
							
								            return false;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if ($document instanceof Document) {
							 | 
						||
| 
								 | 
							
								            return ! $document->error;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return false;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * {@inheritdoc}
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    protected function doSave($id, $data, $lifeTime = 0)
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        $id = $this->normalizeKey($id);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        $lifeTime = $this->normalizeExpiry($lifeTime);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        try {
							 | 
						||
| 
								 | 
							
								            $encoded = serialize($data);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            $document = $this->bucket->upsert($id, $encoded, [
							 | 
						||
| 
								 | 
							
								                'expiry' => (int) $lifeTime,
							 | 
						||
| 
								 | 
							
								            ]);
							 | 
						||
| 
								 | 
							
								        } catch (Exception $e) {
							 | 
						||
| 
								 | 
							
								            return false;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if ($document instanceof Document) {
							 | 
						||
| 
								 | 
							
								            return ! $document->error;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return false;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * {@inheritdoc}
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    protected function doDelete($id)
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        $id = $this->normalizeKey($id);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        try {
							 | 
						||
| 
								 | 
							
								            $document = $this->bucket->remove($id);
							 | 
						||
| 
								 | 
							
								        } catch (Exception $e) {
							 | 
						||
| 
								 | 
							
								            return $e->getCode() === self::KEY_NOT_FOUND;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if ($document instanceof Document) {
							 | 
						||
| 
								 | 
							
								            return ! $document->error;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return false;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * {@inheritdoc}
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    protected function doFlush()
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        $manager = $this->bucket->manager();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // Flush does not return with success or failure, and must be enabled per bucket on the server.
							 | 
						||
| 
								 | 
							
								        // Store a marker item so that we will know if it was successful.
							 | 
						||
| 
								 | 
							
								        $this->doSave(__METHOD__, true, 60);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        $manager->flush();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if ($this->doContains(__METHOD__)) {
							 | 
						||
| 
								 | 
							
								            $this->doDelete(__METHOD__);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            return false;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return true;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * {@inheritdoc}
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    protected function doGetStats()
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        $manager          = $this->bucket->manager();
							 | 
						||
| 
								 | 
							
								        $stats            = $manager->info();
							 | 
						||
| 
								 | 
							
								        $nodes            = $stats['nodes'];
							 | 
						||
| 
								 | 
							
								        $node             = $nodes[0];
							 | 
						||
| 
								 | 
							
								        $interestingStats = $node['interestingStats'];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return [
							 | 
						||
| 
								 | 
							
								            Cache::STATS_HITS   => $interestingStats['get_hits'],
							 | 
						||
| 
								 | 
							
								            Cache::STATS_MISSES => $interestingStats['cmd_get'] - $interestingStats['get_hits'],
							 | 
						||
| 
								 | 
							
								            Cache::STATS_UPTIME => $node['uptime'],
							 | 
						||
| 
								 | 
							
								            Cache::STATS_MEMORY_USAGE     => $interestingStats['mem_used'],
							 | 
						||
| 
								 | 
							
								            Cache::STATS_MEMORY_AVAILABLE => $node['memoryFree'],
							 | 
						||
| 
								 | 
							
								        ];
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    private function normalizeKey(string $id) : string
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        $normalized = substr($id, 0, self::MAX_KEY_LENGTH);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if ($normalized === false) {
							 | 
						||
| 
								 | 
							
								            return $id;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return $normalized;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * Expiry treated as a unix timestamp instead of an offset if expiry is greater than 30 days.
							 | 
						||
| 
								 | 
							
								     *
							 | 
						||
| 
								 | 
							
								     * @src https://developer.couchbase.com/documentation/server/4.1/developer-guide/expiry.html
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    private function normalizeExpiry(int $expiry) : int
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        if ($expiry > self::THIRTY_DAYS_IN_SECONDS) {
							 | 
						||
| 
								 | 
							
								            return time() + $expiry;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return $expiry;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 |