1<?php
2
3declare(strict_types=1);
4
5namespace Metadata\Cache;
6
7use Metadata\ClassMetadata;
8
9class FileCache implements CacheInterface
10{
11    /**
12     * @var string
13     */
14    private $dir;
15
16    public function __construct(string $dir)
17    {
18        if (!is_dir($dir)) {
19            throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist.', $dir));
20        }
21
22        $this->dir = rtrim($dir, '\\/');
23    }
24
25    /**
26     * {@inheritDoc}
27     */
28    public function load(string $class): ?ClassMetadata
29    {
30        $path = $this->dir . '/' . strtr($class, '\\', '-') . '.cache.php';
31        if (!file_exists($path)) {
32            return null;
33        }
34
35        return include $path;
36    }
37
38    /**
39     * {@inheritDoc}
40     */
41    public function put(ClassMetadata $metadata): void
42    {
43        if (!is_writable($this->dir)) {
44            throw new \InvalidArgumentException(sprintf('The directory "%s" is not writable.', $this->dir));
45        }
46
47        $path = $this->dir . '/' . strtr($metadata->name, '\\', '-') . '.cache.php';
48
49        $tmpFile = tempnam($this->dir, 'metadata-cache');
50        file_put_contents($tmpFile, '<?php return unserialize(' . var_export(serialize($metadata), true) . ');');
51
52        // Let's not break filesystems which do not support chmod.
53        @chmod($tmpFile, 0666 & ~umask());
54
55        $this->renameFile($tmpFile, $path);
56    }
57
58    /**
59     * Renames a file with fallback for windows
60     *
61     */
62    private function renameFile(string $source, string $target): void
63    {
64        if (false === @rename($source, $target)) {
65            if (defined('PHP_WINDOWS_VERSION_BUILD')) {
66                if (false === copy($source, $target)) {
67                    throw new \RuntimeException(sprintf('(WIN) Could not write new cache file to %s.', $target));
68                }
69                if (false === unlink($source)) {
70                    throw new \RuntimeException(sprintf('(WIN) Could not delete temp cache file to %s.', $source));
71                }
72            } else {
73                throw new \RuntimeException(sprintf('Could not write new cache file to %s.', $target));
74            }
75        }
76    }
77
78    /**
79     * {@inheritDoc}
80     */
81    public function evict(string $class): void
82    {
83        $path = $this->dir . '/' . strtr($class, '\\', '-') . '.cache.php';
84        if (file_exists($path)) {
85            unlink($path);
86        }
87    }
88}
89