annotate vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 4c8ae668cc8c
children
rev   line source
Chris@0 1 <?php
Chris@0 2 /*
Chris@0 3 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
Chris@0 4 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
Chris@0 5 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
Chris@0 6 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
Chris@0 7 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
Chris@0 8 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
Chris@0 9 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
Chris@0 10 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
Chris@0 11 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
Chris@0 12 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
Chris@0 13 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Chris@0 14 *
Chris@0 15 * This software consists of voluntary contributions made by many individuals
Chris@0 16 * and is licensed under the MIT license. For more information, see
Chris@0 17 * <http://www.doctrine-project.org>.
Chris@0 18 */
Chris@0 19
Chris@0 20 namespace Doctrine\Common\Cache;
Chris@0 21
Chris@0 22 /**
Chris@0 23 * Base file cache driver.
Chris@0 24 *
Chris@0 25 * @since 2.3
Chris@0 26 * @author Fabio B. Silva <fabio.bat.silva@gmail.com>
Chris@0 27 * @author Tobias Schultze <http://tobion.de>
Chris@0 28 */
Chris@0 29 abstract class FileCache extends CacheProvider
Chris@0 30 {
Chris@0 31 /**
Chris@0 32 * The cache directory.
Chris@0 33 *
Chris@0 34 * @var string
Chris@0 35 */
Chris@0 36 protected $directory;
Chris@0 37
Chris@0 38 /**
Chris@0 39 * The cache file extension.
Chris@0 40 *
Chris@0 41 * @var string
Chris@0 42 */
Chris@0 43 private $extension;
Chris@0 44
Chris@0 45 /**
Chris@0 46 * @var int
Chris@0 47 */
Chris@0 48 private $umask;
Chris@0 49
Chris@0 50 /**
Chris@0 51 * @var int
Chris@0 52 */
Chris@0 53 private $directoryStringLength;
Chris@0 54
Chris@0 55 /**
Chris@0 56 * @var int
Chris@0 57 */
Chris@0 58 private $extensionStringLength;
Chris@0 59
Chris@0 60 /**
Chris@0 61 * @var bool
Chris@0 62 */
Chris@0 63 private $isRunningOnWindows;
Chris@0 64
Chris@0 65 /**
Chris@0 66 * Constructor.
Chris@0 67 *
Chris@0 68 * @param string $directory The cache directory.
Chris@0 69 * @param string $extension The cache file extension.
Chris@0 70 *
Chris@0 71 * @throws \InvalidArgumentException
Chris@0 72 */
Chris@0 73 public function __construct($directory, $extension = '', $umask = 0002)
Chris@0 74 {
Chris@0 75 // YES, this needs to be *before* createPathIfNeeded()
Chris@0 76 if ( ! is_int($umask)) {
Chris@0 77 throw new \InvalidArgumentException(sprintf(
Chris@0 78 'The umask parameter is required to be integer, was: %s',
Chris@0 79 gettype($umask)
Chris@0 80 ));
Chris@0 81 }
Chris@0 82 $this->umask = $umask;
Chris@0 83
Chris@0 84 if ( ! $this->createPathIfNeeded($directory)) {
Chris@0 85 throw new \InvalidArgumentException(sprintf(
Chris@0 86 'The directory "%s" does not exist and could not be created.',
Chris@0 87 $directory
Chris@0 88 ));
Chris@0 89 }
Chris@0 90
Chris@0 91 if ( ! is_writable($directory)) {
Chris@0 92 throw new \InvalidArgumentException(sprintf(
Chris@0 93 'The directory "%s" is not writable.',
Chris@0 94 $directory
Chris@0 95 ));
Chris@0 96 }
Chris@0 97
Chris@0 98 // YES, this needs to be *after* createPathIfNeeded()
Chris@0 99 $this->directory = realpath($directory);
Chris@0 100 $this->extension = (string) $extension;
Chris@0 101
Chris@0 102 $this->directoryStringLength = strlen($this->directory);
Chris@0 103 $this->extensionStringLength = strlen($this->extension);
Chris@0 104 $this->isRunningOnWindows = defined('PHP_WINDOWS_VERSION_BUILD');
Chris@0 105 }
Chris@0 106
Chris@0 107 /**
Chris@0 108 * Gets the cache directory.
Chris@0 109 *
Chris@0 110 * @return string
Chris@0 111 */
Chris@0 112 public function getDirectory()
Chris@0 113 {
Chris@0 114 return $this->directory;
Chris@0 115 }
Chris@0 116
Chris@0 117 /**
Chris@0 118 * Gets the cache file extension.
Chris@0 119 *
Chris@0 120 * @return string
Chris@0 121 */
Chris@0 122 public function getExtension()
Chris@0 123 {
Chris@0 124 return $this->extension;
Chris@0 125 }
Chris@0 126
Chris@0 127 /**
Chris@0 128 * @param string $id
Chris@0 129 *
Chris@0 130 * @return string
Chris@0 131 */
Chris@0 132 protected function getFilename($id)
Chris@0 133 {
Chris@0 134 $hash = hash('sha256', $id);
Chris@0 135
Chris@0 136 // This ensures that the filename is unique and that there are no invalid chars in it.
Chris@0 137 if (
Chris@0 138 '' === $id
Chris@0 139 || ((strlen($id) * 2 + $this->extensionStringLength) > 255)
Chris@0 140 || ($this->isRunningOnWindows && ($this->directoryStringLength + 4 + strlen($id) * 2 + $this->extensionStringLength) > 258)
Chris@0 141 ) {
Chris@0 142 // Most filesystems have a limit of 255 chars for each path component. On Windows the the whole path is limited
Chris@0 143 // to 260 chars (including terminating null char). Using long UNC ("\\?\" prefix) does not work with the PHP API.
Chris@0 144 // And there is a bug in PHP (https://bugs.php.net/bug.php?id=70943) with path lengths of 259.
Chris@0 145 // So if the id in hex representation would surpass the limit, we use the hash instead. The prefix prevents
Chris@0 146 // collisions between the hash and bin2hex.
Chris@0 147 $filename = '_' . $hash;
Chris@0 148 } else {
Chris@0 149 $filename = bin2hex($id);
Chris@0 150 }
Chris@0 151
Chris@0 152 return $this->directory
Chris@0 153 . DIRECTORY_SEPARATOR
Chris@0 154 . substr($hash, 0, 2)
Chris@0 155 . DIRECTORY_SEPARATOR
Chris@0 156 . $filename
Chris@0 157 . $this->extension;
Chris@0 158 }
Chris@0 159
Chris@0 160 /**
Chris@0 161 * {@inheritdoc}
Chris@0 162 */
Chris@0 163 protected function doDelete($id)
Chris@0 164 {
Chris@0 165 $filename = $this->getFilename($id);
Chris@0 166
Chris@0 167 return @unlink($filename) || ! file_exists($filename);
Chris@0 168 }
Chris@0 169
Chris@0 170 /**
Chris@0 171 * {@inheritdoc}
Chris@0 172 */
Chris@0 173 protected function doFlush()
Chris@0 174 {
Chris@0 175 foreach ($this->getIterator() as $name => $file) {
Chris@0 176 if ($file->isDir()) {
Chris@0 177 // Remove the intermediate directories which have been created to balance the tree. It only takes effect
Chris@0 178 // if the directory is empty. If several caches share the same directory but with different file extensions,
Chris@0 179 // the other ones are not removed.
Chris@0 180 @rmdir($name);
Chris@0 181 } elseif ($this->isFilenameEndingWithExtension($name)) {
Chris@0 182 // If an extension is set, only remove files which end with the given extension.
Chris@0 183 // If no extension is set, we have no other choice than removing everything.
Chris@0 184 @unlink($name);
Chris@0 185 }
Chris@0 186 }
Chris@0 187
Chris@0 188 return true;
Chris@0 189 }
Chris@0 190
Chris@0 191 /**
Chris@0 192 * {@inheritdoc}
Chris@0 193 */
Chris@0 194 protected function doGetStats()
Chris@0 195 {
Chris@0 196 $usage = 0;
Chris@0 197 foreach ($this->getIterator() as $name => $file) {
Chris@0 198 if (! $file->isDir() && $this->isFilenameEndingWithExtension($name)) {
Chris@0 199 $usage += $file->getSize();
Chris@0 200 }
Chris@0 201 }
Chris@0 202
Chris@0 203 $free = disk_free_space($this->directory);
Chris@0 204
Chris@0 205 return array(
Chris@0 206 Cache::STATS_HITS => null,
Chris@0 207 Cache::STATS_MISSES => null,
Chris@0 208 Cache::STATS_UPTIME => null,
Chris@0 209 Cache::STATS_MEMORY_USAGE => $usage,
Chris@0 210 Cache::STATS_MEMORY_AVAILABLE => $free,
Chris@0 211 );
Chris@0 212 }
Chris@0 213
Chris@0 214 /**
Chris@0 215 * Create path if needed.
Chris@0 216 *
Chris@0 217 * @param string $path
Chris@0 218 * @return bool TRUE on success or if path already exists, FALSE if path cannot be created.
Chris@0 219 */
Chris@0 220 private function createPathIfNeeded($path)
Chris@0 221 {
Chris@0 222 if ( ! is_dir($path)) {
Chris@0 223 if (false === @mkdir($path, 0777 & (~$this->umask), true) && !is_dir($path)) {
Chris@0 224 return false;
Chris@0 225 }
Chris@0 226 }
Chris@0 227
Chris@0 228 return true;
Chris@0 229 }
Chris@0 230
Chris@0 231 /**
Chris@0 232 * Writes a string content to file in an atomic way.
Chris@0 233 *
Chris@0 234 * @param string $filename Path to the file where to write the data.
Chris@0 235 * @param string $content The content to write
Chris@0 236 *
Chris@0 237 * @return bool TRUE on success, FALSE if path cannot be created, if path is not writable or an any other error.
Chris@0 238 */
Chris@0 239 protected function writeFile($filename, $content)
Chris@0 240 {
Chris@0 241 $filepath = pathinfo($filename, PATHINFO_DIRNAME);
Chris@0 242
Chris@0 243 if ( ! $this->createPathIfNeeded($filepath)) {
Chris@0 244 return false;
Chris@0 245 }
Chris@0 246
Chris@0 247 if ( ! is_writable($filepath)) {
Chris@0 248 return false;
Chris@0 249 }
Chris@0 250
Chris@0 251 $tmpFile = tempnam($filepath, 'swap');
Chris@0 252 @chmod($tmpFile, 0666 & (~$this->umask));
Chris@0 253
Chris@0 254 if (file_put_contents($tmpFile, $content) !== false) {
Chris@0 255 if (@rename($tmpFile, $filename)) {
Chris@0 256 return true;
Chris@0 257 }
Chris@0 258
Chris@0 259 @unlink($tmpFile);
Chris@0 260 }
Chris@0 261
Chris@0 262 return false;
Chris@0 263 }
Chris@0 264
Chris@0 265 /**
Chris@0 266 * @return \Iterator
Chris@0 267 */
Chris@0 268 private function getIterator()
Chris@0 269 {
Chris@0 270 return new \RecursiveIteratorIterator(
Chris@0 271 new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS),
Chris@0 272 \RecursiveIteratorIterator::CHILD_FIRST
Chris@0 273 );
Chris@0 274 }
Chris@0 275
Chris@0 276 /**
Chris@0 277 * @param string $name The filename
Chris@0 278 *
Chris@0 279 * @return bool
Chris@0 280 */
Chris@0 281 private function isFilenameEndingWithExtension($name)
Chris@0 282 {
Chris@0 283 return '' === $this->extension
Chris@0 284 || strrpos($name, $this->extension) === (strlen($name) - $this->extensionStringLength);
Chris@0 285 }
Chris@0 286 }