composer package updates
[openemr.git] / vendor / zendframework / zend-cache / src / Storage / Adapter / Dba.php
blob3141be8867abecfe84bb67fb439b9135bc4e48cb
1 <?php
2 /**
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
8 */
10 namespace Zend\Cache\Storage\Adapter;
12 use stdClass;
13 use Traversable;
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,
29 FlushableInterface,
30 IterableInterface,
31 OptimizableInterface,
32 TotalSpaceCapableInterface
34 /**
35 * The DBA resource handle
37 * @var null|resource
39 protected $handle;
41 /**
42 * Buffered total space in bytes
44 * @var null|int|float
46 protected $totalSpace;
48 /**
49 * Constructor
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);
63 /**
64 * Destructor
66 * Closes an open dba resource
68 * @see AbstractAdapter::__destruct()
69 * @return void
71 public function __destruct()
73 $this->_close();
75 parent::__destruct();
78 /* options */
80 /**
81 * Set options.
83 * @param array|Traversable|DbaOptions $options
84 * @return self
85 * @see getOptions()
87 public function setOptions($options)
89 if (! $options instanceof DbaOptions) {
90 $options = new DbaOptions($options);
93 return parent::setOptions($options);
96 /**
97 * Get options.
99 * @return DbaOptions
100 * @see setOptions()
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
115 * @return int|float
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();
136 $handle = null;
137 $totalSpace = & $this->totalSpace;
138 $callback = function ($event) use (& $events, & $handle, & $totalSpace) {
139 $params = $event->getParams();
140 if (isset($params['pathname'])) {
141 $totalSpace = null;
142 $events->detach($handle);
145 $events->attach('option', $callback);
148 return $this->totalSpace;
151 /* AvailableSpaceCapableInterface */
154 * Get available space in bytes
156 * @return float
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);
173 return $avail;
176 /* FlushableInterface */
179 * Flush the whole storage
181 * @return bool
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
194 $this->_close();
196 ErrorHandler::start();
197 $result = unlink($pathname);
198 $error = ErrorHandler::stop();
199 if (! $result) {
200 throw new Exception\RuntimeException("unlink('{$pathname}') failed", 0, $error);
204 return true;
207 /* ClearByNamespaceInterface */
210 * Remove items by given namespace
212 * @param string $namespace
213 * @return bool
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);
224 $result = true;
226 $this->_open();
228 do {
229 // Workaround for PHP-Bug #62491 & #62492
230 $recheck = false;
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);
239 } while ($recheck);
241 return $result;
244 /* ClearByPrefixInterface */
247 * Remove items matching given prefix
249 * @param string $prefix
250 * @return bool
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);
263 $result = true;
265 $this->_open();
267 // Workaround for PHP-Bug #62491 & #62492
268 do {
269 $recheck = false;
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;
274 $recheck = true;
277 $internalKey = dba_nextkey($this->handle);
279 } while ($recheck);
281 return $result;
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
305 * @return bool
306 * @throws Exception\RuntimeException
308 public function optimize()
310 $this->_open();
311 if (! dba_optimize($this->handle)) {
312 throw new Exception\RuntimeException('dba_optimize failed');
314 return true;
317 /* reading */
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();
334 $this->_open();
335 $value = dba_fetch($prefix . $normalizedKey, $this->handle);
337 if ($value === false) {
338 $success = false;
339 return;
342 $success = true;
343 $casToken = $value;
344 return $value;
348 * Internal method to test if an item exists.
350 * @param string $normalizedKey
351 * @return bool
352 * @throws Exception\ExceptionInterface
354 protected function internalHasItem(& $normalizedKey)
356 $options = $this->getOptions();
357 $namespace = $options->getNamespace();
358 $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
360 $this->_open();
361 return dba_exists($prefix . $normalizedKey, $this->handle);
364 /* writing */
367 * Internal method to store an item.
369 * @param string $normalizedKey
370 * @param mixed $value
371 * @return bool
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
383 $this->_open();
384 if (! dba_replace($internalKey, $cacheableValue, $this->handle)) {
385 throw new Exception\RuntimeException("dba_replace('{$internalKey}', ...) failed");
388 return true;
392 * Add an item.
394 * @param string $normalizedKey
395 * @param mixed $value
396 * @return bool
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;
406 $this->_open();
408 // Workaround for PHP-Bug #54242 & #62489
409 if (dba_exists($internalKey, $this->handle)) {
410 return false;
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) {
419 return false;
422 return true;
426 * Internal method to remove an item.
428 * @param string $normalizedKey
429 * @return bool
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;
439 $this->_open();
441 // Workaround for PHP-Bug #62490
442 if (! dba_exists($internalKey, $this->handle)) {
443 return false;
446 return dba_delete($internalKey, $this->handle);
449 /* status */
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(
461 $this,
462 $marker,
464 'supportedDatatypes' => [
465 'NULL' => 'string',
466 'boolean' => 'string',
467 'integer' => 'string',
468 'double' => 'string',
469 'string' => true,
470 'array' => false,
471 'object' => false,
472 'resource' => false,
474 'minTtl' => 0,
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.
501 * @return void
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();
522 if (! $dba) {
523 throw new Exception\RuntimeException(
524 "dba_open('{$pathname}', '{$mode}', '{$handler}') failed",
526 $err
529 $this->handle = $dba;
534 * Close database file if opened
536 * @return void
538 // @codingStandardsIgnoreStart
539 protected function _close()
541 // @codingStandardsIgnoreEnd
542 if ($this->handle) {
543 ErrorHandler::start(E_NOTICE);
544 dba_close($this->handle);
545 ErrorHandler::stop();
546 $this->handle = null;