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