Skip to content

Commit 87280f4

Browse files
Merge pull request #172 from magento-commerce/develop
MCLOUD-14199 - Merge develop branch into 1.4
2 parents 96875f6 + a6cd5fd commit 87280f4

File tree

3 files changed

+184
-9
lines changed

3 files changed

+184
-9
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "magento/magento-cloud-docker",
33
"description": "Magento Cloud Docker",
44
"type": "magento2-component",
5-
"version": "1.4.5",
5+
"version": "1.4.6",
66
"license": [
77
"OSL-3.0",
88
"AFL-3.0"

src/Config/Source/CloudSource.php

Lines changed: 103 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
use Magento\CloudDocker\Service\ServiceFactory;
1616
use Magento\CloudDocker\Service\ServiceInterface;
1717
use Symfony\Component\Yaml\Yaml;
18+
use Symfony\Component\Yaml\Tag\TaggedValue;
19+
1820
use Exception;
1921

2022
/**
@@ -67,23 +69,35 @@ public function __construct(
6769
$this->serviceFactory = $serviceFactory;
6870
}
6971

70-
/**
71-
* @inheritDoc
72-
* @throws ConfigurationMismatchException
73-
*/
72+
/**
73+
* @inheritDoc
74+
*
75+
* @SuppressWarnings("PMD.CyclomaticComplexity")
76+
* @SuppressWarnings("PMD.NPathComplexity")
77+
*/
7478
public function read(): Repository
7579
{
76-
$appConfigFile = $this->fileList->getAppConfig();
80+
$appConfigFile = $this->fileList->getAppConfig();
7781
$servicesConfigFile = $this->fileList->getServicesConfig();
7882

7983
if (!$this->filesystem->exists($appConfigFile) || !$this->filesystem->exists($servicesConfigFile)) {
8084
return new Repository();
8185
}
8286

8387
try {
88+
$flags = 0;
89+
if (defined(Yaml::class . '::PARSE_CONSTANT')) {
90+
$flags |= Yaml::PARSE_CONSTANT;
91+
}
92+
if (defined(Yaml::class . '::PARSE_CUSTOM_TAGS')) {
93+
$flags |= Yaml::PARSE_CUSTOM_TAGS;
94+
}
8495
$appConfig = Yaml::parse(
85-
$this->filesystem->get($this->fileList->getAppConfig())
96+
$this->filesystem->get($this->fileList->getAppConfig()),
97+
$flags
8698
);
99+
100+
$appConfig = $this->normalizeYamlData($appConfig);
87101
} catch (\Exception $exception) {
88102
throw new SourceException($exception->getMessage(), $exception->getCode(), $exception);
89103
}
@@ -148,9 +162,83 @@ public function read(): Repository
148162
}
149163

150164
/**
165+
* Recursively normalizes Symfony YAML TaggedValue objects into PHP-native values.
166+
*
167+
* Handles the following YAML tags:
168+
* - !env: resolves environment variables.
169+
* - !include: parses and normalizes included YAML files.
170+
* - !php/const: resolves PHP constants (e.g. !php/const:\PDO::ATTR_ERRMODE).
171+
* - Other or unknown tags: recursively normalize their values.
172+
*
173+
* Ensures all YAML data is converted to scalars or arrays suitable for safe merging.
174+
*
175+
* @param mixed $data The parsed YAML data (array, scalar, or TaggedValue).
176+
* @return mixed The normalized data structure.
177+
*
178+
* @SuppressWarnings("PHPMD.NPathComplexity")
179+
* @SuppressWarnings("PHPMD.CyclomaticComplexity") Method is intentionally complex due to tag resolution logic.
180+
*/
181+
private function normalizeYamlData(mixed $data): mixed
182+
{
183+
if ($data instanceof TaggedValue) {
184+
$tag = $data->getTag(); // e.g. "php/const:\PDO::MYSQL_ATTR_LOCAL_INFILE"
185+
$value = $data->getValue();
186+
187+
// Handle php/const tags (Symfony strips leading '!')
188+
if (str_starts_with($tag, 'php/const:')) {
189+
// Extract the constant name
190+
$constName = substr($tag, strlen('php/const:'));
191+
$constName = ltrim($constName, '\\');
192+
193+
// Resolve the constant name to its value if defined
194+
$constKey = defined($constName) ? constant($constName) : $constName;
195+
196+
// Handle YAML quirk where ": 1" is parsed literally
197+
$raw = is_string($value) ? $value : (string)$value;
198+
$cleanVal = str_replace([':', ' '], '', $raw);
199+
$constVal = is_numeric($cleanVal) ? (int)$cleanVal : $cleanVal;
200+
201+
return [$constKey => $constVal];
202+
}
203+
204+
// Handle !env
205+
if ($tag === 'env') {
206+
$envValue = getenv((string)$value);
207+
return $envValue !== false ? $envValue : null;
208+
}
209+
210+
// Handle !include
211+
if ($tag === 'include') {
212+
if (file_exists((string)$value)) {
213+
$included = Yaml::parseFile((string)$value);
214+
return $this->normalizeYamlData($included);
215+
}
216+
return null;
217+
}
218+
219+
// Default — recursively normalize nested tagged structures
220+
$normalized = $this->normalizeYamlData($value);
221+
return is_array($normalized) ? $normalized : [$normalized];
222+
}
223+
224+
// Recursively normalize arrays
225+
if (is_array($data)) {
226+
foreach ($data as $key => $value) {
227+
$data[$key] = $this->normalizeYamlData($value);
228+
}
229+
}
230+
231+
return $data;
232+
}
233+
234+
/**
235+
* Adds service relationships to the repository.
236+
*
151237
* @param Repository $repository
152238
* @param array $relationships
239+
*
153240
* @return Repository
241+
*
154242
* @throws SourceException
155243
*/
156244
private function addRelationships(Repository $repository, array $relationships): Repository
@@ -363,8 +451,16 @@ private function addMailhog(Repository $repository): Repository
363451
private function getServiceConfig(): array
364452
{
365453
try {
454+
$flags = 0;
455+
if (defined(Yaml::class . '::PARSE_CONSTANT')) {
456+
$flags |= Yaml::PARSE_CONSTANT;
457+
}
458+
if (defined(Yaml::class . '::PARSE_CUSTOM_TAGS')) {
459+
$flags |= Yaml::PARSE_CUSTOM_TAGS;
460+
}
366461
$servicesConfig = Yaml::parse(
367-
$this->filesystem->get($this->fileList->getServicesConfig())
462+
$this->filesystem->get($this->fileList->getServicesConfig()),
463+
$flags
368464
);
369465
} catch (Exception $exception) {
370466
throw new SourceException($exception->getMessage(), $exception->getCode(), $exception);

tests/functional/Codeception/TestInfrastructure.php

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Composer\Factory;
1111
use Composer\IO\NullIO;
1212
use Symfony\Component\Yaml\Yaml;
13+
use Symfony\Component\Yaml\Tag\TaggedValue;
1314

1415
/**
1516
* The module to work with test infrastructure
@@ -699,7 +700,85 @@ public function writeServicesYaml(array $data): bool
699700
*/
700701
private function readYamlConfiguration(string $path): array
701702
{
702-
return Yaml::parseFile($path);
703+
$flags = 0;
704+
if (defined(Yaml::class . '::PARSE_CONSTANT')) {
705+
$flags |= Yaml::PARSE_CONSTANT;
706+
}
707+
if (defined(Yaml::class . '::PARSE_CUSTOM_TAGS')) {
708+
$flags |= Yaml::PARSE_CUSTOM_TAGS;
709+
}
710+
$data = (array) Yaml::parseFile($path, $flags);
711+
return $this->normalizeYamlData($data) ?: [];
712+
}
713+
714+
/**
715+
* Recursively normalizes Symfony YAML TaggedValue objects into PHP-native values.
716+
*
717+
* Handles the following YAML tags:
718+
* - !env: resolves environment variables.
719+
* - !include: parses and normalizes included YAML files.
720+
* - !php/const: resolves PHP constants (e.g. !php/const:\PDO::ATTR_ERRMODE).
721+
* - Other or unknown tags: recursively normalize their values.
722+
*
723+
* Ensures all YAML data is converted to scalars or arrays suitable for safe merging.
724+
*
725+
* @param mixed $data The parsed YAML data (array, scalar, or TaggedValue).
726+
* @return mixed The normalized data structure.
727+
*
728+
* @SuppressWarnings("PHPMD.NPathComplexity")
729+
* @SuppressWarnings("PHPMD.CyclomaticComplexity") Method is intentionally complex due to tag resolution logic.
730+
*/
731+
private function normalizeYamlData(mixed $data): mixed
732+
{
733+
if ($data instanceof TaggedValue) {
734+
$tag = $data->getTag(); // e.g. "php/const:\PDO::MYSQL_ATTR_LOCAL_INFILE"
735+
$value = $data->getValue();
736+
737+
// Handle php/const tags (Symfony strips leading '!')
738+
if (str_starts_with($tag, 'php/const:')) {
739+
// Extract the constant name
740+
$constName = substr($tag, strlen('php/const:'));
741+
$constName = ltrim($constName, '\\');
742+
743+
// Resolve the constant name to its value if defined
744+
$constKey = defined($constName) ? constant($constName) : $constName;
745+
746+
// Handle YAML quirk where ": 1" is parsed literally
747+
$raw = is_string($value) ? $value : (string)$value;
748+
$cleanVal = str_replace([':', ' '], '', $raw);
749+
$constVal = is_numeric($cleanVal) ? (int)$cleanVal : $cleanVal;
750+
751+
return [$constKey => $constVal];
752+
}
753+
754+
// Handle !env
755+
if ($tag === 'env') {
756+
$envValue = getenv((string)$value);
757+
return $envValue !== false ? $envValue : null;
758+
}
759+
760+
// Handle !include
761+
if ($tag === 'include') {
762+
if (file_exists((string)$value)) {
763+
$included = Yaml::parseFile((string)$value);
764+
return $this->normalizeYamlData($included);
765+
}
766+
return null;
767+
}
768+
769+
// Default — recursively normalize nested tagged structures
770+
$normalized = $this->normalizeYamlData($value);
771+
return is_array($normalized) ? $normalized : [$normalized];
772+
}
773+
774+
// Recursively normalize arrays
775+
if (is_array($data)) {
776+
foreach ($data as $key => $value) {
777+
$data[$key] = $this->normalizeYamlData($value);
778+
}
779+
}
780+
781+
return $data;
703782
}
704783

705784
/**

0 commit comments

Comments
 (0)