Skip to content

Commit bc23f61

Browse files
Merge branch 'MC-5465-requirement-ordering' into MC-5465-merged
2 parents 6732bbf + 095762c commit bc23f61

File tree

5 files changed

+364
-141
lines changed

5 files changed

+364
-141
lines changed

docs/class_descriptions.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,13 +150,17 @@ This is accomplished by comparing `composer.json` fields between the original Ma
150150
- **`findResolution()`**
151151
- For an individual field value, compare the original Magento value to the target Magento value, and if a delta is found, check if the user's installation has a customized value for the field. If the user has changed the value, resolve the conflict according to the CLI command options: use the user's custom value, override with the target Magento value, or interactively ask the user which of the two values should be used
152152
- **`resolveLinkSection()`**
153-
- For a given `composer.json` section that consists of links to package versions/constraints (such as the `require` and `conflict` sections), call `findResolution()` for each package constraint found in either the original Magento root or the target Magento root
153+
- For a given `composer.json` section that consists of links to package versions/constraints (such as the `require` and `conflict` sections), call `findLinkResolution()` for each package constraint found in either the original Magento root or the target Magento root
154154
- **`resolveArraySection()`**
155155
- For a given `composer.json` section that consists of data that is not package links (such as the `"autoload"` or `"extra"` sections), call `resolveNestedArray()` and accept the new values if changes were made
156156
- **`resolveNestedArray()`**
157157
- Recursively processes changes to a `composer.json` value that could be a nested array, calling `findResolution()` for each "leaf" value found in either the original Magento root or the target Magento root
158-
- **`linksToMap()`**
159-
- Helper function to convert a set of package links to an associative array for use by `resolveLinkSection()`
158+
- **`findLinkResolution()`**
159+
- Helper function to call `findResolution()` for a particular package for use by `resolveLinkSection()`
160+
- **`getLinkOrderOverride()`**
161+
- Determine the order to use for a link section when the user's order disagrees with the target Magento section order
162+
- **`buildLinkOrderComparator()`**
163+
- Construct the comparator function to use for sorting a set of links according to `getLinkOverride()` results followed by the order in the target Magento version followed by the order of custom values in the user's installation
160164

161165
#### [**MagentoRootUpdater**](../src/Magento/ComposerRootUpdatePlugin/Updater/MagentoRootUpdater.php)
162166

src/Magento/ComposerRootUpdatePlugin/Updater/DeltaResolver.php

Lines changed: 184 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -89,21 +89,41 @@ public function resolveRootDeltas()
8989
$target = $this->targetMageRootPackage;
9090
$user = $this->userRootPackage;
9191

92-
$this->resolveLinkSection('require', $original->getRequires(), $target->getRequires(), $user->getRequires());
92+
$this->resolveLinkSection(
93+
'require',
94+
$original->getRequires(),
95+
$target->getRequires(),
96+
$user->getRequires(),
97+
true
98+
);
9399
$this->resolveLinkSection(
94100
'require-dev',
95101
$original->getDevRequires(),
96102
$target->getDevRequires(),
97-
$user->getDevRequires()
103+
$user->getDevRequires(),
104+
true
98105
);
99106
$this->resolveLinkSection(
100107
'conflict',
101108
$original->getConflicts(),
102109
$target->getConflicts(),
103-
$user->getConflicts()
110+
$user->getConflicts(),
111+
false
112+
);
113+
$this->resolveLinkSection(
114+
'provide',
115+
$original->getProvides(),
116+
$target->getProvides(),
117+
$user->getProvides(),
118+
false
119+
);
120+
$this->resolveLinkSection(
121+
'replace',
122+
$original->getReplaces(),
123+
$target->getReplaces(),
124+
$user->getReplaces(),
125+
false
104126
);
105-
$this->resolveLinkSection('provide', $original->getProvides(), $target->getProvides(), $user->getProvides());
106-
$this->resolveLinkSection('replace', $original->getReplaces(), $target->getReplaces(), $user->getReplaces());
107127

