3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
10 namespace Zend\Cache\Storage\Adapter
;
14 use Zend\Cache\Exception
;
15 use Zend\Cache\Storage\AvailableSpaceCapableInterface
;
16 use Zend\Cache\Storage\Capabilities
;
17 use Zend\Cache\Storage\ClearByNamespaceInterface
;
18 use Zend\Cache\Storage\ClearByPrefixInterface
;
19 use Zend\Cache\Storage\FlushableInterface
;
20 use Zend\Cache\Storage\IterableInterface
;
21 use Zend\Cache\Storage\OptimizableInterface
;
22 use Zend\Cache\Storage\TotalSpaceCapableInterface
;
23 use Zend\Stdlib\ErrorHandler
;
25 class Dba
extends AbstractAdapter
implements
26 AvailableSpaceCapableInterface
,
27 ClearByNamespaceInterface
,
28 ClearByPrefixInterface
,
32 TotalSpaceCapableInterface
35 * The DBA resource handle
42 * Buffered total space in bytes
46 protected $totalSpace;
51 * @param null|array|Traversable|DbaOptions $options
52 * @throws Exception\ExceptionInterface
54 public function __construct($options = null)
56 if (! extension_loaded('dba')) {
57 throw new Exception\
ExtensionNotLoadedException('Missing ext/dba');
60 parent
::__construct($options);
66 * Closes an open dba resource
68 * @see AbstractAdapter::__destruct()
71 public function __destruct()
83 * @param array|Traversable|DbaOptions $options
87 public function setOptions($options)
89 if (! $options instanceof DbaOptions
) {
90 $options = new DbaOptions($options);
93 return parent
::setOptions($options);
102 public function getOptions()
104 if (! $this->options
) {
105 $this->setOptions(new DbaOptions());
107 return $this->options
;
110 /* TotalSpaceCapableInterface */
113 * Get total space in bytes
117 public function getTotalSpace()
119 if ($this->totalSpace
=== null) {
120 $pathname = $this->getOptions()->getPathname();
122 if ($pathname === '') {
123 throw new Exception\
LogicException('No pathname to database file');
126 ErrorHandler
::start();
127 $total = disk_total_space(dirname($pathname));
128 $error = ErrorHandler
::stop();
129 if ($total === false) {
130 throw new Exception\
RuntimeException("Can't detect total space of '{$pathname}'", 0, $error);
132 $this->totalSpace
= $total;
134 // clean total space buffer on change pathname
135 $events = $this->getEventManager();
137 $totalSpace = & $this->totalSpace
;
138 $callback = function ($event) use (& $events, & $handle, & $totalSpace) {
139 $params = $event->getParams();
140 if (isset($params['pathname'])) {
142 $events->detach($handle);
145 $events->attach('option', $callback);
148 return $this->totalSpace
;
151 /* AvailableSpaceCapableInterface */
154 * Get available space in bytes
158 public function getAvailableSpace()
160 $pathname = $this->getOptions()->getPathname();
162 if ($pathname === '') {
163 throw new Exception\
LogicException('No pathname to database file');
166 ErrorHandler
::start();
167 $avail = disk_free_space(dirname($pathname));
168 $error = ErrorHandler
::stop();
169 if ($avail === false) {
170 throw new Exception\
RuntimeException("Can't detect free space of '{$pathname}'", 0, $error);
176 /* FlushableInterface */
179 * Flush the whole storage
183 public function flush()
185 $pathname = $this->getOptions()->getPathname();
187 if ($pathname === '') {
188 throw new Exception\
LogicException('No pathname to database file');
191 if (file_exists($pathname)) {
192 // close the dba file before delete
193 // and reopen (create) on next use
196 ErrorHandler
::start();
197 $result = unlink($pathname);
198 $error = ErrorHandler
::stop();
200 throw new Exception\
RuntimeException("unlink('{$pathname}') failed", 0, $error);
207 /* ClearByNamespaceInterface */
210 * Remove items by given namespace
212 * @param string $namespace
215 public function clearByNamespace($namespace)
217 $namespace = (string) $namespace;
218 if ($namespace === '') {
219 throw new Exception\
InvalidArgumentException('No namespace given');
222 $prefix = $namespace . $this->getOptions()->getNamespaceSeparator();
223 $prefixl = strlen($prefix);
229 // Workaround for PHP-Bug #62491 & #62492
231 $internalKey = dba_firstkey($this->handle
);
232 while ($internalKey !== false && $internalKey !== null) {
233 if (substr($internalKey, 0, $prefixl) === $prefix) {
234 $result = dba_delete($internalKey, $this->handle
) && $result;
237 $internalKey = dba_nextkey($this->handle
);
244 /* ClearByPrefixInterface */
247 * Remove items matching given prefix
249 * @param string $prefix
252 public function clearByPrefix($prefix)
254 $prefix = (string) $prefix;
255 if ($prefix === '') {
256 throw new Exception\
InvalidArgumentException('No prefix given');
259 $options = $this->getOptions();
260 $namespace = $options->getNamespace();
261 $prefix = ($namespace === '') ?
'' : $namespace . $options->getNamespaceSeparator() . $prefix;
262 $prefixL = strlen($prefix);
267 // Workaround for PHP-Bug #62491 & #62492
270 $internalKey = dba_firstkey($this->handle
);
271 while ($internalKey !== false && $internalKey !== null) {
272 if (substr($internalKey, 0, $prefixL) === $prefix) {
273 $result = dba_delete($internalKey, $this->handle
) && $result;
277 $internalKey = dba_nextkey($this->handle
);
284 /* IterableInterface */
287 * Get the storage iterator
289 * @return DbaIterator
291 public function getIterator()
293 $options = $this->getOptions();
294 $namespace = $options->getNamespace();
295 $prefix = ($namespace === '') ?
'' : $namespace . $options->getNamespaceSeparator();
297 return new DbaIterator($this, $this->handle
, $prefix);
300 /* OptimizableInterface */
303 * Optimize the storage
306 * @throws Exception\RuntimeException
308 public function optimize()
311 if (! dba_optimize($this->handle
)) {
312 throw new Exception\
RuntimeException('dba_optimize failed');
320 * Internal method to get an item.
322 * @param string $normalizedKey
323 * @param bool $success
324 * @param mixed $casToken
325 * @return mixed Data on success, null on failure
326 * @throws Exception\ExceptionInterface
328 protected function internalGetItem(& $normalizedKey, & $success = null, & $casToken = null)
330 $options = $this->getOptions();
331 $namespace = $options->getNamespace();
332 $prefix = ($namespace === '') ?
'' : $namespace . $options->getNamespaceSeparator();
335 $value = dba_fetch($prefix . $normalizedKey, $this->handle
);
337 if ($value === false) {
348 * Internal method to test if an item exists.
350 * @param string $normalizedKey
352 * @throws Exception\ExceptionInterface
354 protected function internalHasItem(& $normalizedKey)
356 $options = $this->getOptions();
357 $namespace = $options->getNamespace();
358 $prefix = ($namespace === '') ?
'' : $namespace . $options->getNamespaceSeparator();
361 return dba_exists($prefix . $normalizedKey, $this->handle
);
367 * Internal method to store an item.
369 * @param string $normalizedKey
370 * @param mixed $value
372 * @throws Exception\ExceptionInterface
374 protected function internalSetItem(& $normalizedKey, & $value)
376 $options = $this->getOptions();
377 $namespace = $options->getNamespace();
378 $prefix = ($namespace === '') ?
'' : $namespace . $options->getNamespaceSeparator();
379 $internalKey = $prefix . $normalizedKey;
381 $cacheableValue = (string) $value; // dba_replace requires a string
384 if (! dba_replace($internalKey, $cacheableValue, $this->handle
)) {
385 throw new Exception\
RuntimeException("dba_replace('{$internalKey}', ...) failed");
394 * @param string $normalizedKey
395 * @param mixed $value
397 * @throws Exception\ExceptionInterface
399 protected function internalAddItem(& $normalizedKey, & $value)
401 $options = $this->getOptions();
402 $namespace = $options->getNamespace();
403 $prefix = ($namespace === '') ?
'' : $namespace . $options->getNamespaceSeparator();
404 $internalKey = $prefix . $normalizedKey;
408 // Workaround for PHP-Bug #54242 & #62489
409 if (dba_exists($internalKey, $this->handle
)) {
413 // Workaround for PHP-Bug #54242 & #62489
414 // dba_insert returns true if key already exists
415 ErrorHandler
::start();
416 $result = dba_insert($internalKey, $value, $this->handle
);
417 $error = ErrorHandler
::stop();
418 if (! $result ||
$error) {
426 * Internal method to remove an item.
428 * @param string $normalizedKey
430 * @throws Exception\ExceptionInterface
432 protected function internalRemoveItem(& $normalizedKey)
434 $options = $this->getOptions();
435 $namespace = $options->getNamespace();
436 $prefix = ($namespace === '') ?
'' : $namespace . $options->getNamespaceSeparator();
437 $internalKey = $prefix . $normalizedKey;
441 // Workaround for PHP-Bug #62490
442 if (! dba_exists($internalKey, $this->handle
)) {
446 return dba_delete($internalKey, $this->handle
);
452 * Internal method to get capabilities of this adapter
454 * @return Capabilities
456 protected function internalGetCapabilities()
458 if ($this->capabilities
=== null) {
459 $marker = new stdClass();
460 $capabilities = new Capabilities(
464 'supportedDatatypes' => [
466 'boolean' => 'string',
467 'integer' => 'string',
468 'double' => 'string',
475 'supportedMetadata' => [],
476 'maxKeyLength' => 0, // TODO: maxKeyLength ????
477 'namespaceIsPrefix' => true,
478 'namespaceSeparator' => $this->getOptions()->getNamespaceSeparator(),
482 // update namespace separator on change option
483 $this->getEventManager()->attach('option', function ($event) use ($capabilities, $marker) {
484 $params = $event->getParams();
486 if (isset($params['namespace_separator'])) {
487 $capabilities->setNamespaceSeparator($marker, $params['namespace_separator']);
491 $this->capabilities
= $capabilities;
492 $this->capabilityMarker
= $marker;
495 return $this->capabilities
;
499 * Open the database if not already done.
502 * @throws Exception\LogicException
503 * @throws Exception\RuntimeException
505 // @codingStandardsIgnoreStart
506 protected function _open()
508 // @codingStandardsIgnoreEnd
509 if (! $this->handle
) {
510 $options = $this->getOptions();
511 $pathname = $options->getPathname();
512 $mode = $options->getMode();
513 $handler = $options->getHandler();
515 if ($pathname === '') {
516 throw new Exception\
LogicException('No pathname to database file');
519 ErrorHandler
::start();
520 $dba = dba_open($pathname, $mode, $handler);
521 $err = ErrorHandler
::stop();
523 throw new Exception\
RuntimeException(
524 "dba_open('{$pathname}', '{$mode}', '{$handler}') failed",
529 $this->handle
= $dba;
534 * Close database file if opened
538 // @codingStandardsIgnoreStart
539 protected function _close()
541 // @codingStandardsIgnoreEnd
543 ErrorHandler
::start(E_NOTICE
);
544 dba_close($this->handle
);
545 ErrorHandler
::stop();
546 $this->handle
= null;