1
|
|
|
<?php |
2
|
|
|
namespace Consolidation\SiteAlias; |
3
|
|
|
|
4
|
|
|
use Consolidation\Config\Loader\ConfigProcessor; |
5
|
|
|
use Dflydev\DotAccessData\Util as DotAccessDataUtil; |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* Discover alias files: |
9
|
|
|
* |
10
|
|
|
* - sitename.site.yml: contains multiple aliases, one for each of the |
11
|
|
|
* environments of 'sitename'. |
12
|
|
|
*/ |
13
|
|
|
class SiteAliasFileLoader |
14
|
|
|
{ |
15
|
|
|
/** |
16
|
|
|
* @var SiteAliasFileDiscovery |
17
|
|
|
*/ |
18
|
|
|
protected $discovery; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* @var array |
22
|
|
|
*/ |
23
|
|
|
protected $referenceData; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* @var array |
27
|
|
|
*/ |
28
|
|
|
protected $loader; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* SiteAliasFileLoader constructor |
32
|
|
|
* |
33
|
|
|
* @param SiteAliasFileDiscovery|null $discovery |
34
|
|
|
*/ |
35
|
|
|
public function __construct($discovery = null) |
36
|
|
|
{ |
37
|
|
|
$this->discovery = $discovery ?: new SiteAliasFileDiscovery(); |
38
|
|
|
$this->referenceData = []; |
39
|
|
|
$this->loader = []; |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Allow configuration data to be used in replacements in the alias file. |
44
|
|
|
*/ |
45
|
|
|
public function setReferenceData($data) |
46
|
|
|
{ |
47
|
|
|
$this->referenceData = $data; |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Add a search location to our discovery object. |
52
|
|
|
* |
53
|
|
|
* @param string $path |
54
|
|
|
* |
55
|
|
|
* @return $this |
56
|
|
|
*/ |
57
|
|
|
public function addSearchLocation($path) |
58
|
|
|
{ |
59
|
|
|
$this->discovery()->addSearchLocation($path); |
60
|
|
|
return $this; |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* Return our discovery object. |
65
|
|
|
* |
66
|
|
|
* @return SiteAliasFileDiscovery |
67
|
|
|
*/ |
68
|
|
|
public function discovery() |
69
|
|
|
{ |
70
|
|
|
return $this->discovery; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Load the file containing the specified alias name. |
75
|
|
|
* |
76
|
|
|
* @param SiteAliasName $aliasName |
77
|
|
|
* |
78
|
|
|
* @return AliasRecord|false |
79
|
|
|
*/ |
80
|
|
|
public function load(SiteAliasName $aliasName) |
81
|
|
|
{ |
82
|
|
|
// First attempt to load a sitename.site.yml file for the alias. |
83
|
|
|
$aliasRecord = $this->loadSingleAliasFile($aliasName); |
84
|
|
|
if ($aliasRecord) { |
85
|
|
|
return $aliasRecord; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
// If aliasname was provides as @site.env and we did not find it, |
89
|
|
|
// then we are done. |
90
|
|
|
if ($aliasName->hasSitename()) { |
91
|
|
|
return false; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
// If $aliasName was provided as `@foo` (`hasSitename()` returned `false` |
|
|
|
|
95
|
|
|
// above), then this was interpreted as `@self.foo` when we searched |
96
|
|
|
// above. If we could not find an alias record for `@self.foo`, then we |
97
|
|
|
// will try to search again, this time with the assumption that `@foo` |
98
|
|
|
// might be `@foo.<default>`, where `<default>` is the default |
99
|
|
|
// environment for the specified site. Note that in this instance, the |
100
|
|
|
// sitename will be found in $aliasName->env(). |
101
|
|
|
$sitename = $aliasName->env(); |
102
|
|
|
return $this->loadDefaultEnvFromSitename($sitename); |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Given only a site name, load the default environment from it. |
107
|
|
|
*/ |
108
|
|
|
protected function loadDefaultEnvFromSitename($sitename) |
109
|
|
|
{ |
110
|
|
|
$path = $this->discovery()->findSingleSiteAliasFile($sitename); |
111
|
|
|
if (!$path) { |
|
|
|
|
112
|
|
|
return false; |
113
|
|
|
} |
114
|
|
|
$data = $this->loadSiteDataFromPath($path); |
115
|
|
|
if (!$data) { |
116
|
|
|
return false; |
117
|
|
|
} |
118
|
|
|
$env = $this->getDefaultEnvironmentName($data); |
119
|
|
|
|
120
|
|
|
$aliasName = new SiteAliasName($sitename, $env); |
121
|
|
|
$processor = new ConfigProcessor(); |
122
|
|
|
return $this->fetchAliasRecordFromSiteAliasData($aliasName, $processor, $data); |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* Return a list of all site aliases loadable from any findable path. |
127
|
|
|
* |
128
|
|
|
* @return AliasRecord[] |
129
|
|
|
*/ |
130
|
|
|
public function loadAll() |
131
|
|
|
{ |
132
|
|
|
$result = []; |
133
|
|
|
$paths = $this->discovery()->findAllSingleAliasFiles(); |
134
|
|
|
foreach ($paths as $path) { |
135
|
|
|
$aliasRecords = $this->loadSingleSiteAliasFileAtPath($path); |
136
|
|
|
if ($aliasRecords) { |
137
|
|
|
foreach ($aliasRecords as $aliasRecord) { |
138
|
|
|
$this->storeAliasRecordInResut($result, $aliasRecord); |
139
|
|
|
} |
140
|
|
|
} |
141
|
|
|
} |
142
|
|
|
ksort($result); |
143
|
|
|
return $result; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Return a list of all available alias files. Does not include |
148
|
|
|
* legacy files. |
149
|
|
|
* |
150
|
|
|
* @return string[] |
151
|
|
|
*/ |
152
|
|
|
public function listAll() |
153
|
|
|
{ |
154
|
|
|
return $this->discovery()->findAllSingleAliasFiles(); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* Given an alias name that might represent multiple sites, |
159
|
|
|
* return a list of all matching alias records. If nothing was found, |
160
|
|
|
* or the name represents a single site + env, then we take |
161
|
|
|
* no action and return `false`. |
162
|
|
|
* |
163
|
|
|
* @param string $sitename The site name to return all environments for. |
164
|
|
|
* @return AliasRecord[]|false |
165
|
|
|
*/ |
166
|
|
|
public function loadMultiple($sitename) |
167
|
|
|
{ |
168
|
|
|
if ($path = $this->discovery()->findSingleSiteAliasFile($sitename)) { |
169
|
|
|
if ($siteData = $this->loadSiteDataFromPath($path)) { |
170
|
|
|
// Convert the raw array into a list of alias records. |
171
|
|
|
return $this->createAliasRecordsFromSiteData($sitename, $siteData); |
172
|
|
|
} |
173
|
|
|
} |
174
|
|
|
return false; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* @param array $siteData list of sites with its respective data |
179
|
|
|
* |
180
|
|
|
* @param SiteAliasName $aliasName The name of the record being created |
|
|
|
|
181
|
|
|
* @param $siteData An associative array of envrionment => site data |
182
|
|
|
* @return AliasRecord[] |
183
|
|
|
*/ |
184
|
|
|
protected function createAliasRecordsFromSiteData($sitename, $siteData) |
185
|
|
|
{ |
186
|
|
|
$result = []; |
187
|
|
|
if (!is_array($siteData) || empty($siteData)) { |
188
|
|
|
return $result; |
189
|
|
|
} |
190
|
|
|
foreach ($siteData as $envName => $data) { |
191
|
|
|
if (is_array($data)) { |
192
|
|
|
$aliasName = new SiteAliasName($sitename, $envName); |
193
|
|
|
|
194
|
|
|
$processor = new ConfigProcessor(); |
195
|
|
|
$oneRecord = $this->fetchAliasRecordFromSiteAliasData($aliasName, $processor, $siteData); |
196
|
|
|
$this->storeAliasRecordInResut($result, $oneRecord); |
|
|
|
|
197
|
|
|
} |
198
|
|
|
} |
199
|
|
|
return $result; |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* Store an alias record in a list. If the alias record has |
204
|
|
|
* a known name, then the key of the list will be the record's name. |
205
|
|
|
* Otherwise, append the record to the end of the list with |
206
|
|
|
* a numeric index. |
207
|
|
|
* |
208
|
|
|
* @param &AliasRecord[] $result list of alias records |
|
|
|
|
209
|
|
|
* @param AliasRecord $aliasRecord one more alias to store in the result |
210
|
|
|
*/ |
211
|
|
|
protected function storeAliasRecordInResut(&$result, AliasRecord $aliasRecord) |
212
|
|
|
{ |
213
|
|
|
if (!$aliasRecord) { |
214
|
|
|
return; |
215
|
|
|
} |
216
|
|
|
$key = $aliasRecord->name(); |
217
|
|
|
if (empty($key)) { |
218
|
|
|
$result[] = $aliasRecord; |
219
|
|
|
return; |
220
|
|
|
} |
221
|
|
|
$result[$key] = $aliasRecord; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* If the alias name is '@sitename', or if it is '@sitename.env', then |
226
|
|
|
* look for a sitename.site.yml file that contains it. |
227
|
|
|
* |
228
|
|
|
* @param SiteAliasName $aliasName |
229
|
|
|
* |
230
|
|
|
* @return AliasRecord|false |
231
|
|
|
*/ |
232
|
|
|
protected function loadSingleAliasFile(SiteAliasName $aliasName) |
233
|
|
|
{ |
234
|
|
|
// Check to see if the appropriate sitename.alias.yml file can be |
235
|
|
|
// found. Return if it cannot. |
236
|
|
|
$path = $this->discovery()->findSingleSiteAliasFile($aliasName->sitename()); |
237
|
|
|
if (!$path) { |
|
|
|
|
238
|
|
|
return false; |
239
|
|
|
} |
240
|
|
|
return $this->loadSingleAliasFileWithNameAtPath($aliasName, $path); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Given only the path to an alias file `site.alias.yml`, return all |
245
|
|
|
* of the alias records for every environment stored in that file. |
246
|
|
|
* |
247
|
|
|
* @param string $path |
248
|
|
|
* @return AliasRecord[] |
249
|
|
|
*/ |
250
|
|
|
protected function loadSingleSiteAliasFileAtPath($path) |
251
|
|
|
{ |
252
|
|
|
$sitename = $this->siteNameFromPath($path); |
253
|
|
|
if ($siteData = $this->loadSiteDataFromPath($path)) { |
254
|
|
|
return $this->createAliasRecordsFromSiteData($sitename, $siteData); |
255
|
|
|
} |
256
|
|
|
return false; |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* Given the path to a single site alias file `site.alias.yml`, |
261
|
|
|
* return the `site` part. |
262
|
|
|
* |
263
|
|
|
* @param string $path |
264
|
|
|
*/ |
265
|
|
|
protected function siteNameFromPath($path) |
266
|
|
|
{ |
267
|
|
|
return $this->basenameWithoutExtension($path, '.site.yml'); |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
/** |
271
|
|
|
* Chop off the `aliases.yml` or `alias.yml` part of a path. This works |
272
|
|
|
* just like `basename`, except it will throw if the provided path |
273
|
|
|
* does not end in the specified extension. |
274
|
|
|
* |
275
|
|
|
* @param string $path |
276
|
|
|
* @param string $extension |
277
|
|
|
* @return string |
278
|
|
|
* @throws \Exception |
279
|
|
|
*/ |
280
|
|
|
protected function basenameWithoutExtension($path, $extension) |
281
|
|
|
{ |
282
|
|
|
$result = basename($path, $extension); |
283
|
|
|
// It is an error if $path does not end with site.yml |
284
|
|
|
if ($result == basename($path)) { |
285
|
|
|
throw new \Exception("$path must end with '$extension'"); |
286
|
|
|
} |
287
|
|
|
return $result; |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* Given an alias name and a path, load the data from the path |
292
|
|
|
* and process it as needed to generate the alias record. |
293
|
|
|
* |
294
|
|
|
* @param SiteAliasName $aliasName |
295
|
|
|
* @param string $path |
296
|
|
|
* @return AliasRecord|false |
297
|
|
|
*/ |
298
|
|
|
protected function loadSingleAliasFileWithNameAtPath(SiteAliasName $aliasName, $path) |
299
|
|
|
{ |
300
|
|
|
$data = $this->loadSiteDataFromPath($path); |
301
|
|
|
if (!$data) { |
302
|
|
|
return false; |
303
|
|
|
} |
304
|
|
|
$processor = new ConfigProcessor(); |
305
|
|
|
return $this->fetchAliasRecordFromSiteAliasData($aliasName, $processor, $data); |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
/** |
309
|
|
|
* Load the yml from the given path |
310
|
|
|
* |
311
|
|
|
* @param string $path |
312
|
|
|
* @return array|bool |
313
|
|
|
*/ |
314
|
|
|
protected function loadSiteDataFromPath($path) |
315
|
|
|
{ |
316
|
|
|
$data = $this->loadData($path); |
317
|
|
|
if (!$data) { |
|
|
|
|
318
|
|
|
return false; |
319
|
|
|
} |
320
|
|
|
$selfSiteAliases = $this->findSelfSiteAliases($data); |
321
|
|
|
$data = array_merge($data, $selfSiteAliases); |
322
|
|
|
return $data; |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
* Given an array of site aliases, find the first one that is |
327
|
|
|
* local (has no 'host' item) and also contains a 'self.site.yml' file. |
328
|
|
|
* @param array $data |
|
|
|
|
329
|
|
|
* @return array |
330
|
|
|
*/ |
331
|
|
|
protected function findSelfSiteAliases($site_aliases) |
332
|
|
|
{ |
333
|
|
|
foreach ($site_aliases as $site => $data) { |
334
|
|
|
if (!isset($data['host']) && isset($data['root'])) { |
335
|
|
|
foreach (['.', '..'] as $relative_path) { |
336
|
|
|
$candidate = $data['root'] . '/' . $relative_path . '/drush/sites/self.site.yml'; |
337
|
|
|
if (file_exists($candidate)) { |
338
|
|
|
return $this->loadData($candidate); |
339
|
|
|
} |
340
|
|
|
} |
341
|
|
|
} |
342
|
|
|
} |
343
|
|
|
return []; |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* Load the contents of the specified file. |
348
|
|
|
* |
349
|
|
|
* @param string $path Path to file to load |
350
|
|
|
* @return array |
351
|
|
|
*/ |
352
|
|
|
protected function loadData($path) |
353
|
|
|
{ |
354
|
|
|
if (empty($path) || !file_exists($path)) { |
355
|
|
|
return []; |
356
|
|
|
} |
357
|
|
|
$loader = $this->getLoader(pathinfo($path, PATHINFO_EXTENSION)); |
358
|
|
|
if (!$loader) { |
359
|
|
|
return []; |
360
|
|
|
} |
361
|
|
|
return $loader->load($path); |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
/** |
365
|
|
|
* @return DataFileLoaderInterface |
366
|
|
|
*/ |
367
|
|
|
public function getLoader($extension) |
368
|
|
|
{ |
369
|
|
|
if (!isset($this->loader[$extension])) { |
370
|
|
|
return null; |
371
|
|
|
} |
372
|
|
|
return $this->loader[$extension]; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
public function addLoader($extension, DataFileLoaderInterface $loader) |
376
|
|
|
{ |
377
|
|
|
$this->loader[$extension] = $loader; |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
/** |
381
|
|
|
* Given an array containing site alias data, return an alias record |
382
|
|
|
* containing the data for the requested record. If there is a 'common' |
383
|
|
|
* section, then merge that in as well. |
384
|
|
|
* |
385
|
|
|
* @param SiteAliasName $aliasName the alias we are loading |
386
|
|
|
* @param array $data |
387
|
|
|
* |
388
|
|
|
* @return AliasRecord|false |
389
|
|
|
*/ |
390
|
|
|
protected function fetchAliasRecordFromSiteAliasData(SiteAliasName $aliasName, ConfigProcessor $processor, array $data) |
391
|
|
|
{ |
392
|
|
|
$data = $this->adjustIfSingleAlias($data); |
393
|
|
|
$env = $this->getEnvironmentName($aliasName, $data); |
394
|
|
|
if (!$this->siteEnvExists($data, $env)) { |
395
|
|
|
return false; |
396
|
|
|
} |
397
|
|
|
|
398
|
|
|
// Add the 'common' section if it exists. |
399
|
|
|
if (isset($data['common']) && is_array($data['common'])) { |
400
|
|
|
$processor->add($data['common']); |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
// Then add the data from the desired environment. |
404
|
|
|
$processor->add($data[$env]); |
405
|
|
|
|
406
|
|
|
// Export the combined data and create an AliasRecord object to manage it. |
407
|
|
|
return new AliasRecord($processor->export($this->referenceData), '@' . $aliasName->sitename(), $env); |
408
|
|
|
} |
409
|
|
|
|
410
|
|
|
/** |
411
|
|
|
* Determine whether there is a valid-looking environment '$env' in the |
412
|
|
|
* provided site alias data. |
413
|
|
|
* |
414
|
|
|
* @param array $data |
415
|
|
|
* @param string $env |
416
|
|
|
* @return bool |
417
|
|
|
*/ |
418
|
|
|
protected function siteEnvExists(array $data, $env) |
419
|
|
|
{ |
420
|
|
|
return ( |
421
|
|
|
is_array($data) && |
422
|
|
|
isset($data[$env]) && |
423
|
|
|
is_array($data[$env]) |
424
|
|
|
); |
425
|
|
|
} |
426
|
|
|
|
427
|
|
|
/** |
428
|
|
|
* Adjust the alias data for a single-site alias. Usually, a .yml alias |
429
|
|
|
* file will contain multiple entries, one for each of the environments |
430
|
|
|
* of an alias. If there are no environments |
431
|
|
|
* |
432
|
|
|
* @param array $data |
433
|
|
|
* @return array |
434
|
|
|
*/ |
435
|
|
|
protected function adjustIfSingleAlias($data) |
436
|
|
|
{ |
437
|
|
|
if (!$this->detectSingleAlias($data)) { |
438
|
|
|
return $data; |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
$result = [ |
442
|
|
|
'default' => $data, |
443
|
|
|
]; |
444
|
|
|
|
445
|
|
|
return $result; |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
/** |
449
|
|
|
* A single-environment alias looks something like this: |
450
|
|
|
* |
451
|
|
|
* --- |
452
|
|
|
* root: /path/to/drupal |
453
|
|
|
* uri: https://mysite.org |
454
|
|
|
* |
455
|
|
|
* A multiple-environment alias looks something like this: |
456
|
|
|
* |
457
|
|
|
* --- |
458
|
|
|
* default: dev |
459
|
|
|
* dev: |
460
|
|
|
* root: /path/to/dev |
461
|
|
|
* uri: https://dev.mysite.org |
462
|
|
|
* stage: |
463
|
|
|
* root: /path/to/stage |
464
|
|
|
* uri: https://stage.mysite.org |
465
|
|
|
* |
466
|
|
|
* The differentiator between these two is that the multi-environment |
467
|
|
|
* alias always has top-level elements that are associative arrays, and |
468
|
|
|
* the single-environment alias never does. |
469
|
|
|
* |
470
|
|
|
* @param array $data |
471
|
|
|
* @return bool |
472
|
|
|
*/ |
473
|
|
|
protected function detectSingleAlias($data) |
474
|
|
|
{ |
475
|
|
|
foreach ($data as $key => $value) { |
476
|
|
|
if (is_array($value) && DotAccessDataUtil::isAssoc($value)) { |
477
|
|
|
return false; |
478
|
|
|
} |
479
|
|
|
} |
480
|
|
|
return true; |
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
/** |
484
|
|
|
* Return the name of the environment requested. |
485
|
|
|
* |
486
|
|
|
* @param SiteAliasName $aliasName the alias we are loading |
487
|
|
|
* @param array $data |
488
|
|
|
* |
489
|
|
|
* @return string |
490
|
|
|
*/ |
491
|
|
|
protected function getEnvironmentName(SiteAliasName $aliasName, array $data) |
492
|
|
|
{ |
493
|
|
|
// If the alias name specifically mentions the environment |
494
|
|
|
// to use, then return it. |
495
|
|
|
if ($aliasName->hasEnv()) { |
496
|
|
|
return $aliasName->env(); |
497
|
|
|
} |
498
|
|
|
return $this->getDefaultEnvironmentName($data); |
499
|
|
|
} |
500
|
|
|
|
501
|
|
|
/** |
502
|
|
|
* Given a data array containing site alias environments, determine which |
503
|
|
|
* envirionmnet should be used as the default environment. |
504
|
|
|
* |
505
|
|
|
* @param array $data |
506
|
|
|
* @return string |
507
|
|
|
*/ |
508
|
|
|
protected function getDefaultEnvironmentName(array $data) |
509
|
|
|
{ |
510
|
|
|
// If there is an entry named 'default', it will either contain the |
511
|
|
|
// name of the environment to use by default, or it will itself be |
512
|
|
|
// the default environment. |
513
|
|
|
if (isset($data['default'])) { |
514
|
|
|
return is_array($data['default']) ? 'default' : $data['default']; |
515
|
|
|
} |
516
|
|
|
// If there is an environment named 'dev', it will be our default. |
517
|
|
|
if (isset($data['dev'])) { |
518
|
|
|
return 'dev'; |
519
|
|
|
} |
520
|
|
|
// If we don't know which environment to use, just take the first one. |
521
|
|
|
$keys = array_keys($data); |
522
|
|
|
return reset($keys); |
|
|
|
|
523
|
|
|
} |
524
|
|
|
} |
525
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.