Skip to content

Commit c270ae4

Browse files
committed
update all schema dump lock files + add a script to automatically update those schema dump files
1 parent 155235e commit c270ae4

File tree

11 files changed

+235
-0
lines changed

11 files changed

+235
-0
lines changed
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* # Check which Diff lock fixtures need refresh
6+
* php tests/comparisons/Diff/refresh_schema_dumps.php
7+
*
8+
* # Regenerate all Diff lock fixtures in place
9+
* php tests/comparisons/Diff/refresh_schema_dumps.php --write
10+
*
11+
* # Regenerate only one fixture
12+
* php tests/comparisons/Diff/refresh_schema_dumps.php --write --file tests/comparisons/Diff/decimalChange/schema-dump-test_comparisons_mysql.lock
13+
*/
14+
15+
require dirname(__DIR__, 3) . '/vendor/autoload.php';
16+
17+
const EXIT_OK = 0;
18+
const EXIT_UPDATES_AVAILABLE = 1;
19+
const EXIT_ERROR = 2;
20+
21+
/**
22+
* Defaults for known typed properties that can be introduced between CakePHP releases.
23+
*
24+
* @var array<string, array<string, mixed>>
25+
*/
26+
$knownDefaults = [
27+
Cake\Database\Schema\Column::class => [
28+
'fixed' => false,
29+
],
30+
];
31+
32+
$options = getopt('', ['write', 'file:']);
33+
$write = isset($options['write']);
34+
$filesOption = $options['file'] ?? [];
35+
$selectedFiles = is_array($filesOption) ? $filesOption : [$filesOption];
36+
37+
$files = getFixtureFiles(__DIR__, $selectedFiles);
38+
if ($files === []) {
39+
fwrite(STDERR, "No schema dump fixtures found.\n");
40+
exit(EXIT_ERROR);
41+
}
42+
43+
$updatedFiles = [];
44+
$hasPendingUpdates = false;
45+
46+
foreach ($files as $file) {
47+
$serialized = file_get_contents($file);
48+
if ($serialized === false) {
49+
fwrite(STDERR, "Failed to read: {$file}\n");
50+
exit(EXIT_ERROR);
51+
}
52+
53+
$data = @unserialize($serialized);
54+
if ($data === false && $serialized !== serialize(false)) {
55+
fwrite(STDERR, "Failed to unserialize: {$file}\n");
56+
exit(EXIT_ERROR);
57+
}
58+
59+
$visited = new SplObjectStorage();
60+
fixUninitializedTypedProperties($data, $knownDefaults, $visited, $file);
61+
62+
$normalized = serialize($data);
63+
if ($normalized === $serialized) {
64+
continue;
65+
}
66+
67+
$hasPendingUpdates = true;
68+
if ($write) {
69+
if (file_put_contents($file, $normalized) === false) {
70+
fwrite(STDERR, "Failed to write: {$file}\n");
71+
exit(EXIT_ERROR);
72+
}
73+
$updatedFiles[] = $file;
74+
echo "Updated {$file}\n";
75+
} else {
76+
echo "Needs update {$file}\n";
77+
}
78+
}
79+
80+
if (!$hasPendingUpdates) {
81+
echo "All schema dump fixtures are up to date.\n";
82+
exit(EXIT_OK);
83+
}
84+
85+
if (!$write) {
86+
echo "Run with --write to regenerate fixtures.\n";
87+
exit(EXIT_UPDATES_AVAILABLE);
88+
}
89+
90+
echo sprintf("Updated %d fixture(s).\n", count($updatedFiles));
91+
exit(EXIT_OK);
92+
93+
/**
94+
* @param array<int, string> $selectedFiles
95+
* @return array<int, string>
96+
*/
97+
function getFixtureFiles(string $baseDir, array $selectedFiles): array
98+
{
99+
if ($selectedFiles !== []) {
100+
$files = [];
101+
foreach ($selectedFiles as $file) {
102+
if ($file === '') {
103+
continue;
104+
}
105+
$resolved = str_starts_with($file, '/')
106+
? $file
107+
: realpath(getcwd() . DIRECTORY_SEPARATOR . $file);
108+
if ($resolved === false || !is_file($resolved)) {
109+
fwrite(STDERR, "Invalid --file path: {$file}\n");
110+
exit(EXIT_ERROR);
111+
}
112+
$files[] = $resolved;
113+
}
114+
115+
return $files;
116+
}
117+
118+
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($baseDir));
119+
$files = [];
120+
foreach ($iterator as $item) {
121+
if (!$item->isFile()) {
122+
continue;
123+
}
124+
$path = $item->getPathname();
125+
if (preg_match('/schema-dump-.*\.lock$/', $path) === 1) {
126+
$files[] = $path;
127+
}
128+
}
129+
sort($files);
130+
131+
return $files;
132+
}
133+
134+
/**
135+
* @param array<string, array<string, mixed>> $knownDefaults
136+
*/
137+
function fixUninitializedTypedProperties(
138+
mixed &$value,
139+
array $knownDefaults,
140+
SplObjectStorage $visited,
141+
string $file
142+
): void {
143+
if (is_array($value)) {
144+
foreach ($value as &$nested) {
145+
fixUninitializedTypedProperties($nested, $knownDefaults, $visited, $file);
146+
}
147+
unset($nested);
148+
149+
return;
150+
}
151+
152+
if (!is_object($value) || $visited->offsetExists($value)) {
153+
return;
154+
}
155+
156+
$visited[$value] = true;
157+
$class = new ReflectionClass($value);
158+
$defaultsByClass = $knownDefaults[$class->getName()] ?? [];
159+
160+
foreach (getAllProperties($class) as $property) {
161+
if ($property->isStatic()) {
162+
continue;
163+
}
164+
165+
if (!$property->isInitialized($value)) {
166+
if (array_key_exists($property->getName(), $defaultsByClass)) {
167+
$property->setValue($value, $defaultsByClass[$property->getName()]);
168+
continue;
169+
}
170+
$inferred = inferDefaultValue($property);
171+
if ($inferred['ok'] === false) {
172+
$className = $class->getName();
173+
$propertyName = $property->getName();
174+
fwrite(
175+
STDERR,
176+
"Cannot infer default for uninitialized typed property " .
177+
"{$className}::\${$propertyName} in {$file}\n"
178+
);
179+
exit(EXIT_ERROR);
180+
}
181+
$property->setValue($value, $inferred['value']);
182+
}
183+
184+
$nested = $property->getValue($value);
185+
fixUninitializedTypedProperties($nested, $knownDefaults, $visited, $file);
186+
$property->setValue($value, $nested);
187+
}
188+
}
189+
190+
/**
191+
* @return array<int, ReflectionProperty>
192+
*/
193+
function getAllProperties(ReflectionClass $class): array
194+
{
195+
$properties = [];
196+
do {
197+
foreach ($class->getProperties() as $property) {
198+
$declaringClass = $property->getDeclaringClass()->getName();
199+
$name = $declaringClass . '::' . $property->getName();
200+
$properties[$name] = $property;
201+
}
202+
$class = $class->getParentClass();
203+
} while ($class !== false);
204+
205+
return array_values($properties);
206+
}
207+
208+
/**
209+
* @return array{ok: bool, value?: mixed}
210+
*/
211+
function inferDefaultValue(ReflectionProperty $property): array
212+
{
213+
$type = $property->getType();
214+
if ($type === null) {
215+
return ['ok' => true, 'value' => null];
216+
}
217+
218+
if ($type->allowsNull()) {
219+
return ['ok' => true, 'value' => null];
220+
}
221+
222+
if ($type instanceof ReflectionNamedType) {
223+
return match ($type->getName()) {
224+
'bool', 'false' => ['ok' => true, 'value' => false],
225+
'true' => ['ok' => true, 'value' => true],
226+
'int' => ['ok' => true, 'value' => 0],
227+
'float' => ['ok' => true, 'value' => 0.0],
228+
'string' => ['ok' => true, 'value' => ''],
229+
'array' => ['ok' => true, 'value' => []],
230+
default => ['ok' => false],
231+
};
232+
}
233+
234+
return ['ok' => false];
235+
}
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)