Skip to content

Commit 1134bb1

Browse files
authored
Default to signed for all int columns, opt-in for unsigned (#956)
* Make unsigned the default for int columns. * Opt-in for unsigned column defaults Use tearDown() to restore Configure defaults instead of running tests in separate processes. This improves test performance. * Fix MigrationHelperTest failing when run with adapter tests The adapter tests (MysqlAdapterTest, etc.) drop and recreate the database in their setUp method, which destroys the schema tables created by SchemaLoader. MigrationHelperTest then fails because the users and special_tags tables no longer exist. Use #[RunTestsInSeparateProcesses] to ensure MigrationHelperTest runs in isolation with its own fresh schema.
1 parent 1c06cf8 commit 1134bb1

23 files changed

+491
-66
lines changed

config/app.example.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
return [
88
'Migrations' => [
9-
'unsigned_primary_keys' => null,
10-
'column_null_default' => null,
9+
'unsigned_primary_keys' => null, // Default false
10+
'unsigned_ints' => null, // Default false, make sure this is aligned with the above config
11+
'column_null_default' => null, // Default false
1112
],
1213
];

docs/en/index.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ your application in your **config/app.php** file as explained in the `Database
4343
Configuration section
4444
<https://book.cakephp.org/5/en/orm/database-basics.html#database-configuration>`__.
4545

46+
Upgrading from 4.x
47+
==================
48+
49+
If you are upgrading from Migrations 4.x, please see the :doc:`upgrading` guide
50+
for breaking changes and migration steps.
51+
4652
Overview
4753
========
4854

@@ -841,6 +847,7 @@ Feature Flags
841847
Migrations offers a few feature flags for compatibility. These features are disabled by default but can be enabled if required:
842848

843849
* ``unsigned_primary_keys``: Should Migrations create primary keys as unsigned integers? (default: ``false``)
850+
* ``unsigned_ints``: Should Migrations create all integer columns as unsigned? (default: ``false``)
844851
* ``column_null_default``: Should Migrations create columns as null by default? (default: ``false``)
845852
* ``add_timestamps_use_datetime``: Should Migrations use ``DATETIME`` type
846853
columns for the columns added by ``addTimestamps()``.
@@ -849,9 +856,18 @@ Set them via Configure to enable (e.g. in ``config/app.php``)::
849856

850857
'Migrations' => [
851858
'unsigned_primary_keys' => true,
859+
'unsigned_ints' => true,
852860
'column_null_default' => true,
853861
],
854862

863+
.. note::
864+
865+
The ``unsigned_primary_keys`` and ``unsigned_ints`` options only affect MySQL databases.
866+
When generating migrations with ``bake migration_snapshot`` or ``bake migration_diff``,
867+
the ``signed`` attribute will only be included in the output for unsigned columns
868+
(as ``'signed' => false``). Signed is the default for integer columns in MySQL, so
869+
``'signed' => true`` is never output.
870+
855871
Skipping the ``schema.lock`` file generation
856872
============================================
857873

docs/en/upgrading.rst

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
Upgrading from 4.x to 5.x
2+
#########################
3+
4+
Migrations 5.x includes significant changes from 4.x. This guide outlines
5+
the breaking changes and what you need to update when upgrading.
6+
7+
Requirements
8+
============
9+
10+
- **PHP 8.2+** is now required (was PHP 8.1+)
11+
- **CakePHP 5.3+** is now required
12+
- **Phinx has been removed** - The builtin backend is now the only supported backend
13+
14+
If you were already using the builtin backend in 4.x (introduced in 4.3, default in 4.4),
15+
the upgrade should be straightforward. See :doc:`upgrading-to-builtin-backend` for more
16+
details on API differences between the phinx and builtin backends.
17+
18+
Command Changes
19+
===============
20+
21+
The phinx wrapper commands have been removed. The new command structure is:
22+
23+
Migrations
24+
----------
25+
26+
The migration commands remain unchanged:
27+
28+
.. code-block:: bash
29+
30+
bin/cake migrations migrate
31+
bin/cake migrations rollback
32+
bin/cake migrations status
33+
bin/cake migrations mark_migrated
34+
bin/cake migrations dump
35+
36+
Seeds
37+
-----
38+
39+
Seed commands have changed:
40+
41+
.. code-block:: bash
42+
43+
# 4.x # 5.x
44+
bin/cake migrations seed bin/cake seeds run
45+
bin/cake migrations seed --seed X bin/cake seeds run X
46+
47+
The new seed commands are:
48+
49+
- ``bin/cake seeds run`` - Run seed classes
50+
- ``bin/cake seeds run SeedName`` - Run a specific seed
51+
- ``bin/cake seeds status`` - Show seed execution status
52+
- ``bin/cake seeds reset`` - Reset seed execution tracking
53+
54+
Maintaining Backward Compatibility
55+
----------------------------------
56+
57+
If you need to maintain the old ``migrations seed`` command for existing scripts or
58+
CI/CD pipelines, you can add command aliases in your ``src/Application.php``::
59+
60+
public function console(CommandCollection $commands): CommandCollection
61+
{
62+
$commands = $this->addConsoleCommands($commands);
63+
64+
// Add backward compatibility alias
65+
$commands->add('migrations seed', \Migrations\Command\SeedCommand::class);
66+
67+
return $commands;
68+
}
69+
70+
Removed Classes and Namespaces
71+
==============================
72+
73+
The following have been removed in 5.x:
74+
75+
- ``Migrations\Command\Phinx\*`` - All phinx wrapper commands
76+
- ``Migrations\Command\MigrationsCommand`` - Use ``bin/cake migrations`` entry point
77+
- ``Migrations\Command\MigrationsSeedCommand`` - Use ``bin/cake seeds run``
78+
- ``Migrations\Command\MigrationsCacheBuildCommand`` - Schema cache is managed differently
79+
- ``Migrations\Command\MigrationsCacheClearCommand`` - Schema cache is managed differently
80+
- ``Migrations\Command\MigrationsCreateCommand`` - Use ``bin/cake bake migration``
81+
82+
If you have code that directly references any of these classes, you will need to update it.
83+
84+
API Changes
85+
===========
86+
87+
Adapter Query Results
88+
---------------------
89+
90+
If your migrations use ``AdapterInterface::query()`` to fetch rows, the return type has
91+
changed from a phinx result to ``Cake\Database\StatementInterface``::
92+
93+
// 4.x (phinx)
94+
$stmt = $this->getAdapter()->query('SELECT * FROM articles');
95+
$rows = $stmt->fetchAll();
96+
$row = $stmt->fetch();
97+
98+
// 5.x (builtin)
99+
$stmt = $this->getAdapter()->query('SELECT * FROM articles');
100+
$rows = $stmt->fetchAll('assoc');
101+
$row = $stmt->fetch('assoc');
102+
103+
New Features in 5.x
104+
===================
105+
106+
5.x includes several new features:
107+
108+
Seed Tracking
109+
-------------
110+
111+
Seeds are now tracked in a ``cake_seeds`` table by default, preventing accidental re-runs.
112+
Use ``--force`` to run a seed again, or ``bin/cake seeds reset`` to clear tracking.
113+
See :doc:`seeding` for more details.
114+
115+
Check Constraints
116+
-----------------
117+
118+
Support for database check constraints via ``addCheckConstraint()``.
119+
See :doc:`writing-migrations` for usage details.
120+
121+
MySQL ALTER Options
122+
-------------------
123+
124+
Support for ``ALGORITHM`` and ``LOCK`` options on MySQL ALTER TABLE operations,
125+
allowing control over how MySQL performs schema changes.
126+
127+
insertOrSkip() for Seeds
128+
------------------------
129+
130+
New ``insertOrSkip()`` method for seeds to insert records only if they don't already exist,
131+
making seeds more idempotent.
132+
133+
Migration File Compatibility
134+
============================
135+
136+
Your existing migration files should work without changes in most cases. The builtin backend
137+
provides the same API as phinx for common operations.
138+
139+
If you encounter issues with existing migrations, please report them at
140+
https://github.com/cakephp/migrations/issues

src/Command/BakeMigrationDiffCommand.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -290,14 +290,10 @@ protected function getColumns(): void
290290
}
291291
}
292292

