1: <?php
2:
3: namespace Charcoal\Loader;
4:
5: use \InvalidArgumentException;
6:
7:
8: use \Psr\Log\LoggerAwareInterface;
9: use \Psr\Log\LoggerAwareTrait;
10: use \Psr\Log\NullLogger;
11:
12: 13: 14:
15: class FileLoader implements
16: LoggerAwareInterface
17: {
18: use LoggerAwareTrait;
19:
20: 21: 22: 23: 24:
25: protected $ident;
26:
27: 28: 29: 30: 31:
32: protected $paths = [];
33:
34: 35: 36: 37: 38:
39: private $basePath = '';
40:
41: 42: 43: 44: 45:
46: public function __construct(array $data = null)
47: {
48: if (isset($data['base_path'])) {
49: $this->setBasePath($data['base_path']);
50: }
51:
52: if (isset($data['paths'])) {
53: $this->addPaths($data['paths']);
54: }
55:
56: if (!isset($data['logger'])) {
57: $data['logger'] = new NullLogger();
58: }
59:
60: $this->setLogger($data['logger']);
61: }
62:
63: 64: 65: 66: 67:
68: public function ident()
69: {
70: return $this->ident;
71: }
72:
73: 74: 75: 76: 77: 78: 79:
80: public function setIdent($ident)
81: {
82: if (!is_string($ident)) {
83: throw new InvalidArgumentException(
84: sprintf(
85: 'Identifier for [%1$s] must be a string.',
86: get_called_class()
87: )
88: );
89: }
90:
91: $this->ident = $ident;
92:
93: return $this;
94: }
95:
96: 97: 98: 99: 100:
101: public function basePath()
102: {
103: return $this->basePath;
104: }
105:
106: 107: 108: 109: 110: 111: 112:
113: public function setBasePath($basePath)
114: {
115: if (!is_string($basePath)) {
116: throw new InvalidArgumentException(
117: 'Base path must be a string'
118: );
119: }
120:
121: $basePath = realpath($basePath);
122:
123: $this->basePath = rtrim($basePath, '/\\').DIRECTORY_SEPARATOR;
124:
125: return $this;
126: }
127:
128: 129: 130: 131: 132: 133:
134: public function load($ident = null)
135: {
136: if ($ident === null) {
137: return '';
138: }
139:
140: $fileContent = $this->loadFirstFromSearchPath($ident);
141:
142: if ($fileContent) {
143: return $fileContent;
144: }
145:
146: return '';
147: }
148:
149: 150: 151: 152: 153: 154:
155: protected function loadFirstFromSearchPath($filename)
156: {
157: $file = $this->firstMatchingFilename($filename);
158:
159: if ($file) {
160: return file_get_contents($file);
161: }
162:
163: return null;
164: }
165:
166: 167: 168: 169: 170: 171:
172: protected function firstMatchingFilename($filename)
173: {
174: if (file_exists($filename)) {
175: return $filename;
176: }
177:
178: $paths = $this->paths();
179:
180: if (empty($paths)) {
181: return null;
182: }
183:
184: foreach ($paths as $path) {
185: $file = $path.DIRECTORY_SEPARATOR.$filename;
186: if (file_exists($file)) {
187: return $file;
188: }
189: }
190:
191: return null;
192: }
193:
194: 195: 196: 197: 198: 199:
200: protected function allMatchingFilenames($filename)
201: {
202: $matches = [];
203:
204: if (file_exists($filename)) {
205: $matches[] = $filename;
206: }
207:
208: $paths = $this->paths();
209:
210: if (empty($paths)) {
211: return $matches;
212: }
213:
214: foreach ($paths as $path) {
215: $file = $path.DIRECTORY_SEPARATOR.$filename;
216: if (file_exists($file)) {
217: $matches[] = $file;
218: }
219: }
220:
221: return $matches;
222: }
223: 224: 225: 226: 227: 228: 229:
230: protected function loadJsonFile($filename)
231: {
232: $content = file_get_contents($filename);
233:
234: if ($content === null) {
235: return null;
236: }
237:
238: $data = json_decode($content, true);
239: $error = json_last_error();
240:
241: if ($error == JSON_ERROR_NONE) {
242: return $data;
243: }
244:
245: switch ($error) {
246: case JSON_ERROR_NONE:
247: break;
248: case JSON_ERROR_DEPTH:
249: $issue = 'Maximum stack depth exceeded';
250: break;
251: case JSON_ERROR_STATE_MISMATCH:
252: $issue = 'Underflow or the modes mismatch';
253: break;
254: case JSON_ERROR_CTRL_CHAR:
255: $issue = 'Unexpected control character found';
256: break;
257: case JSON_ERROR_SYNTAX:
258: $issue = 'Syntax error, malformed JSON';
259: break;
260: case JSON_ERROR_UTF8:
261: $issue = 'Malformed UTF-8 characters, possibly incorrectly encoded';
262: break;
263: default:
264: $issue = 'Unknown error';
265: break;
266: }
267:
268: throw new InvalidArgumentException(
269: sprintf('JSON %s could not be parsed: "%s"', $filename, $issue)
270: );
271: }
272:
273: 274: 275: 276: 277:
278: public function paths()
279: {
280: return $this->paths;
281: }
282:
283: 284: 285: 286: 287: 288:
289: public function setPaths(array $paths)
290: {
291: $this->paths = [];
292: $this->addPaths($paths);
293:
294: return $this;
295: }
296:
297: 298: 299: 300: 301: 302:
303: public function addPaths(array $paths)
304: {
305: foreach ($paths as $path) {
306: $this->addPath($path);
307: }
308:
309: return $this;
310: }
311:
312: 313: 314: 315: 316: 317: 318:
319: public function addPath($path)
320: {
321: $path = $this->resolvePath($path);
322:
323: if ($path && $this->validatePath($path)) {
324: $this->paths[] = $path;
325: }
326:
327: return $this;
328: }
329:
330: 331: 332: 333: 334: 335:
336: public function prependPath($path)
337: {
338: $path = $this->resolvePath($path);
339:
340: if ($path && $this->validatePath($path)) {
341: array_unshift($this->paths, $path);
342: }
343:
344: return $this;
345: }
346:
347: 348: 349: 350: 351: 352: 353:
354: public function resolvePath($path)
355: {
356: if (!is_string($path)) {
357: throw new InvalidArgumentException(
358: 'Path needs to be a string'
359: );
360: }
361:
362: $basePath = $this->basePath();
363: $path = ltrim($path, '/\\');
364:
365: if ($basePath && strpos($path, $basePath) === false) {
366: $path = $basePath.$path;
367: }
368:
369: return $path;
370: }
371:
372: 373: 374: 375: 376: 377:
378: public function validatePath($path)
379: {
380: return file_exists($path);
381: }
382: }
383: