Passed
Branch merge-configs (a25828)
by Alan
03:09
created

AbstractConfigRepository::resolveDirectory()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 8
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 17
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 = [];
81
82
		foreach ($this->directories as $directory) {
83
			$fullPath = $this->resolveFile($directory, $fileName . '.php');
84
85
			if (!empty($fullPath)) {
86
				$paths[] = $fullPath;
87
			}
88
		}
89
90
		if (empty($paths)) {
91
			throw new InvalidConfigFileException($fileName);
92
		}
93
94
		return $this->configs[$fileName] = $this->factory->create($paths);
95
	}
96
97
	/**
98
	 * Resolve a given directory to an absolute path.
99
	 *
100
	 * @param string $directory
101
	 *
102
	 * @return string
103
	 *
104
	 * @throws \FigTree\Exceptions\InvalidDirectoryException
105
	 * @throws \FigTree\Exceptions\InvalidPathException
106
	 * @throws \FigTree\Exceptions\UnreadablePathException
107
	 */
108
	protected function resolveDirectory(string $directory): string
109
	{
110
		$dir = realpath($directory);
111
112
		if (empty($dir)) {
113
			throw new InvalidPathException($directory);
114
		}
115
116
		if (!is_dir($dir)) {
117
			throw new InvalidDirectoryException($directory);
118
		}
119
120
		if (!is_readable($dir)) {
121
			throw new UnreadablePathException(sprintf('Directory %s is not readable.', $dir));
122
		}
123
124
		return $dir;
125
	}
126
127
	/**
128
	 * Resolve a file in a given directory to an absolute path,
129
	 * additionally ensuring the file exists within that directory.
130
	 *
131
	 * @param string $directory
132
	 * @param string $file
133
	 *
134
	 * @return string|null
135
	 *
136
	 * @throws \FigTree\Config\Exceptions\InvalidConfigFilePathException
137
	 */
138
	protected function resolveFile(string $directory, string $file): ?string
139
	{
140
		$prefix = $directory . DIRECTORY_SEPARATOR;
141
142
		$path = realpath($prefix . $file);
143
144
		if (empty($path) || !is_file($path) || !is_readable($path)) {
145
			return null;
146
		}
147
148
		if (!str_starts_with($path, $prefix)) {
149
			throw new InvalidConfigFilePathException($file);
150
		}
151
152
		return $path;
153
	}
154
}
155