Skip to content

Commit 2533552

Browse files
authored
Merge pull request #389 from cakephp/feature/binary-to-varbinary-rector
Add BinaryColumnToVarbinaryRector for TYPE_VARBINARY migration
1 parent 2fd859d commit 2533552

5 files changed

Lines changed: 270 additions & 0 deletions

File tree

config/rector/sets/cakephp60.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22
declare(strict_types=1);
33

4+
use Cake\Upgrade\Rector\Cake6\BinaryColumnToVarbinaryRector;
45
use Cake\Upgrade\Rector\Cake6\BreadcrumbsHelperTitleToContentRector;
56
use Cake\Upgrade\Rector\Cake6\EventManagerOnRector;
67
use Cake\Upgrade\Rector\Cake6\RemoveAssignmentFromVoidMethodRector;
@@ -1050,4 +1051,10 @@
10501051
// BreadcrumbsHelper 'title' key/parameter renamed to 'content'
10511052
// @see https://github.com/cakephp/cakephp/pull/18334
10521053
$rectorConfig->rule(BreadcrumbsHelperTitleToContentRector::class);
1054+
1055+
// Binary column type changes for TYPE_VARBINARY support
1056+
// 'binary' with 'fixed' => true becomes 'binary' (remove fixed)
1057+
// 'binary' without 'fixed' becomes 'varbinary'
1058+
// @see https://github.com/cakephp/cakephp/pull/19258
1059+
$rectorConfig->rule(BinaryColumnToVarbinaryRector::class);
10531060
};
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Cake\Upgrade\Rector\Cake6;
5+
6+
use PhpParser\Node;
7+
use PhpParser\Node\Arg;
8+
use PhpParser\Node\Expr\Array_;
9+
use PhpParser\Node\Expr\ArrayItem;
10+
use PhpParser\Node\Expr\ConstFetch;
11+
use PhpParser\Node\Expr\MethodCall;
12+
use PhpParser\Node\Identifier;
13+
use PhpParser\Node\Scalar\String_;
14+
use Rector\Rector\AbstractRector;
15+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
16+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
17+
18+
/**
19+
* Transforms binary column definitions for CakePHP 6.0 TYPE_VARBINARY support.
20+
*
21+
* In CakePHP 6.0, a new TYPE_VARBINARY was added for variable-length binary columns.
22+
* The 'fixed' attribute is no longer used - instead use 'binary' for fixed-length
23+
* and 'varbinary' for variable-length binary columns.
24+
*
25+
* @see https://github.com/cakephp/cakephp/pull/19258
26+
*/
27+
final class BinaryColumnToVarbinaryRector extends AbstractRector
28+
{
29+
public function getRuleDefinition(): RuleDefinition
30+
{
31+
return new RuleDefinition(
32+
'Change binary column with fixed attribute to use varbinary type',
33+
[
34+
new CodeSample(
35+
<<<'CODE_SAMPLE'
36+
// Migration file
37+
$table->addColumn('hash', 'binary', ['length' => 20, 'fixed' => true]);
38+
$table->addColumn('data', 'binary', ['length' => 255]);
39+
CODE_SAMPLE
40+
,
41+
<<<'CODE_SAMPLE'
42+
// Migration file
43+
$table->addColumn('hash', 'binary', ['length' => 20]);
44+
$table->addColumn('data', 'varbinary', ['length' => 255]);
45+
CODE_SAMPLE,
46+
),
47+
],
48+
);
49+
}
50+
51+
/**
52+
* @return array<class-string<\PhpParser\Node>>
53+
*/
54+
public function getNodeTypes(): array
55+
{
56+
return [MethodCall::class];
57+
}
58+
59+
/**
60+
* @param \PhpParser\Node\Expr\MethodCall $node
61+
*/
62+
public function refactor(Node $node): ?Node
63+
{
64+
if (!$node->name instanceof Identifier) {
65+
return null;
66+
}
67+
68+
$methodName = $node->name->toString();
69+
70+
// Handle addColumn() calls in migrations
71+
if ($methodName === 'addColumn') {
72+
return $this->refactorAddColumn($node);
73+
}
74+
75+
return null;
76+
}
77+
78+
/**
79+
* Refactor addColumn('name', 'binary', [...]) calls
80+
*/
81+
private function refactorAddColumn(MethodCall $node): ?MethodCall
82+
{
83+
$args = $node->args;
84+
85+
// Need at least 2 arguments: column name and type
86+
if (count($args) < 2) {
87+
return null;
88+
}
89+
90+
// Check if second argument is 'binary' type
91+
$typeArg = $args[1];
92+
if (!$typeArg instanceof Arg) {
93+
return null;
94+
}
95+
96+
if (!$typeArg->value instanceof String_) {
97+
return null;
98+
}
99+
100+
if ($typeArg->value->value !== 'binary') {
101+
return null;
102+
}
103+
104+
// Check third argument (options array) if it exists
105+
$hasFixed = false;
106+
$optionsArg = $args[2] ?? null;
107+
108+
if ($optionsArg instanceof Arg && $optionsArg->value instanceof Array_) {
109+
$hasFixed = $this->hasFixedTrue($optionsArg->value);
110+
111+
if ($hasFixed) {
112+
// Remove 'fixed' => true from the options
113+
$this->removeFixedFromArray($optionsArg->value);
114+
}
115+
}
116+
117+
// If 'fixed' => true was set, keep as 'binary' (already correct after removing fixed)
118+
// If 'fixed' was not set, change type to 'varbinary'
119+
if (!$hasFixed) {
120+
$typeArg->value = new String_('varbinary');
121+
}
122+
123+
return $node;
124+
}
125+
126+
/**
127+
* Check if array contains 'fixed' => true
128+
*/
129+
private function hasFixedTrue(Array_ $array): bool
130+
{
131+
foreach ($array->items as $item) {
132+
if (!$item instanceof ArrayItem) {
133+
continue;
134+
}
135+
136+
if (
137+
$item->key instanceof String_ &&
138+
$item->key->value === 'fixed' &&
139+
$item->value instanceof ConstFetch &&
140+
strtolower($item->value->name->toString()) === 'true'
141+
) {
142+
return true;
143+
}
144+
}
145+
146+
return false;
147+
}
148+
149+
/**
150+
* Remove 'fixed' key from array
151+
*/
152+
private function removeFixedFromArray(Array_ $array): void
153+
{
154+
$array->items = array_values(array_filter(
155+
$array->items,
156+
function ($item) {
157+
if (!$item instanceof ArrayItem) {
158+
return true;
159+
}
160+
161+
return !($item->key instanceof String_ && $item->key->value === 'fixed');
162+
},
163+
));
164+
}
165+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Cake\Upgrade\Test\TestCase\Rector\MethodCall\BinaryColumnToVarbinaryRector;
5+
6+
use Iterator;
7+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
8+
9+
final class BinaryColumnToVarbinaryRectorTest extends AbstractRectorTestCase
10+
{
11+
/**
12+
* @dataProvider provideData()
13+
*/
14+
public function test(string $filePath): void
15+
{
16+
$this->doTestFile($filePath);
17+
}
18+
19+
public static function provideData(): Iterator
20+
{
21+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
22+
}
23+
24+
public function provideConfigFilePath(): string
25+
{
26+
return __DIR__ . '/config/configured_rule.php';
27+
}
28+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
namespace Cake\Upgrade\Test\TestCase\Rector\MethodCall\BinaryColumnToVarbinaryRector\Fixture;
4+
5+
use Migrations\BaseMigration;
6+
7+
class CreateTokensTable extends BaseMigration
8+
{
9+
public function change(): void
10+
{
11+
$table = $this->table('tokens');
12+
13+
// Fixed-length binary - should remove 'fixed' => true
14+
$table->addColumn('hash', 'binary', ['length' => 32, 'fixed' => true]);
15+
16+
// Variable-length binary - should change to 'varbinary'
17+
$table->addColumn('data', 'binary', ['length' => 255]);
18+
19+
// Variable-length binary with other options - should change to 'varbinary'
20+
$table->addColumn('payload', 'binary', ['length' => 1024, 'null' => true]);
21+
22+
// Non-binary types should not be affected
23+
$table->addColumn('name', 'string', ['length' => 100]);
24+
$table->addColumn('count', 'integer');
25+
26+
$table->create();
27+
}
28+
}
29+
30+
?>
31+
-----
32+
<?php
33+
34+
namespace Cake\Upgrade\Test\TestCase\Rector\MethodCall\BinaryColumnToVarbinaryRector\Fixture;
35+
36+
use Migrations\BaseMigration;
37+
38+
class CreateTokensTable extends BaseMigration
39+
{
40+
public function change(): void
41+
{
42+
$table = $this->table('tokens');
43+
44+
// Fixed-length binary - should remove 'fixed' => true
45+
$table->addColumn('hash', 'binary', ['length' => 32]);
46+
47+
// Variable-length binary - should change to 'varbinary'
48+
$table->addColumn('data', 'varbinary', ['length' => 255]);
49+
50+
// Variable-length binary with other options - should change to 'varbinary'
51+
$table->addColumn('payload', 'varbinary', ['length' => 1024, 'null' => true]);
52+
53+
// Non-binary types should not be affected
54+
$table->addColumn('name', 'string', ['length' => 100]);
55+
$table->addColumn('count', 'integer');
56+
57+
$table->create();
58+
}
59+
}
60+
61+
?>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
use Cake\Upgrade\Rector\Cake6\BinaryColumnToVarbinaryRector;
5+
use Rector\Config\RectorConfig;
6+
7+
return static function (RectorConfig $rectorConfig): void {
8+
$rectorConfig->rule(BinaryColumnToVarbinaryRector::class);
9+
};

0 commit comments

Comments
 (0)