1<?php 2/** 3 * Zend Framework (http://framework.zend.com/) 4 * 5 * @link http://github.com/zendframework/zf2 for the canonical source repository 6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 7 * @license http://framework.zend.com/license/new-bsd New BSD License 8 */ 9 10namespace Zend\Cache\Storage\Adapter; 11 12use MongoCollection as MongoResource; 13use MongoDate; 14use MongoException as MongoResourceException; 15use stdClass; 16use Zend\Cache\Exception; 17use Zend\Cache\Storage\Capabilities; 18use Zend\Cache\Storage\FlushableInterface; 19 20class MongoDb extends AbstractAdapter implements FlushableInterface 21{ 22 /** 23 * Has this instance be initialized 24 * 25 * @var bool 26 */ 27 private $initialized = false; 28 29 /** 30 * the mongodb resource manager 31 * 32 * @var null|MongoDbResourceManager 33 */ 34 private $resourceManager; 35 36 /** 37 * The mongodb resource id 38 * 39 * @var null|string 40 */ 41 private $resourceId; 42 43 /** 44 * The namespace prefix 45 * 46 * @var string 47 */ 48 private $namespacePrefix = ''; 49 50 /** 51 * {@inheritDoc} 52 * 53 * @throws Exception\ExtensionNotLoadedException 54 */ 55 public function __construct($options = null) 56 { 57 if (!class_exists('Mongo') || !class_exists('MongoClient')) { 58 throw new Exception\ExtensionNotLoadedException('MongoDb extension not loaded or Mongo polyfill not included'); 59 } 60 61 parent::__construct($options); 62 63 $initialized = & $this->initialized; 64 65 $this->getEventManager()->attach( 66 'option', 67 function () use (& $initialized) { 68 $initialized = false; 69 } 70 ); 71 } 72 73 /** 74 * get mongodb resource 75 * 76 * @return MongoResource 77 */ 78 private function getMongoDbResource() 79 { 80 if (! $this->initialized) { 81 $options = $this->getOptions(); 82 83 $this->resourceManager = $options->getResourceManager(); 84 $this->resourceId = $options->getResourceId(); 85 $namespace = $options->getNamespace(); 86 $this->namespacePrefix = ($namespace === '' ? '' : $namespace . $options->getNamespaceSeparator()); 87 $this->initialized = true; 88 } 89 90 return $this->resourceManager->getResource($this->resourceId); 91 } 92 93 /** 94 * {@inheritDoc} 95 */ 96 public function setOptions($options) 97 { 98 return parent::setOptions($options instanceof MongoDbOptions ? $options : new MongoDbOptions($options)); 99 } 100 101 /** 102 * Get options. 103 * 104 * @return MongoDbOptions 105 * @see setOptions() 106 */ 107 public function getOptions() 108 { 109 return $this->options; 110 } 111 112 /** 113 * {@inheritDoc} 114 * 115 * @throws Exception\RuntimeException 116 */ 117 protected function internalGetItem(& $normalizedKey, & $success = null, & $casToken = null) 118 { 119 $result = $this->fetchFromCollection($normalizedKey); 120 $success = false; 121 122 if (null === $result) { 123 return; 124 } 125 126 if (isset($result['expires'])) { 127 if (! $result['expires'] instanceof MongoDate) { 128 throw new Exception\RuntimeException(sprintf( 129 "The found item _id '%s' for key '%s' is not a valid cache item" 130 . ": the field 'expired' isn't an instance of MongoDate, '%s' found instead", 131 (string) $result['_id'], 132 $this->namespacePrefix . $normalizedKey, 133 is_object($result['expires']) ? get_class($result['expires']) : gettype($result['expires']) 134 )); 135 } 136 137 if ($result['expires']->sec < time()) { 138 $this->internalRemoveItem($key); 139 140 return; 141 } 142 } 143 144 if (! array_key_exists('value', $result)) { 145 throw new Exception\RuntimeException(sprintf( 146 "The found item _id '%s' for key '%s' is not a valid cache item: missing the field 'value'", 147 (string) $result['_id'], 148 $this->namespacePrefix . $normalizedKey 149 )); 150 } 151 152 $success = true; 153 154 return $casToken = $result['value']; 155 } 156 157 /** 158 * {@inheritDoc} 159 * 160 * @throws Exception\RuntimeException 161 */ 162 protected function internalSetItem(& $normalizedKey, & $value) 163 { 164 $mongo = $this->getMongoDbResource(); 165 $key = $this->namespacePrefix . $normalizedKey; 166 $ttl = $this->getOptions()->getTTl(); 167 $expires = null; 168 $cacheItem = array( 169 'key' => $key, 170 'value' => $value, 171 ); 172 173 if ($ttl > 0) { 174 $expiresMicro = microtime(true) + $ttl; 175 $expiresSecs = (int) $expiresMicro; 176 $cacheItem['expires'] = new MongoDate($expiresSecs, $expiresMicro - $expiresSecs); 177 } 178 179 try { 180 $mongo->remove(array('key' => $key)); 181 182 $result = $mongo->insert($cacheItem); 183 } catch (MongoResourceException $e) { 184 throw new Exception\RuntimeException($e->getMessage(), $e->getCode(), $e); 185 } 186 187 return null !== $result && ((double) 1) === $result['ok']; 188 } 189 190 /** 191 * {@inheritDoc} 192 * 193 * @throws Exception\RuntimeException 194 */ 195 protected function internalRemoveItem(& $normalizedKey) 196 { 197 try { 198 $result = $this->getMongoDbResource()->remove(array('key' => $this->namespacePrefix . $normalizedKey)); 199 } catch (MongoResourceException $e) { 200 throw new Exception\RuntimeException($e->getMessage(), $e->getCode(), $e); 201 } 202 203 return false !== $result 204 && ((double) 1) === $result['ok'] 205 && $result['n'] > 0; 206 } 207 208 /** 209 * {@inheritDoc} 210 */ 211 public function flush() 212 { 213 $result = $this->getMongoDbResource()->drop(); 214 215 return ((double) 1) === $result['ok']; 216 } 217 218 /** 219 * {@inheritDoc} 220 */ 221 protected function internalGetCapabilities() 222 { 223 if ($this->capabilities) { 224 return $this->capabilities; 225 } 226 227 return $this->capabilities = new Capabilities( 228 $this, 229 $this->capabilityMarker = new stdClass(), 230 array( 231 'supportedDatatypes' => array( 232 'NULL' => true, 233 'boolean' => true, 234 'integer' => true, 235 'double' => true, 236 'string' => true, 237 'array' => true, 238 'object' => false, 239 'resource' => false, 240 ), 241 'supportedMetadata' => array( 242 '_id', 243 ), 244 'minTtl' => 0, 245 'maxTtl' => 0, 246 'staticTtl' => true, 247 'ttlPrecision' => 1, 248 'useRequestTime' => false, 249 'expiredRead' => false, 250 'maxKeyLength' => 255, 251 'namespaceIsPrefix' => true, 252 ) 253 ); 254 } 255 256 /** 257 * {@inheritDoc} 258 * 259 * @throws Exception\ExceptionInterface 260 */ 261 protected function internalGetMetadata(& $normalizedKey) 262 { 263 $result = $this->fetchFromCollection($normalizedKey); 264 265 return null !== $result ? array('_id' => $result['_id']) : false; 266 } 267 268 /** 269 * Return raw records from MongoCollection 270 * 271 * @param string $normalizedKey 272 * 273 * @return array|null 274 * 275 * @throws Exception\RuntimeException 276 */ 277 private function fetchFromCollection(& $normalizedKey) 278 { 279 try { 280 return $this->getMongoDbResource()->findOne(array('key' => $this->namespacePrefix . $normalizedKey)); 281 } catch (MongoResourceException $e) { 282 throw new Exception\RuntimeException($e->getMessage(), $e->getCode(), $e); 283 } 284 } 285} 286