108128
$this->resolveArraySection('autoload', $original->getAutoload(), $target->getAutoload(), $user->getAutoload());
109129
$this->resolveArraySection(
@@ -215,53 +235,26 @@ public function findResolution(
215235
* @param Link[] $originalMageLinks
216236
* @param Link[] $targetMageLinks
217237
* @param Link[] $userLinks
238+
* @param bool $verifyOrder
218239
* @return array
219240
*/
220-
public function resolveLinkSection($section, $originalMageLinks, $targetMageLinks, $userLinks)
241+
public function resolveLinkSection($section, $originalMageLinks, $targetMageLinks, $userLinks, $verifyOrder)
221242
{
222-
/** @var Link[] $originalLinkMap */
223-
$originalLinkMap = static::linksToMap($originalMageLinks);
224-
225-
/** @var Link[] $targetLinkMap */
226-
$targetLinkMap = static::linksToMap($targetMageLinks);
227-
228-
/** @var Link[] $userLinkMap */
229-
$userLinkMap = static::linksToMap($userLinks);
230-
231243
$adds = [];
232244
$removes = [];
233245
$changes = [];
234-
$magePackages = array_unique(array_merge(array_keys($originalLinkMap), array_keys($targetLinkMap)));
246+
$magePackages = array_unique(array_merge(array_keys($originalMageLinks), array_keys($targetMageLinks)));
235247
foreach ($magePackages as $pkg) {
236248
if ($section === 'require' && PackageUtils::getMagentoProductEdition($pkg)) {
237249
continue;
238250
}
239-
$field = "$section:$pkg";
240-
$originalConstraint = key_exists($pkg, $originalLinkMap) ? $originalLinkMap[$pkg]->getConstraint() : null;
241-
$originalMageVal = ($originalConstraint === null) ? null : $originalConstraint->__toString();
242-
$prettyOriginalMageVal = ($originalConstraint === null) ? null : $originalConstraint->getPrettyString();
243-
$targetConstraint = key_exists($pkg, $targetLinkMap) ? $targetLinkMap[$pkg]->getConstraint() : null;
244-
$targetMageVal = ($targetConstraint === null) ? null : $targetConstraint->__toString();
245-
$prettyTargetMageVal = ($targetConstraint === null) ? null : $targetConstraint->getPrettyString();
246-
$userConstraint = key_exists($pkg, $userLinkMap) ? $userLinkMap[$pkg]->getConstraint() : null;
247-
$userVal = ($userConstraint === null) ? null : $userConstraint->__toString();
248-
$prettyUserVal = ($userConstraint === null) ? null : $userConstraint->getPrettyString();
249-
250-
$action = $this->findResolution(
251-
$field,
252-
$originalMageVal,
253-
$targetMageVal,
254-
$userVal,
255-
$prettyOriginalMageVal,
256-
$prettyTargetMageVal,
257-
$prettyUserVal
258-
);
251+
$action = $this->findLinkResolution($section, $pkg, $originalMageLinks, $targetMageLinks, $userLinks);
259252
if ($action == static::ADD_VAL) {
260-
$adds[$pkg] = $targetLinkMap[$pkg];
253+
$adds[$pkg] = $targetMageLinks[$pkg];
261254
} elseif ($action == static::REMOVE_VAL) {
262255
$removes[] = $pkg;
263256
} elseif ($action == static::CHANGE_VAL) {
264-
$changes[$pkg] = $targetLinkMap[$pkg];
257+
$changes[$pkg] = $targetMageLinks[$pkg];
265258
}
266259
}
267260

@@ -287,11 +280,26 @@ public function resolveLinkSection($section, $originalMageLinks, $targetMageLink
287280
$this->console->labeledVerbose("Updating $section constraints: " . implode(', ', $prettyChanges));
288281
}
289282

283+
$enforcedOrder = [];
284+
if ($verifyOrder) {
285+
$enforcedOrder = $this->getLinkOrderOverride(
286+
$section,
287+
array_keys($originalMageLinks),
288+
array_keys($targetMageLinks),
289+
array_keys($userLinks)
290+
);
291+
if ($enforcedOrder !== []) {
292+
$changed = true;
293+
$prettyOrder = " [\n " . implode(",\n ", $enforcedOrder) . "\n ]";
294+
$this->console->labeledVerbose("Updating $section order:\n$prettyOrder");
295+
}
296+
}
297+
290298
if ($changed) {
291299
$replacements = array_values($adds);
292300

293301
/** @var Link $userLink */
294-
foreach ($userLinkMap as $pkg => $userLink) {
302+
foreach ($userLinks as $pkg => $userLink) {
295303
if (in_array($pkg, $removes)) {
296304
continue;
297305
} elseif (key_exists($pkg, $changes)) {
@@ -301,6 +309,12 @@ public function resolveLinkSection($section, $originalMageLinks, $targetMageLink
301309
}
302310
}
303311

312+
usort($replacements, $this->buildLinkOrderComparator(
313+
$enforcedOrder,
314+
array_keys($targetMageLinks),
315+
array_keys($userLinks)
316+
));
317+
304318
$newJson = [];
305319
/** @var Link $link */
306320
foreach ($replacements as $link) {
@@ -349,37 +363,15 @@ public function resolveNestedArray($field, $originalMageVal, $targetMageVal, $us
349363
$result = $userVal === null ? [] : $userVal;
350364

351365
if (is_array($originalMageVal) && is_array($targetMageVal) && is_array($userVal)) {
352-
$originalMageAssociativePart = [];
353-
$originalMageFlatPart = [];
354-
foreach ($originalMageVal as $key => $value) {
355-
if (is_string($key)) {
356-
$originalMageAssociativePart[$key] = $value;
357-
} else {
358-
$originalMageFlatPart[] = $value;
359-
}
360-
}
366+
$originalMageAssociativePart = array_filter($originalMageVal, 'is_string', ARRAY_FILTER_USE_KEY);
367+
$originalMageFlatPart = array_filter($originalMageVal, 'is_int', ARRAY_FILTER_USE_KEY);
361368

362-
$targetMageAssociativePart = [];
363-
$targetMageFlatPart = [];
364-
foreach ($targetMageVal as $key => $value) {
365-
if (is_string($key)) {
366-
$targetMageAssociativePart[$key] = $value;
367-
} else {
368-
$targetMageFlatPart[] = $value;
369-
}
370-
}
369+
$targetMageAssociativePart = array_filter($targetMageVal, 'is_string', ARRAY_FILTER_USE_KEY);
370+
$targetMageFlatPart = array_filter($targetMageVal, 'is_int', ARRAY_FILTER_USE_KEY);
371371

372-
$userAssociativePart = [];
373-
$userFlatPart = [];
374-
foreach ($userVal as $key => $value) {
375-
if (is_string($key)) {
376-
$userAssociativePart[$key] = $value;
377-
} else {
378-
$userFlatPart[] = $value;
379-
}
380-
}
372+
$userAssociativePart = array_filter($userVal, 'is_string', ARRAY_FILTER_USE_KEY);
381373

382-
$associativeResult = array_filter($result, 'is_string', ARRAY_FILTER_USE_KEY);
374+
$associativeResult = $userAssociativePart;
383375
$mageKeys = array_unique(
384376
array_merge(array_keys($originalMageAssociativePart), array_keys($targetMageAssociativePart))
385377
);
@@ -417,7 +409,7 @@ public function resolveNestedArray($field, $originalMageVal, $targetMageVal, $us
417409
}
418410
}
419411

420-
$flatResult = array_filter($result, 'is_int', ARRAY_FILTER_USE_KEY);
412+
$flatResult = array_filter($userVal, 'is_int', ARRAY_FILTER_USE_KEY);
421413
$flatAdds = array_diff(array_diff($targetMageFlatPart, $originalMageFlatPart), $flatResult);
422414
if ($flatAdds !== []) {
423415
$valChanged = true;
@@ -456,17 +448,132 @@ public function resolveNestedArray($field, $originalMageVal, $targetMageVal, $us
456448
}
457449

458450
/**
459-
* Helper function to convert a set of links to an associative array with target package names as keys
451+
* Helper function to find the resolution action for a package constraint in the Link sections
460452
*
461-
* @param Link[] $links
462-
* @return array
453+
* @param string $section
454+
* @param string $pkg
455+
* @param Link[] $originalLinkMap
456+
* @param Link[] $targetLinkMap
457+
* @param Link[] $userLinkMap
458+
* @return string|null ADD_VAL|REMOVE_VAL|CHANGE_VAL to adjust the link constraint, null for no change
463459
*/
464-
protected function linksToMap($links)
460+
protected function findLinkResolution($section, $pkg, $originalLinkMap, $targetLinkMap, $userLinkMap)
465461
{
466-
$targets = array_map(function ($link) {
467-
/** @var Link $link */
468-
return $link->getTarget();
469-
}, $links);
470-
return array_combine($targets, $links);
462+
$field = "$section:$pkg";
463+
$originalConstraint = key_exists($pkg, $originalLinkMap) ? $originalLinkMap[$pkg]->getConstraint() : null;
464+
$originalMageVal = ($originalConstraint === null) ? null : $originalConstraint->__toString();
465+
$prettyOriginalMageVal = ($originalConstraint === null) ? null : $originalConstraint->getPrettyString();
466+
$targetConstraint = key_exists($pkg, $targetLinkMap) ? $targetLinkMap[$pkg]->getConstraint() : null;
467+
$targetMageVal = ($targetConstraint === null) ? null : $targetConstraint->__toString();
468+
$prettyTargetMageVal = ($targetConstraint === null) ? null : $targetConstraint->getPrettyString();
469+
$userConstraint = key_exists($pkg, $userLinkMap) ? $userLinkMap[$pkg]->getConstraint() : null;
470+
$userVal = ($userConstraint === null) ? null : $userConstraint->__toString();
471+
$prettyUserVal = ($userConstraint === null) ? null : $userConstraint->getPrettyString();
472+
473+
return $this->findResolution(
474+
$field,
475+
$originalMageVal,
476+
$targetMageVal,
477+
$userVal,
478+
$prettyOriginalMageVal,
479+
$prettyTargetMageVal,
480+
$prettyUserVal
481+
);
482+
}
483+
484+
/**
485+
* Get the order to use for a link section if local and target versions disagree
486+
*
487+
* @param string $section
488+
* @param string[] $originalMageOrder
489+
* @param string[] $targetMageOrder
490+
* @param string[] $userOrder
491+
* @return string[]
492+
*/
493+
protected function getLinkOrderOverride($section, $originalMageOrder, $targetMageOrder, $userOrder)
494+
{
495+
$overrideOrder = [];
496+
497+
$conflictTargetOrder = array_values(array_intersect($targetMageOrder, $userOrder));
498+
$conflictUserOrder = array_values(array_intersect($userOrder, $targetMageOrder));
499+
500+
// Check if the user's link order does not match the target section for links that appear in both
501+
if ($conflictTargetOrder != $conflictUserOrder) {
502+
$conflictOriginalOrder = array_values(array_intersect($originalMageOrder, $targetMageOrder));
503+
504+
// Check if the user's order is different than the target order because the order has changed between
505+
// the original and target Magento versions
506+
if ($conflictOriginalOrder !== $conflictUserOrder) {
507+
$targetLabel = $this->retriever->getTargetLabel();
508+
$userOrderDesc = " [\n " . implode(",\n ", $conflictUserOrder) . "\n ]";
509+
$targetOrderDesc = " [\n " . implode(",\n ", $conflictTargetOrder) . "\n ]";
510+
$conflictDesc = "$targetLabel is trying to change the existing order of the $section section.\n" .
511+
"Local order:\n$userOrderDesc\n$targetLabel order:\n$targetOrderDesc";
512+
$shouldOverride = $this->overrideUserValues;
513+
if ($this->overrideUserValues) {
514+
$this->console->log($conflictDesc);
515+
$this->console->log(
516+
'Overriding local order due to --' . MageRootRequireCommand::OVERRIDE_OPT . '.'
517+
);
518+
} else {
519+
$shouldOverride = $this->console->ask(
520+
"$conflictDesc\nWould you like to override the local order?"
521+
);
522+
}
523+
524+
if (!$shouldOverride) {
525+
$this->console->comment("$conflictDesc but it will not be changed. Re-run using " .
526+
'--' . MageRootRequireCommand::OVERRIDE_OPT . ' or ' .
527+
'--' . MageRootRequireCommand::INTERACTIVE_OPT . ' to override with the Magento order.');
528+
$overrideOrder = $conflictUserOrder;
529+
} else {
530+
$overrideOrder = $conflictTargetOrder;
531+
}
532+
} else {
533+
$overrideOrder = $conflictTargetOrder;
534+
}
535+
}
536+
537+
return $overrideOrder;
538+
}
539+
540+
/**
541+
* Construct a comparison function to use in sorting an array of links by prioritized order lists
542+
*
543+
* @param string[] $overrideOrder
544+
* @param string[] $targetMageOrder
545+
* @param string[] $userOrder
546+
* @return \Closure
547+
*/
548+
protected function buildLinkOrderComparator($overrideOrder, $targetMageOrder, $userOrder)
549+
{
550+
$prioritizedOrderings = [$overrideOrder, $targetMageOrder, $userOrder];
551+
552+
return function ($link1, $link2) use ($prioritizedOrderings) {
553+
/**
554+
* @var Link $link1
555+
* @var Link $link2
556+
*/
557+
$package1 = $link1->getTarget();
558+
$package2 = $link2->getTarget();
559+
560+
// Check each ordering array to see if it contains both links and if so use their positions to sort
561+
// If the ordering array does not contain both links, try the next one
562+
foreach ($prioritizedOrderings as $sortOrder) {
563+
$index1 = array_search($package1, $sortOrder);
564+
$index2 = array_search($package2, $sortOrder);
565+
if ($index1 !== false && $index2 !== false) {
566+
if ($index1 == $index2) {
567+
return 0;
568+
} else {
569+
return $index1 < $index2 ? -1 : 1;
570+
}
571+
}
572+
}
573+
574+
// None of the ordering arrays contain both elements, so their relative positions in the sorted array
575+
// do not matter
576+
return 0;
577+
};
471578
}
472579
}

0 commit comments

Comments
 (0)