1<?php
2
3/*
4 * This file is part of Mustache.php.
5 *
6 * (c) 2010-2017 Justin Hileman
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12/**
13 * Mustache Cache filesystem implementation.
14 *
15 * A FilesystemCache instance caches Mustache Template classes from the filesystem by name:
16 *
17 *     $cache = new Mustache_Cache_FilesystemCache(dirname(__FILE__).'/cache');
18 *     $cache->cache($className, $compiledSource);
19 *
20 * The FilesystemCache benefits from any opcode caching that may be setup in your environment. So do that, k?
21 */
22class Mustache_Cache_FilesystemCache extends Mustache_Cache_AbstractCache
23{
24    private $baseDir;
25    private $fileMode;
26
27    /**
28     * Filesystem cache constructor.
29     *
30     * @param string $baseDir  Directory for compiled templates
31     * @param int    $fileMode Override default permissions for cache files. Defaults to using the system-defined umask
32     */
33    public function __construct($baseDir, $fileMode = null)
34    {
35        $this->baseDir = $baseDir;
36        $this->fileMode = $fileMode;
37    }
38
39    /**
40     * Load the class from cache using `require_once`.
41     *
42     * @param string $key
43     *
44     * @return bool
45     */
46    public function load($key)
47    {
48        $fileName = $this->getCacheFilename($key);
49        if (!is_file($fileName)) {
50            return false;
51        }
52
53        require_once $fileName;
54
55        return true;
56    }
57
58    /**
59     * Cache and load the compiled class.
60     *
61     * @param string $key
62     * @param string $value
63     */
64    public function cache($key, $value)
65    {
66        $fileName = $this->getCacheFilename($key);
67
68        $this->log(
69            Mustache_Logger::DEBUG,
70            'Writing to template cache: "{fileName}"',
71            array('fileName' => $fileName)
72        );
73
74        $this->writeFile($fileName, $value);
75        $this->load($key);
76    }
77
78    /**
79     * Build the cache filename.
80     * Subclasses should override for custom cache directory structures.
81     *
82     * @param string $name
83     *
84     * @return string
85     */
86    protected function getCacheFilename($name)
87    {
88        return sprintf('%s/%s.php', $this->baseDir, $name);
89    }
90
91    /**
92     * Create cache directory.
93     *
94     * @throws Mustache_Exception_RuntimeException If unable to create directory
95     *
96     * @param string $fileName
97     *
98     * @return string
99     */
100    private function buildDirectoryForFilename($fileName)
101    {
102        $dirName = dirname($fileName);
103        if (!is_dir($dirName)) {
104            $this->log(
105                Mustache_Logger::INFO,
106                'Creating Mustache template cache directory: "{dirName}"',
107                array('dirName' => $dirName)
108            );
109
110            @mkdir($dirName, 0777, true);
111            // @codeCoverageIgnoreStart
112            if (!is_dir($dirName)) {
113                throw new Mustache_Exception_RuntimeException(sprintf('Failed to create cache directory "%s".', $dirName));
114            }
115            // @codeCoverageIgnoreEnd
116        }
117
118        return $dirName;
119    }
120
121    /**
122     * Write cache file.
123     *
124     * @throws Mustache_Exception_RuntimeException If unable to write file
125     *
126     * @param string $fileName
127     * @param string $value
128     */
129    private function writeFile($fileName, $value)
130    {
131        $dirName = $this->buildDirectoryForFilename($fileName);
132
133        $this->log(
134            Mustache_Logger::DEBUG,
135            'Caching compiled template to "{fileName}"',
136            array('fileName' => $fileName)
137        );
138
139        $tempFile = tempnam($dirName, basename($fileName));
140        if (false !== @file_put_contents($tempFile, $value)) {
141            if (@rename($tempFile, $fileName)) {
142                $mode = isset($this->fileMode) ? $this->fileMode : (0666 & ~umask());
143                @chmod($fileName, $mode);
144
145                return;
146            }
147
148            // @codeCoverageIgnoreStart
149            $this->log(
150                Mustache_Logger::ERROR,
151                'Unable to rename Mustache temp cache file: "{tempName}" -> "{fileName}"',
152                array('tempName' => $tempFile, 'fileName' => $fileName)
153            );
154            // @codeCoverageIgnoreEnd
155        }
156
157        // @codeCoverageIgnoreStart
158        throw new Mustache_Exception_RuntimeException(sprintf('Failed to write cache file "%s".', $fileName));
159        // @codeCoverageIgnoreEnd
160    }
161}
162