Added the zend framework 2 library, the path is specified in line no.26 in zend_modul...
[openemr.git] / interface / modules / zend_modules / library / Zend / Cache / Storage / Adapter / Filesystem.php
blobd2ed44ec1f5c016726806cebc4bc1bbb897c1d8b
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-2013 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 Exception as BaseException;
13 use GlobIterator;
14 use stdClass;
15 use Zend\Cache\Exception;
16 use Zend\Cache\Storage;
17 use Zend\Cache\Storage\AvailableSpaceCapableInterface;
18 use Zend\Cache\Storage\Capabilities;
19 use Zend\Cache\Storage\ClearByNamespaceInterface;
20 use Zend\Cache\Storage\ClearByPrefixInterface;
21 use Zend\Cache\Storage\ClearExpiredInterface;
22 use Zend\Cache\Storage\FlushableInterface;
23 use Zend\Cache\Storage\IterableInterface;
24 use Zend\Cache\Storage\OptimizableInterface;
25 use Zend\Cache\Storage\TaggableInterface;
26 use Zend\Cache\Storage\TotalSpaceCapableInterface;
27 use Zend\Stdlib\ErrorHandler;
29 class Filesystem extends AbstractAdapter implements
30 AvailableSpaceCapableInterface,
31 ClearByNamespaceInterface,
32 ClearByPrefixInterface,
33 ClearExpiredInterface,
34 FlushableInterface,
35 IterableInterface,
36 OptimizableInterface,
37 TaggableInterface,
38 TotalSpaceCapableInterface
41 /**
42 * Buffered total space in bytes
44 * @var null|int|float
46 protected $totalSpace;
48 /**
49 * An identity for the last filespec
50 * (cache directory + namespace prefix + key + directory level)
52 * @var string
54 protected $lastFileSpecId = '';
56 /**
57 * The last used filespec
59 * @var string
61 protected $lastFileSpec = '';
63 /**
64 * Set options.
66 * @param array|\Traversable|FilesystemOptions $options
67 * @return Filesystem
68 * @see getOptions()
70 public function setOptions($options)
72 if (!$options instanceof FilesystemOptions) {
73 $options = new FilesystemOptions($options);
76 return parent::setOptions($options);
79 /**
80 * Get options.
82 * @return FilesystemOptions
83 * @see setOptions()
85 public function getOptions()
87 if (!$this->options) {
88 $this->setOptions(new FilesystemOptions());
90 return $this->options;
93 /* FlushableInterface */
95 /**
96 * Flush the whole storage
98 * @throws Exception\RuntimeException
99 * @return bool
101 public function flush()
103 $flags = GlobIterator::SKIP_DOTS | GlobIterator::CURRENT_AS_PATHNAME;
104 $dir = $this->getOptions()->getCacheDir();
105 $clearFolder = null;
106 $clearFolder = function ($dir) use (& $clearFolder, $flags) {
107 $it = new GlobIterator($dir . DIRECTORY_SEPARATOR . '*', $flags);
108 foreach ($it as $pathname) {
109 if ($it->isDir()) {
110 $clearFolder($pathname);
111 rmdir($pathname);
112 } else {
113 unlink($pathname);
118 ErrorHandler::start();
119 $clearFolder($dir);
120 $error = ErrorHandler::stop();
121 if ($error) {
122 throw new Exception\RuntimeException("Flushing directory '{$dir}' failed", 0, $error);
125 return true;
128 /* ClearExpiredInterface */
131 * Remove expired items
133 * @return bool
135 public function clearExpired()
137 $options = $this->getOptions();
138 $namespace = $options->getNamespace();
139 $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
141 $flags = GlobIterator::SKIP_DOTS | GlobIterator::CURRENT_AS_FILEINFO;
142 $path = $options->getCacheDir()
143 . str_repeat(DIRECTORY_SEPARATOR . $prefix . '*', $options->getDirLevel())
144 . DIRECTORY_SEPARATOR . $prefix . '*.dat';
145 $glob = new GlobIterator($path, $flags);
146 $time = time();
147 $ttl = $options->getTtl();
149 ErrorHandler::start();
150 foreach ($glob as $entry) {
151 $mtime = $entry->getMTime();
152 if ($time >= $mtime + $ttl) {
153 $pathname = $entry->getPathname();
154 unlink($pathname);
156 $tagPathname = substr($pathname, 0, -4) . '.tag';
157 if (file_exists($tagPathname)) {
158 unlink($tagPathname);
162 $error = ErrorHandler::stop();
163 if ($error) {
164 throw new Exception\RuntimeException("Failed to clear expired items", 0, $error);
167 return true;
170 /* ClearByNamespaceInterface */
173 * Remove items by given namespace
175 * @param string $namespace
176 * @throws Exception\RuntimeException
177 * @return bool
179 public function clearByNamespace($namespace)
181 $namespace = (string) $namespace;
182 if ($namespace === '') {
183 throw new Exception\InvalidArgumentException('No namespace given');
186 $options = $this->getOptions();
187 $prefix = $namespace . $options->getNamespaceSeparator();
189 $flags = GlobIterator::SKIP_DOTS | GlobIterator::CURRENT_AS_PATHNAME;
190 $path = $options->getCacheDir()
191 . str_repeat(DIRECTORY_SEPARATOR . $prefix . '*', $options->getDirLevel())
192 . DIRECTORY_SEPARATOR . $prefix . '*';
193 $glob = new GlobIterator($path, $flags);
195 ErrorHandler::start();
196 foreach ($glob as $pathname) {
197 unlink($pathname);
199 $error = ErrorHandler::stop();
200 if ($error) {
201 throw new Exception\RuntimeException("Failed to remove files of '{$path}'", 0, $error);
204 return true;
207 /* ClearByPrefixInterface */
210 * Remove items matching given prefix
212 * @param string $prefix
213 * @throws Exception\RuntimeException
214 * @return bool
216 public function clearByPrefix($prefix)
218 $prefix = (string) $prefix;
219 if ($prefix === '') {
220 throw new Exception\InvalidArgumentException('No prefix given');
223 $options = $this->getOptions();
224 $namespace = $options->getNamespace();
225 $nsPrefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
227 $flags = GlobIterator::SKIP_DOTS | GlobIterator::CURRENT_AS_PATHNAME;
228 $path = $options->getCacheDir()
229 . str_repeat(DIRECTORY_SEPARATOR . $nsPrefix . '*', $options->getDirLevel())
230 . DIRECTORY_SEPARATOR . $nsPrefix . $prefix . '*';
231 $glob = new GlobIterator($path, $flags);
233 ErrorHandler::start();
234 foreach ($glob as $pathname) {
235 unlink($pathname);
237 $error = ErrorHandler::stop();
238 if ($error) {
239 throw new Exception\RuntimeException("Failed to remove files of '{$path}'", 0, $error);
242 return true;
245 /* TaggableInterface */
248 * Set tags to an item by given key.
249 * An empty array will remove all tags.
251 * @param string $key
252 * @param string[] $tags
253 * @return bool
255 public function setTags($key, array $tags)
257 $this->normalizeKey($key);
258 if (!$this->internalHasItem($key)) {
259 return false;
262 $filespec = $this->getFileSpec($key);
264 if (!$tags) {
265 $this->unlink($filespec . '.tag');
266 return true;
269 $this->putFileContent($filespec . '.tag', implode("\n", $tags));
270 return true;
274 * Get tags of an item by given key
276 * @param string $key
277 * @return string[]|FALSE
279 public function getTags($key)
281 $this->normalizeKey($key);
282 if (!$this->internalHasItem($key)) {
283 return false;
286 $filespec = $this->getFileSpec($key);
287 $tags = array();
288 if (file_exists($filespec . '.tag')) {
289 $tags = explode("\n", $this->getFileContent($filespec . '.tag'));
292 return $tags;
296 * Remove items matching given tags.
298 * If $disjunction only one of the given tags must match
299 * else all given tags must match.
301 * @param string[] $tags
302 * @param bool $disjunction
303 * @return bool
305 public function clearByTags(array $tags, $disjunction = false)
307 if (!$tags) {
308 return true;
311 $tagCount = count($tags);
312 $options = $this->getOptions();
313 $namespace = $options->getNamespace();
314 $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
316 $flags = GlobIterator::SKIP_DOTS | GlobIterator::CURRENT_AS_PATHNAME;
317 $path = $options->getCacheDir()
318 . str_repeat(DIRECTORY_SEPARATOR . $prefix . '*', $options->getDirLevel())
319 . DIRECTORY_SEPARATOR . $prefix . '*.tag';
320 $glob = new GlobIterator($path, $flags);
322 foreach ($glob as $pathname) {
323 $diff = array_diff($tags, explode("\n", $this->getFileContent($pathname)));
325 $rem = false;
326 if ($disjunction && count($diff) < $tagCount) {
327 $rem = true;
328 } elseif (!$disjunction && !$diff) {
329 $rem = true;
332 if ($rem) {
333 unlink($pathname);
335 $datPathname = substr($pathname, 0, -4) . '.dat';
336 if (file_exists($datPathname)) {
337 unlink($datPathname);
342 return true;
345 /* IterableInterface */
348 * Get the storage iterator
350 * @return FilesystemIterator
352 public function getIterator()
354 $options = $this->getOptions();
355 $namespace = $options->getNamespace();
356 $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
357 $path = $options->getCacheDir()
358 . str_repeat(DIRECTORY_SEPARATOR . $prefix . '*', $options->getDirLevel())
359 . DIRECTORY_SEPARATOR . $prefix . '*.dat';
360 return new FilesystemIterator($this, $path, $prefix);
363 /* OptimizableInterface */
366 * Optimize the storage
368 * @return bool
369 * @return Exception\RuntimeException
371 public function optimize()
373 $options = $this->getOptions();
374 if ($options->getDirLevel()) {
375 $namespace = $options->getNamespace();
376 $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
378 // removes only empty directories
379 $this->rmDir($options->getCacheDir(), $prefix);
381 return true;
384 /* TotalSpaceCapableInterface */
387 * Get total space in bytes
389 * @throws Exception\RuntimeException
390 * @return int|float
392 public function getTotalSpace()
394 if ($this->totalSpace === null) {
395 $path = $this->getOptions()->getCacheDir();
397 ErrorHandler::start();
398 $total = disk_total_space($path);
399 $error = ErrorHandler::stop();
400 if ($total === false) {
401 throw new Exception\RuntimeException("Can't detect total space of '{$path}'", 0, $error);
403 $this->totalSpace = $total;
405 // clean total space buffer on change cache_dir
406 $events = $this->getEventManager();
407 $handle = null;
408 $totalSpace = & $this->totalSpace;
409 $callback = function ($event) use (& $events, & $handle, & $totalSpace) {
410 $params = $event->getParams();
411 if (isset($params['cache_dir'])) {
412 $totalSpace = null;
413 $events->detach($handle);
416 $handle = $events->attach('option', $callback);
419 return $this->totalSpace;
422 /* AvailableSpaceCapableInterface */
425 * Get available space in bytes
427 * @throws Exception\RuntimeException
428 * @return int|float
430 public function getAvailableSpace()
432 $path = $this->getOptions()->getCacheDir();
434 ErrorHandler::start();
435 $avail = disk_free_space($path);
436 $error = ErrorHandler::stop();
437 if ($avail === false) {
438 throw new Exception\RuntimeException("Can't detect free space of '{$path}'", 0, $error);
441 return $avail;
444 /* reading */
447 * Get an item.
449 * @param string $key
450 * @param bool $success
451 * @param mixed $casToken
452 * @return mixed Data on success, null on failure
453 * @throws Exception\ExceptionInterface
455 * @triggers getItem.pre(PreEvent)
456 * @triggers getItem.post(PostEvent)
457 * @triggers getItem.exception(ExceptionEvent)
459 public function getItem($key, & $success = null, & $casToken = null)
461 $options = $this->getOptions();
462 if ($options->getReadable() && $options->getClearStatCache()) {
463 clearstatcache();
466 $argn = func_num_args();
467 if ($argn > 2) {
468 return parent::getItem($key, $success, $casToken);
469 } elseif ($argn > 1) {
470 return parent::getItem($key, $success);
473 return parent::getItem($key);
477 * Get multiple items.
479 * @param array $keys
480 * @return array Associative array of keys and values
481 * @throws Exception\ExceptionInterface
483 * @triggers getItems.pre(PreEvent)
484 * @triggers getItems.post(PostEvent)
485 * @triggers getItems.exception(ExceptionEvent)
487 public function getItems(array $keys)
489 $options = $this->getOptions();
490 if ($options->getReadable() && $options->getClearStatCache()) {
491 clearstatcache();
494 return parent::getItems($keys);
498 * Internal method to get an item.
500 * @param string $normalizedKey
501 * @param bool $success
502 * @param mixed $casToken
503 * @return mixed Data on success, null on failure
504 * @throws Exception\ExceptionInterface
506 protected function internalGetItem(& $normalizedKey, & $success = null, & $casToken = null)
508 if (!$this->internalHasItem($normalizedKey)) {
509 $success = false;
510 return null;
513 try {
514 $filespec = $this->getFileSpec($normalizedKey);
515 $data = $this->getFileContent($filespec . '.dat');
517 // use filemtime + filesize as CAS token
518 if (func_num_args() > 2) {
519 $casToken = filemtime($filespec . '.dat') . filesize($filespec . '.dat');
521 $success = true;
522 return $data;
524 } catch (BaseException $e) {
525 $success = false;
526 throw $e;
531 * Internal method to get multiple items.
533 * @param array $normalizedKeys
534 * @return array Associative array of keys and values
535 * @throws Exception\ExceptionInterface
537 protected function internalGetItems(array & $normalizedKeys)
539 $options = $this->getOptions();
540 $keys = $normalizedKeys; // Don't change argument passed by reference
541 $result = array();
542 while ($keys) {
544 // LOCK_NB if more than one items have to read
545 $nonBlocking = count($keys) > 1;
546 $wouldblock = null;
548 // read items
549 foreach ($keys as $i => $key) {
550 if (!$this->internalHasItem($key)) {
551 unset($keys[$i]);
552 continue;
555 $filespec = $this->getFileSpec($key);
556 $data = $this->getFileContent($filespec . '.dat', $nonBlocking, $wouldblock);
557 if ($nonBlocking && $wouldblock) {
558 continue;
559 } else {
560 unset($keys[$i]);
563 $result[$key] = $data;
566 // TODO: Don't check ttl after first iteration
567 // $options['ttl'] = 0;
570 return $result;
574 * Test if an item exists.
576 * @param string $key
577 * @return bool
578 * @throws Exception\ExceptionInterface
580 * @triggers hasItem.pre(PreEvent)
581 * @triggers hasItem.post(PostEvent)
582 * @triggers hasItem.exception(ExceptionEvent)
584 public function hasItem($key)
586 $options = $this->getOptions();
587 if ($options->getReadable() && $options->getClearStatCache()) {
588 clearstatcache();
591 return parent::hasItem($key);
595 * Test multiple items.
597 * @param array $keys
598 * @return array Array of found keys
599 * @throws Exception\ExceptionInterface
601 * @triggers hasItems.pre(PreEvent)
602 * @triggers hasItems.post(PostEvent)
603 * @triggers hasItems.exception(ExceptionEvent)
605 public function hasItems(array $keys)
607 $options = $this->getOptions();
608 if ($options->getReadable() && $options->getClearStatCache()) {
609 clearstatcache();
612 return parent::hasItems($keys);
616 * Internal method to test if an item exists.
618 * @param string $normalizedKey
619 * @return bool
620 * @throws Exception\ExceptionInterface
622 protected function internalHasItem(& $normalizedKey)
624 $file = $this->getFileSpec($normalizedKey) . '.dat';
625 if (!file_exists($file)) {
626 return false;
629 $ttl = $this->getOptions()->getTtl();
630 if ($ttl) {
631 ErrorHandler::start();
632 $mtime = filemtime($file);
633 $error = ErrorHandler::stop();
634 if (!$mtime) {
635 throw new Exception\RuntimeException(
636 "Error getting mtime of file '{$file}'", 0, $error
640 if (time() >= ($mtime + $ttl)) {
641 return false;
645 return true;
649 * Get metadata
651 * @param string $key
652 * @return array|bool Metadata on success, false on failure
654 public function getMetadata($key)
656 $options = $this->getOptions();
657 if ($options->getReadable() && $options->getClearStatCache()) {
658 clearstatcache();
661 return parent::getMetadata($key);
665 * Get metadatas
667 * @param array $keys
668 * @param array $options
669 * @return array Associative array of keys and metadata
671 public function getMetadatas(array $keys, array $options = array())
673 $options = $this->getOptions();
674 if ($options->getReadable() && $options->getClearStatCache()) {
675 clearstatcache();
678 return parent::getMetadatas($keys);
682 * Get info by key
684 * @param string $normalizedKey
685 * @return array|bool Metadata on success, false on failure
687 protected function internalGetMetadata(& $normalizedKey)
689 if (!$this->internalHasItem($normalizedKey)) {
690 return false;
693 $options = $this->getOptions();
694 $filespec = $this->getFileSpec($normalizedKey);
695 $file = $filespec . '.dat';
697 $metadata = array(
698 'filespec' => $filespec,
699 'mtime' => filemtime($file)
702 if (!$options->getNoCtime()) {
703 $metadata['ctime'] = filectime($file);
706 if (!$options->getNoAtime()) {
707 $metadata['atime'] = fileatime($file);
710 return $metadata;
714 * Internal method to get multiple metadata
716 * @param array $normalizedKeys
717 * @return array Associative array of keys and metadata
718 * @throws Exception\ExceptionInterface
720 protected function internalGetMetadatas(array & $normalizedKeys)
722 $options = $this->getOptions();
723 $result = array();
725 foreach ($normalizedKeys as $normalizedKey) {
726 $filespec = $this->getFileSpec($normalizedKey);
727 $file = $filespec . '.dat';
729 $metadata = array(
730 'filespec' => $filespec,
731 'mtime' => filemtime($file),
734 if (!$options->getNoCtime()) {
735 $metadata['ctime'] = filectime($file);
738 if (!$options->getNoAtime()) {
739 $metadata['atime'] = fileatime($file);
742 $result[$normalizedKey] = $metadata;
745 return $result;
748 /* writing */
751 * Store an item.
753 * @param string $key
754 * @param mixed $value
755 * @return bool
756 * @throws Exception\ExceptionInterface
758 * @triggers setItem.pre(PreEvent)
759 * @triggers setItem.post(PostEvent)
760 * @triggers setItem.exception(ExceptionEvent)
762 public function setItem($key, $value)
764 $options = $this->getOptions();
765 if ($options->getWritable() && $options->getClearStatCache()) {
766 clearstatcache();
768 return parent::setItem($key, $value);
772 * Store multiple items.
774 * @param array $keyValuePairs
775 * @return array Array of not stored keys
776 * @throws Exception\ExceptionInterface
778 * @triggers setItems.pre(PreEvent)
779 * @triggers setItems.post(PostEvent)
780 * @triggers setItems.exception(ExceptionEvent)
782 public function setItems(array $keyValuePairs)
784 $options = $this->getOptions();
785 if ($options->getWritable() && $options->getClearStatCache()) {
786 clearstatcache();
789 return parent::setItems($keyValuePairs);
793 * Add an item.
795 * @param string $key
796 * @param mixed $value
797 * @return bool
798 * @throws Exception\ExceptionInterface
800 * @triggers addItem.pre(PreEvent)
801 * @triggers addItem.post(PostEvent)
802 * @triggers addItem.exception(ExceptionEvent)
804 public function addItem($key, $value)
806 $options = $this->getOptions();
807 if ($options->getWritable() && $options->getClearStatCache()) {
808 clearstatcache();
811 return parent::addItem($key, $value);
815 * Add multiple items.
817 * @param array $keyValuePairs
818 * @return bool
819 * @throws Exception\ExceptionInterface
821 * @triggers addItems.pre(PreEvent)
822 * @triggers addItems.post(PostEvent)
823 * @triggers addItems.exception(ExceptionEvent)
825 public function addItems(array $keyValuePairs)
827 $options = $this->getOptions();
828 if ($options->getWritable() && $options->getClearStatCache()) {
829 clearstatcache();
832 return parent::addItems($keyValuePairs);
836 * Replace an existing item.
838 * @param string $key
839 * @param mixed $value
840 * @return bool
841 * @throws Exception\ExceptionInterface
843 * @triggers replaceItem.pre(PreEvent)
844 * @triggers replaceItem.post(PostEvent)
845 * @triggers replaceItem.exception(ExceptionEvent)
847 public function replaceItem($key, $value)
849 $options = $this->getOptions();
850 if ($options->getWritable() && $options->getClearStatCache()) {
851 clearstatcache();
854 return parent::replaceItem($key, $value);
858 * Replace multiple existing items.
860 * @param array $keyValuePairs
861 * @return bool
862 * @throws Exception\ExceptionInterface
864 * @triggers replaceItems.pre(PreEvent)
865 * @triggers replaceItems.post(PostEvent)
866 * @triggers replaceItems.exception(ExceptionEvent)
868 public function replaceItems(array $keyValuePairs)
870 $options = $this->getOptions();
871 if ($options->getWritable() && $options->getClearStatCache()) {
872 clearstatcache();
875 return parent::replaceItems($keyValuePairs);
879 * Internal method to store an item.
881 * @param string $normalizedKey
882 * @param mixed $value
883 * @return bool
884 * @throws Exception\ExceptionInterface
886 protected function internalSetItem(& $normalizedKey, & $value)
888 $filespec = $this->getFileSpec($normalizedKey);
889 $this->prepareDirectoryStructure($filespec);
891 // write data in non-blocking mode
892 $wouldblock = null;
893 $this->putFileContent($filespec . '.dat', $value, true, $wouldblock);
895 // delete related tag file (if present)
896 $this->unlink($filespec . '.tag');
898 // Retry writing data in blocking mode if it was blocked before
899 if ($wouldblock) {
900 $this->putFileContent($filespec . '.dat', $value);
903 return true;
907 * Internal method to store multiple items.
909 * @param array $normalizedKeyValuePairs
910 * @return array Array of not stored keys
911 * @throws Exception\ExceptionInterface
913 protected function internalSetItems(array & $normalizedKeyValuePairs)
915 $oldUmask = null;
917 // create an associated array of files and contents to write
918 $contents = array();
919 foreach ($normalizedKeyValuePairs as $key => & $value) {
920 $filespec = $this->getFileSpec($key);
921 $this->prepareDirectoryStructure($filespec);
923 // *.dat file
924 $contents[$filespec . '.dat'] = & $value;
926 // *.tag file
927 $this->unlink($filespec . '.tag');
930 // write to disk
931 while ($contents) {
932 $nonBlocking = count($contents) > 1;
933 $wouldblock = null;
935 foreach ($contents as $file => & $content) {
936 $this->putFileContent($file, $content, $nonBlocking, $wouldblock);
937 if (!$nonBlocking || !$wouldblock) {
938 unset($contents[$file]);
943 // return OK
944 return array();
948 * Set an item only if token matches
950 * It uses the token received from getItem() to check if the item has
951 * changed before overwriting it.
953 * @param mixed $token
954 * @param string $key
955 * @param mixed $value
956 * @return bool
957 * @throws Exception\ExceptionInterface
958 * @see getItem()
959 * @see setItem()
961 public function checkAndSetItem($token, $key, $value)
963 $options = $this->getOptions();
964 if ($options->getWritable() && $options->getClearStatCache()) {
965 clearstatcache();
968 return parent::checkAndSetItem($token, $key, $value);
972 * Internal method to set an item only if token matches
974 * @param mixed $token
975 * @param string $normalizedKey
976 * @param mixed $value
977 * @return bool
978 * @throws Exception\ExceptionInterface
979 * @see getItem()
980 * @see setItem()
982 protected function internalCheckAndSetItem(& $token, & $normalizedKey, & $value)
984 if (!$this->internalHasItem($normalizedKey)) {
985 return false;
988 // use filemtime + filesize as CAS token
989 $file = $this->getFileSpec($normalizedKey) . '.dat';
990 $check = filemtime($file) . filesize($file);
991 if ($token !== $check) {
992 return false;
995 return $this->internalSetItem($normalizedKey, $value);
999 * Reset lifetime of an item
1001 * @param string $key
1002 * @return bool
1003 * @throws Exception\ExceptionInterface
1005 * @triggers touchItem.pre(PreEvent)
1006 * @triggers touchItem.post(PostEvent)
1007 * @triggers touchItem.exception(ExceptionEvent)
1009 public function touchItem($key)
1011 $options = $this->getOptions();
1012 if ($options->getWritable() && $options->getClearStatCache()) {
1013 clearstatcache();
1016 return parent::touchItem($key);
1020 * Reset lifetime of multiple items.
1022 * @param array $keys
1023 * @return array Array of not updated keys
1024 * @throws Exception\ExceptionInterface
1026 * @triggers touchItems.pre(PreEvent)
1027 * @triggers touchItems.post(PostEvent)
1028 * @triggers touchItems.exception(ExceptionEvent)
1030 public function touchItems(array $keys)
1032 $options = $this->getOptions();
1033 if ($options->getWritable() && $options->getClearStatCache()) {
1034 clearstatcache();
1037 return parent::touchItems($keys);
1041 * Internal method to reset lifetime of an item
1043 * @param string $normalizedKey
1044 * @return bool
1045 * @throws Exception\ExceptionInterface
1047 protected function internalTouchItem(& $normalizedKey)
1049 if (!$this->internalHasItem($normalizedKey)) {
1050 return false;
1053 $filespec = $this->getFileSpec($normalizedKey);
1055 ErrorHandler::start();
1056 $touch = touch($filespec . '.dat');
1057 $error = ErrorHandler::stop();
1058 if (!$touch) {
1059 throw new Exception\RuntimeException(
1060 "Error touching file '{$filespec}.dat'", 0, $error
1064 return true;
1068 * Remove an item.
1070 * @param string $key
1071 * @return bool
1072 * @throws Exception\ExceptionInterface
1074 * @triggers removeItem.pre(PreEvent)
1075 * @triggers removeItem.post(PostEvent)
1076 * @triggers removeItem.exception(ExceptionEvent)
1078 public function removeItem($key)
1080 $options = $this->getOptions();
1081 if ($options->getWritable() && $options->getClearStatCache()) {
1082 clearstatcache();
1085 return parent::removeItem($key);
1089 * Remove multiple items.
1091 * @param array $keys
1092 * @return array Array of not removed keys
1093 * @throws Exception\ExceptionInterface
1095 * @triggers removeItems.pre(PreEvent)
1096 * @triggers removeItems.post(PostEvent)
1097 * @triggers removeItems.exception(ExceptionEvent)
1099 public function removeItems(array $keys)
1101 $options = $this->getOptions();
1102 if ($options->getWritable() && $options->getClearStatCache()) {
1103 clearstatcache();
1106 return parent::removeItems($keys);
1110 * Internal method to remove an item.
1112 * @param string $normalizedKey
1113 * @return bool
1114 * @throws Exception\ExceptionInterface
1116 protected function internalRemoveItem(& $normalizedKey)
1118 $filespec = $this->getFileSpec($normalizedKey);
1119 if (!file_exists($filespec . '.dat')) {
1120 return false;
1121 } else {
1122 $this->unlink($filespec . '.dat');
1123 $this->unlink($filespec . '.tag');
1125 return true;
1128 /* status */
1131 * Internal method to get capabilities of this adapter
1133 * @return Capabilities
1135 protected function internalGetCapabilities()
1137 if ($this->capabilities === null) {
1138 $marker = new stdClass();
1139 $options = $this->getOptions();
1141 // detect metadata
1142 $metadata = array('mtime', 'filespec');
1143 if (!$options->getNoAtime()) {
1144 $metadata[] = 'atime';
1146 if (!$options->getNoCtime()) {
1147 $metadata[] = 'ctime';
1150 $capabilities = new Capabilities(
1151 $this,
1152 $marker,
1153 array(
1154 'supportedDatatypes' => array(
1155 'NULL' => 'string',
1156 'boolean' => 'string',
1157 'integer' => 'string',
1158 'double' => 'string',
1159 'string' => true,
1160 'array' => false,
1161 'object' => false,
1162 'resource' => false,
1164 'supportedMetadata' => $metadata,
1165 'minTtl' => 1,
1166 'maxTtl' => 0,
1167 'staticTtl' => false,
1168 'ttlPrecision' => 1,
1169 'expiredRead' => true,
1170 'maxKeyLength' => 251, // 255 - strlen(.dat | .tag)
1171 'namespaceIsPrefix' => true,
1172 'namespaceSeparator' => $options->getNamespaceSeparator(),
1176 // update capabilities on change options
1177 $this->getEventManager()->attach('option', function ($event) use ($capabilities, $marker) {
1178 $params = $event->getParams();
1180 if (isset($params['namespace_separator'])) {
1181 $capabilities->setNamespaceSeparator($marker, $params['namespace_separator']);
1184 if (isset($params['no_atime']) || isset($params['no_ctime'])) {
1185 $metadata = $capabilities->getSupportedMetadata();
1187 if (isset($params['no_atime']) && !$params['no_atime']) {
1188 $metadata[] = 'atime';
1189 } elseif (isset($params['no_atime']) && ($index = array_search('atime', $metadata)) !== false) {
1190 unset($metadata[$index]);
1193 if (isset($params['no_ctime']) && !$params['no_ctime']) {
1194 $metadata[] = 'ctime';
1195 } elseif (isset($params['no_ctime']) && ($index = array_search('ctime', $metadata)) !== false) {
1196 unset($metadata[$index]);
1199 $capabilities->setSupportedMetadata($marker, $metadata);
1203 $this->capabilityMarker = $marker;
1204 $this->capabilities = $capabilities;
1207 return $this->capabilities;
1210 /* internal */
1213 * Removes directories recursive by namespace
1215 * @param string $dir Directory to delete
1216 * @param string $prefix Namespace + Separator
1217 * @return bool
1219 protected function rmDir($dir, $prefix)
1221 $glob = glob(
1222 $dir . DIRECTORY_SEPARATOR . $prefix . '*',
1223 GLOB_ONLYDIR | GLOB_NOESCAPE | GLOB_NOSORT
1225 if (!$glob) {
1226 // On some systems glob returns false even on empty result
1227 return true;
1230 $ret = true;
1231 foreach ($glob as $subdir) {
1232 // skip removing current directory if removing of sub-directory failed
1233 if ($this->rmDir($subdir, $prefix)) {
1234 // ignore not empty directories
1235 ErrorHandler::start();
1236 $ret = rmdir($subdir) && $ret;
1237 ErrorHandler::stop();
1238 } else {
1239 $ret = false;
1243 return $ret;
1247 * Get file spec of the given key and namespace
1249 * @param string $normalizedKey
1250 * @return string
1252 protected function getFileSpec($normalizedKey)
1254 $options = $this->getOptions();
1255 $namespace = $options->getNamespace();
1256 $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
1257 $path = $options->getCacheDir() . DIRECTORY_SEPARATOR;
1258 $level = $options->getDirLevel();
1260 $fileSpecId = $path . $prefix . $normalizedKey . '/' . $level;
1261 if ($this->lastFileSpecId !== $fileSpecId) {
1262 if ($level > 0) {
1263 // create up to 256 directories per directory level
1264 $hash = md5($normalizedKey);
1265 for ($i = 0, $max = ($level * 2); $i < $max; $i+= 2) {
1266 $path .= $prefix . $hash[$i] . $hash[$i+1] . DIRECTORY_SEPARATOR;
1270 $this->lastFileSpecId = $fileSpecId;
1271 $this->lastFileSpec = $path . $prefix . $normalizedKey;
1274 return $this->lastFileSpec;
1278 * Read info file
1280 * @param string $file
1281 * @param bool $nonBlocking Don't block script if file is locked
1282 * @param bool $wouldblock The optional argument is set to TRUE if the lock would block
1283 * @return array|bool The info array or false if file wasn't found
1284 * @throws Exception\RuntimeException
1286 protected function readInfoFile($file, $nonBlocking = false, & $wouldblock = null)
1288 if (!file_exists($file)) {
1289 return false;
1292 $content = $this->getFileContent($file, $nonBlocking, $wouldblock);
1293 if ($nonBlocking && $wouldblock) {
1294 return false;
1297 ErrorHandler::start();
1298 $ifo = unserialize($content);
1299 $err = ErrorHandler::stop();
1300 if (!is_array($ifo)) {
1301 throw new Exception\RuntimeException(
1302 "Corrupted info file '{$file}'", 0, $err
1306 return $ifo;
1310 * Read a complete file
1312 * @param string $file File complete path
1313 * @param bool $nonBlocking Don't block script if file is locked
1314 * @param bool $wouldblock The optional argument is set to TRUE if the lock would block
1315 * @return string
1316 * @throws Exception\RuntimeException
1318 protected function getFileContent($file, $nonBlocking = false, & $wouldblock = null)
1320 $locking = $this->getOptions()->getFileLocking();
1321 $wouldblock = null;
1323 ErrorHandler::start();
1325 // if file locking enabled -> file_get_contents can't be used
1326 if ($locking) {
1327 $fp = fopen($file, 'rb');
1328 if ($fp === false) {
1329 $err = ErrorHandler::stop();
1330 throw new Exception\RuntimeException(
1331 "Error opening file '{$file}'", 0, $err
1335 if ($nonBlocking) {
1336 $lock = flock($fp, LOCK_SH | LOCK_NB, $wouldblock);
1337 if ($wouldblock) {
1338 fclose($fp);
1339 ErrorHandler::stop();
1340 return;
1342 } else {
1343 $lock = flock($fp, LOCK_SH);
1346 if (!$lock) {
1347 fclose($fp);
1348 $err = ErrorHandler::stop();
1349 throw new Exception\RuntimeException(
1350 "Error locking file '{$file}'", 0, $err
1354 $res = stream_get_contents($fp);
1355 if ($res === false) {
1356 flock($fp, LOCK_UN);
1357 fclose($fp);
1358 $err = ErrorHandler::stop();
1359 throw new Exception\RuntimeException(
1360 'Error getting stream contents', 0, $err
1364 flock($fp, LOCK_UN);
1365 fclose($fp);
1367 // if file locking disabled -> file_get_contents can be used
1368 } else {
1369 $res = file_get_contents($file, false);
1370 if ($res === false) {
1371 $err = ErrorHandler::stop();
1372 throw new Exception\RuntimeException(
1373 "Error getting file contents for file '{$file}'", 0, $err
1378 ErrorHandler::stop();
1379 return $res;
1383 * Prepares a directory structure for the given file(spec)
1384 * using the configured directory level.
1386 * @param string $file
1387 * @return void
1388 * @throws Exception\RuntimeException
1390 protected function prepareDirectoryStructure($file)
1392 $options = $this->getOptions();
1393 $level = $options->getDirLevel();
1395 // Directory structure is required only if directory level > 0
1396 if (!$level) {
1397 return;
1400 // Directory structure already exists
1401 $pathname = dirname($file);
1402 if (file_exists($pathname)) {
1403 return;
1406 $perm = $options->getDirPermission();
1407 $umask = $options->getUmask();
1408 if ($umask !== false && $perm !== false) {
1409 $perm = $perm & ~$umask;
1412 ErrorHandler::start();
1414 if ($perm === false || $level == 1) {
1415 // build-in mkdir function is enough
1417 $umask = ($umask !== false) ? umask($umask) : false;
1418 $res = mkdir($pathname, ($perm !== false) ? $perm : 0777, true);
1420 if ($umask !== false) {
1421 umask($umask);
1424 if (!$res) {
1425 $oct = ($perm === false) ? '777' : decoct($perm);
1426 $err = ErrorHandler::stop();
1427 throw new Exception\RuntimeException(
1428 "mkdir('{$pathname}', 0{$oct}, true) failed", 0, $err
1432 if ($perm !== false && !chmod($pathname, $perm)) {
1433 $oct = decoct($perm);
1434 $err = ErrorHandler::stop();
1435 throw new Exception\RuntimeException(
1436 "chmod('{$pathname}', 0{$oct}) failed", 0, $err
1440 } else {
1441 // build-in mkdir function sets permission together with current umask
1442 // which doesn't work well on multo threaded webservers
1443 // -> create directories one by one and set permissions
1445 // find existing path and missing path parts
1446 $parts = array();
1447 $path = $pathname;
1448 while (!file_exists($path)) {
1449 array_unshift($parts, basename($path));
1450 $nextPath = dirname($path);
1451 if ($nextPath === $path) {
1452 break;
1454 $path = $nextPath;
1457 // make all missing path parts
1458 foreach ($parts as $part) {
1459 $path.= DIRECTORY_SEPARATOR . $part;
1461 // create a single directory, set and reset umask immediately
1462 $umask = ($umask !== false) ? umask($umask) : false;
1463 $res = mkdir($path, ($perm === false) ? 0777 : $perm, false);
1464 if ($umask !== false) {
1465 umask($umask);
1468 if (!$res) {
1469 $oct = ($perm === false) ? '777' : decoct($perm);
1470 $err = ErrorHandler::stop();
1471 throw new Exception\RuntimeException(
1472 "mkdir('{$path}', 0{$oct}, false) failed"
1476 if ($perm !== false && !chmod($path, $perm)) {
1477 $oct = decoct($perm);
1478 $err = ErrorHandler::stop();
1479 throw new Exception\RuntimeException(
1480 "chmod('{$path}', 0{$oct}) failed"
1486 ErrorHandler::stop();
1490 * Write content to a file
1492 * @param string $file File complete path
1493 * @param string $data Data to write
1494 * @param bool $nonBlocking Don't block script if file is locked
1495 * @param bool $wouldblock The optional argument is set to TRUE if the lock would block
1496 * @return void
1497 * @throws Exception\RuntimeException
1499 protected function putFileContent($file, $data, $nonBlocking = false, & $wouldblock = null)
1501 $options = $this->getOptions();
1502 $locking = $options->getFileLocking();
1503 $nonBlocking = $locking && $nonBlocking;
1504 $wouldblock = null;
1506 $umask = $options->getUmask();
1507 $perm = $options->getFilePermission();
1508 if ($umask !== false && $perm !== false) {
1509 $perm = $perm & ~$umask;
1512 ErrorHandler::start();
1514 // if locking and non blocking is enabled -> file_put_contents can't used
1515 if ($locking && $nonBlocking) {
1517 $umask = ($umask !== false) ? umask($umask) : false;
1519 $fp = fopen($file, 'cb');
1521 if ($umask) {
1522 umask($umask);
1525 if (!$fp) {
1526 $err = ErrorHandler::stop();
1527 throw new Exception\RuntimeException(
1528 "Error opening file '{$file}'", 0, $err
1532 if ($perm !== false && !chmod($file, $perm)) {
1533 fclose($fp);
1534 $oct = decoct($perm);
1535 $err = ErrorHandler::stop();
1536 throw new Exception\RuntimeException("chmod('{$file}', 0{$oct}) failed", 0, $err);
1539 if (!flock($fp, LOCK_EX | LOCK_NB, $wouldblock)) {
1540 fclose($fp);
1541 $err = ErrorHandler::stop();
1542 if ($wouldblock) {
1543 return;
1544 } else {
1545 throw new Exception\RuntimeException("Error locking file '{$file}'", 0, $err);
1549 if (fwrite($fp, $data) === false) {
1550 flock($fp, LOCK_UN);
1551 fclose($fp);
1552 $err = ErrorHandler::stop();
1553 throw new Exception\RuntimeException("Error writing file '{$file}'", 0, $err);
1556 if (!ftruncate($fp, strlen($data))) {
1557 flock($fp, LOCK_UN);
1558 fclose($fp);
1559 $err = ErrorHandler::stop();
1560 throw new Exception\RuntimeException("Error truncating file '{$file}'", 0, $err);
1563 flock($fp, LOCK_UN);
1564 fclose($fp);
1566 // else -> file_put_contents can be used
1567 } else {
1568 $flags = 0;
1569 if ($locking) {
1570 $flags = $flags | LOCK_EX;
1573 $umask = ($umask !== false) ? umask($umask) : false;
1575 $rs = file_put_contents($file, $data, $flags);
1577 if ($umask) {
1578 umask($umask);
1581 if ($rs === false) {
1582 $err = ErrorHandler::stop();
1583 throw new Exception\RuntimeException(
1584 "Error writing file '{$file}'", 0, $err
1588 if ($perm !== false && !chmod($file, $perm)) {
1589 $oct = decoct($perm);
1590 $err = ErrorHandler::stop();
1591 throw new Exception\RuntimeException("chmod('{$file}', 0{$oct}) failed", 0, $err);
1595 ErrorHandler::stop();
1599 * Unlink a file
1601 * @param string $file
1602 * @return void
1603 * @throws RuntimeException
1605 protected function unlink($file)
1607 ErrorHandler::start();
1608 $res = unlink($file);
1609 $err = ErrorHandler::stop();
1611 // only throw exception if file still exists after deleting
1612 if (!$res && file_exists($file)) {
1613 throw new Exception\RuntimeException(
1614 "Error unlinking file '{$file}'; file still exists", 0, $err