Passed
Push — master ( ac1451...1d2831 )
by Alan
02:29 queued 11s
created

AbstractConfigRepository::addDirectory()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 7
rs 10
1
<?php
2
3
namespace FigTree\Config;
4
5
use FigTree\Exceptions\{
6
	InvalidPathException,
7
	InvalidDirectoryException,
8
	UnreadablePathException,
9
};
10
use FigTree\Config\Contracts\{
11
	ConfigRepositoryInterface,
12
	ConfigFactoryInterface,
13
	ConfigInterface,
14
};
15
use FigTree\Config\Exceptions\{
16
    InvalidConfigFileException,
17
    InvalidConfigFilePathException,
18
};
19
20
abstract class AbstractConfigRepository implements ConfigRepositoryInterface
21
{
22
	protected ConfigFactoryInterface $factory;
23
24
	/**
25
	 * Config file directories.
26
	 *
27
	 * @var array
28
	 */
29
	protected $directories = [];
30
31
	/**
32
	 * Cached Config objects.
33
	 *
34
	 * @var array
35
	 */
36
	protected $configs = [];
37
38
	/**
39
	 * Get the directories to search for a Config file.
40
	 *
41
	 * @return array
42
	 */
43
	public function getDirectories(): array
44
	{
45
		return $this->directories;
46
	}
47
48
	/**
49
	 * Add a directory to search for a Config file.
50
	 *
51
	 * @param string $directory
52
	 *
53
	 * @return $this
54
	 */
55
	public function addDirectory(string $directory): ConfigRepositoryInterface
56
	{
57
		$dir = $this->resolveDirectory($directory);
58
59
		$this->directories[] = $dir;
60
61
		return $this;
62
	}
63
64
	/**
65
	 * Search for a Config file from the list of applicable directories.
66
	 * Returns null if it could not be found.
67
	 *
68
	 * @param string $fileName
69
	 *
70
	 * @return \FigTree\Config\Contracts\ConfigInterface
71
	 *
72
	 * @throws \FigTree\Config\Exceptions\InvalidConfigFileException
73
	 */
74
	public function get(string $fileName): ConfigInterface
75
	{
76
		if (key_exists($fileName, $this->configs)) {
77
			return $this->configs[$fileName];
78
		}
79
80
		$paths = $this->search($fileName);
81
82
		if (empty($paths)) {
83
			throw new InvalidConfigFileException($fileName);
84
		}
85
86
		return $this->configs[$fileName] = $this->factory->create($paths);
87
	}
88
89
	/**
90
	 * Find all instances of the given file within configured directories.
91
	 *
92
	 * @param string $fileName
93
	 *
94
	 * @return array
95
	 */
96
	protected function search(string $fileName): array
97
	{
98
		$paths = [];
99
100
		foreach ($this->directories as $directory) {
101
			$fullPath = $this->resolveFile($directory, $fileName . '.php');
102
103
			if (!empty($fullPath)) {
104
				$paths[] = $fullPath;
105
			}
106
		}
107
108
		return $paths;
109
	}
110
111
	/**
112
	 * Resolve a given directory to an absolute path.
113
	 *
114
	 * @param string $directory
115
	 *
116
	 * @return string
117
	 *
118
	 * @throws \FigTree\Exceptions\InvalidDirectoryException
119
	 * @throws \FigTree\Exceptions\InvalidPathException
120
	 * @throws \FigTree\Exceptions\UnreadablePathException
121
	 */
122
	protected function resolveDirectory(string $directory): string
123
	{
124
		$dir = realpath($directory);
125
126
		if (empty($dir)) {
127
			throw new InvalidPathException($directory);
128
		}
129
130
		if (!is_dir($dir)) {
131
			throw new InvalidDirectoryException($directory);
132
		}
133
134
		if (!is_readable($dir)) {
135
			throw new UnreadablePathException(sprintf('Directory %s is not readable.', $dir));
136
		}
137
138
		return $dir;
139
	}
140
141
	/**
142
	 * Resolve a file in a given directory to an absolute path,
143
	 * additionally ensuring the file exists within that directory.
144
	 *
145
	 * @param string $directory
146
	 * @param string $file
147
	 *
148
	 * @return string|null
149
	 *
150
	 * @throws \FigTree\Config\Exceptions\InvalidConfigFilePathException
151
	 */
152
	protected function resolveFile(string $directory, string $file): ?string
153
	{
154
		$prefix = $directory . DIRECTORY_SEPARATOR;
155
156
		$path = realpath($prefix . $file);
157
158
		if (empty($path) || !is_file($path) || !is_readable($path)) {
159
			return null;
160
		}
161
162
		if (!str_starts_with($path, $prefix)) {
163
			throw new InvalidConfigFilePathException($file);
164
		}
165
166
		return $path;
167
	}
168
}
169