1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Config\Resource;
13
14/**
15 * DirectoryResource represents a resources stored in a subdirectory tree.
16 *
17 * @author Fabien Potencier <fabien@symfony.com>
18 */
19class DirectoryResource implements SelfCheckingResourceInterface, \Serializable
20{
21    private $resource;
22    private $pattern;
23
24    /**
25     * @param string      $resource The file path to the resource
26     * @param string|null $pattern  A pattern to restrict monitored files
27     *
28     * @throws \InvalidArgumentException
29     */
30    public function __construct($resource, $pattern = null)
31    {
32        $this->resource = realpath($resource) ?: (file_exists($resource) ? $resource : false);
33        $this->pattern = $pattern;
34
35        if (false === $this->resource || !is_dir($this->resource)) {
36            throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist.', $resource));
37        }
38    }
39
40    /**
41     * {@inheritdoc}
42     */
43    public function __toString()
44    {
45        return md5(serialize([$this->resource, $this->pattern]));
46    }
47
48    /**
49     * @return string The file path to the resource
50     */
51    public function getResource()
52    {
53        return $this->resource;
54    }
55
56    /**
57     * Returns the pattern to restrict monitored files.
58     *
59     * @return string|null
60     */
61    public function getPattern()
62    {
63        return $this->pattern;
64    }
65
66    /**
67     * {@inheritdoc}
68     */
69    public function isFresh($timestamp)
70    {
71        if (!is_dir($this->resource)) {
72            return false;
73        }
74
75        if ($timestamp < filemtime($this->resource)) {
76            return false;
77        }
78
79        foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->resource), \RecursiveIteratorIterator::SELF_FIRST) as $file) {
80            // if regex filtering is enabled only check matching files
81            if ($this->pattern && $file->isFile() && !preg_match($this->pattern, $file->getBasename())) {
82                continue;
83            }
84
85            // always monitor directories for changes, except the .. entries
86            // (otherwise deleted files wouldn't get detected)
87            if ($file->isDir() && '/..' === substr($file, -3)) {
88                continue;
89            }
90
91            // for broken links
92            try {
93                $fileMTime = $file->getMTime();
94            } catch (\RuntimeException $e) {
95                continue;
96            }
97
98            // early return if a file's mtime exceeds the passed timestamp
99            if ($timestamp < $fileMTime) {
100                return false;
101            }
102        }
103
104        return true;
105    }
106
107    /**
108     * @internal
109     */
110    public function serialize()
111    {
112        return serialize([$this->resource, $this->pattern]);
113    }
114
115    /**
116     * @internal
117     */
118    public function unserialize($serialized)
119    {
120        list($this->resource, $this->pattern) = unserialize($serialized);
121    }
122}
123