Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion src/Configuration/Parameter/SimpleParameterProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ public static function hashForCacheInvalidation(): string

ksort($strictParameters);

return sha1(serialize($strictParameters));
return sha1(serialize(self::canonicalizeObjects($strictParameters, [])));
}

/**
Expand Down Expand Up @@ -231,6 +231,42 @@ private static function relativizeProjectPaths(array $parameters, string $projec
return $parameters;
}

/**
* serialize() writes object properties in declaration order, which differs for the same class
* between runtimes: PHPStan Turbo replaces e.g. PHPStan\Type\MixedType with a native class
* declaring its properties in another order. The main process and the Turbo workers would then
* hash the same configuration differently and keep clearing each other's cache.
*
* @param array<int, true> $visitedObjectIds
*/
private static function canonicalizeObjects(mixed $value, array $visitedObjectIds): mixed
{
if (is_array($value)) {
return array_map(
static fn (mixed $item): mixed => self::canonicalizeObjects($item, $visitedObjectIds),
$value
);
}

if (! is_object($value)) {
return $value;
}

$objectId = spl_object_id($value);
if (isset($visitedObjectIds[$objectId])) {
return $value::class;
}

$visitedObjectIds[$objectId] = true;

$properties = (array) $value;
ksort($properties);

return [
$value::class => self::canonicalizeObjects($properties, $visitedObjectIds),
];
}

/**
* Normalized to "/" separators, so the prefix compares alike with config paths on every platform.
* Empty when the working directory cannot be resolved, which makes the relativizing above a
Expand Down
29 changes: 29 additions & 0 deletions tests/Configuration/ConfigurationFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Rector\Console\ProcessConfigureDecorator;
use Rector\FileSystem\FilesFinder;
use Rector\Testing\PHPUnit\AbstractLazyTestCase;
use stdClass;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;

Expand All @@ -20,6 +21,7 @@ protected function tearDown(): void
SimpleParameterProvider::setParameter(Option::IS_RUN_NARROWED, false);
SimpleParameterProvider::setParameter(Option::SOURCE, []);
SimpleParameterProvider::setParameter(Option::PATHS, []);
SimpleParameterProvider::setParameter(Option::RULE_CONFIGURATIONS, []);
}

public function test(): void
Expand Down Expand Up @@ -93,6 +95,33 @@ public function testSourceParameterIsOutsideTheCacheInvalidationHash(): void
$this->assertSame($hashForOnePath, SimpleParameterProvider::hashForCacheInvalidation());
}

public function testObjectPropertyOrderIsOutsideTheCacheInvalidationHash(): void
{
// PHPStan Turbo workers declare e.g. MixedType properties in another order than the main process,
// the same configured value must hash alike in both
$firstOrder = new stdClass();
$firstOrder->isExplicitMixed = false;
$firstOrder->subtractedType = null;

SimpleParameterProvider::setParameter(Option::RULE_CONFIGURATIONS, [
'SomeRector' => [$firstOrder],
]);
$firstOrderHash = SimpleParameterProvider::hashForCacheInvalidation();

$secondOrder = new stdClass();
$secondOrder->subtractedType = null;
$secondOrder->isExplicitMixed = false;

SimpleParameterProvider::setParameter(Option::RULE_CONFIGURATIONS, [
'SomeRector' => [$secondOrder],
]);

$this->assertSame($firstOrderHash, SimpleParameterProvider::hashForCacheInvalidation());

$secondOrder->isExplicitMixed = true;
$this->assertNotSame($firstOrderHash, SimpleParameterProvider::hashForCacheInvalidation());
}

/**
* @param string[] $paths
*/
Expand Down
Loading