293+
// Only convert unsigned to signed if it actually changed
293294
if (isset($changedAttributes['unsigned'])) {
294295
$changedAttributes['signed'] = !$changedAttributes['unsigned'];
295296
unset($changedAttributes['unsigned']);
296-
} else {
297-
// badish hack
298-
if (isset($column['unsigned']) && $column['unsigned'] === true) {
299-
$changedAttributes['signed'] = false;
300-
}
301297
}
302298

303299
// For decimal columns, handle CakePHP schema -> migration attribute mapping

src/Db/Adapter/MysqlAdapter.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -621,9 +621,8 @@ public function getColumns(string $tableName): array
621621
->setScale($record['precision'] ?? null)
622622
->setComment($record['comment']);
623623

624-
if ($record['unsigned'] ?? false) {
625-
$column->setSigned(!$record['unsigned']);
626-
}
624+
// Always set unsigned property based on unsigned flag
625+
$column->setUnsigned($record['unsigned'] ?? false);
627626
if ($record['autoIncrement'] ?? false) {
628627
$column->setIdentity(true);
629628
}

src/Db/Table/Column.php

Lines changed: 79 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,26 @@
1818

1919
/**
2020
* This object is based loosely on: https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html.
21+
*
22+
* ## Configuration
23+
*
24+
* The following configuration options can be set in your application's config:
25+
*
26+
* - `Migrations.unsigned_primary_keys` (bool): When true, identity columns default to unsigned.
27+
* Default: false
28+
*
29+
* - `Migrations.unsigned_ints` (bool): When true, all integer columns default to unsigned.
30+
* Default: false
31+
*
32+
* Example configuration in config/app.php:
33+
* ```php
34+
* 'Migrations' => [
35+
* 'unsigned_primary_keys' => true,
36+
* 'unsigned_ints' => true,
37+
* ]
38+
* ```
39+
*
40+
* Note: Explicitly calling setUnsigned() or setSigned() on a column will override these defaults.
2141
*/
2242
class Column extends DatabaseColumn
2343
{
@@ -493,6 +513,63 @@ public function getComment(): ?string
493513
return $this->comment;
494514
}
495515

516+
/**
517+
* Gets whether field should be unsigned.
518+
*
519+
* Checks configuration options to determine unsigned behavior:
520+
* - If explicitly set via setUnsigned/setSigned, uses that value
521+
* - If identity column and Migrations.unsigned_primary_keys is true, returns true
522+
* - If integer type and Migrations.unsigned_ints is true, returns true
523+
* - Otherwise defaults to false (signed)
524+
*
525+
* @return bool
526+
*/
527+
public function getUnsigned(): bool
528+
{
529+
// If explicitly set, use that value
530+
if ($this->unsigned !== null) {
531+
return $this->unsigned;
532+
}
533+
534+
$integerTypes = [
535+
self::INTEGER,
536+
self::BIGINTEGER,
537+
self::SMALLINTEGER,
538+
self::TINYINTEGER,
539+
];
540+
541+
// Only apply configuration to integer types
542+
if (!in_array($this->type, $integerTypes, true)) {
543+
return false;
544+
}
545+
546+
// Check if this is a primary key/identity column
547+
if ($this->identity && Configure::read('Migrations.unsigned_primary_keys')) {
548+
return true;
549+
}
550+
551+
// Check general integer configuration
552+
if (Configure::read('Migrations.unsigned_ints')) {
553+
return true;
554+
}
555+
556+
// Default to signed for backward compatibility
557+
return false;
558+
}
559+
560+
/**
561+
* Sets whether field should be unsigned.
562+
*
563+
* @param bool $unsigned Unsigned
564+
* @return $this
565+
*/
566+
public function setUnsigned(bool $unsigned)
567+
{
568+
$this->unsigned = $unsigned;
569+
570+
return $this;
571+
}
572+
496573
/**
497574
* Sets whether field should be signed.
498575
*
@@ -515,18 +592,7 @@ public function setSigned(bool $signed)
515592
*/
516593
public function getSigned(): bool
517594
{
518-
return $this->unsigned === null ? true : !$this->unsigned;
519-
}
520-
521-
/**
522-
* Should the column be signed?
523-
*
524-
* @return bool
525-
* @deprecated 5.0 Use isUnsigned() instead.
526-
*/
527-
public function isSigned(): bool
528-
{
529-
return $this->getSigned();
595+
return !$this->isUnsigned();
530596
}
531597

532598
/**
@@ -826,7 +892,7 @@ public function toArray(): array
826892
'null' => $this->getNull(),
827893
'default' => $default,
828894
'generated' => $this->getGenerated(),
829-
'unsigned' => !$this->getSigned(),
895+
'unsigned' => $this->getUnsigned(),
830896
'onUpdate' => $this->getUpdate(),
831897
'collate' => $this->getCollation(),
832898
'precision' => $precision,

src/View/Helper/MigrationHelper.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,10 @@ public function getColumnOption(array $options): array
406406

407407
if (!$isMysql) {
408408
unset($columnOptions['signed']);
409+
} elseif (isset($columnOptions['signed']) && $columnOptions['signed'] === true) {
410+
// Remove 'signed' => true since signed is the default for integer columns
411+
// Only output explicit 'signed' => false for unsigned columns
412+
unset($columnOptions['signed']);
409413
}
410414

411415
if (($isMysql || $isSqlserver) && !empty($columnOptions['collate'])) {
@@ -530,6 +534,10 @@ public function attributes(TableSchemaInterface|string $table, string $column):
530534
$isMysql = $connection->getDriver() instanceof Mysql;
531535
if (!$isMysql) {
532536
unset($attributes['signed']);
537+
} elseif (isset($attributes['signed']) && $attributes['signed'] === true) {
538+
// Remove 'signed' => true since signed is now the default for integer columns
539+
// Only output explicit 'signed' => false for unsigned columns
540+
unset($attributes['signed']);
533541
}
534542

535543
$defaultCollation = $tableSchema->getOptions()['collation'] ?? null;

tests/TestCase/Command/BakeMigrationDiffCommandTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,9 @@ public function testBakingDiff()
233233
{
234234
$this->skipIf(!env('DB_URL_COMPARE'));
235235

236+
Configure::write('Migrations.unsigned_primary_keys', true);
237+
Configure::write('Migrations.unsigned_ints', true);
238+
236239
$this->runDiffBakingTest('Default');
237240
}
238241

0 commit comments

Comments
 (0)