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
45 changes: 44 additions & 1 deletion docs/en/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ also edit the migration after generation to add or customize the columns

Columns on the command line follow the following pattern::

fieldName:fieldType?[length]:indexType:indexName
fieldName:fieldType?[length]:default[value]:indexType:indexName

For instance, the following are all valid ways of specifying an email field:

Expand All @@ -238,6 +238,16 @@ Columns with a question mark after the fieldType will make the column nullable.

The ``length`` part is optional and should always be written between bracket.

The ``default[value]`` part is optional and sets the default value for the column.
Supported value types include:

* Booleans: ``true`` or ``false`` - e.g., ``active:boolean:default[true]``
* Integers: ``0``, ``123``, ``-456`` - e.g., ``count:integer:default[0]``
* Floats: ``1.5``, ``-2.75`` - e.g., ``rate:decimal:default[1.5]``
* Strings: ``'hello'`` or ``"world"`` (quoted) - e.g., ``status:string:default['pending']``
* Null: ``null`` or ``NULL`` - e.g., ``description:text?:default[null]``
* SQL expressions: ``CURRENT_TIMESTAMP`` - e.g., ``created_at:datetime:default[CURRENT_TIMESTAMP]``

Fields named ``created`` and ``modified``, as well as any field with a ``_at``
suffix, will automatically be set to the type ``datetime``.

Expand Down Expand Up @@ -318,6 +328,39 @@ will generate::
}
}

Adding a column with a default value
-------------------------------------

You can specify default values for columns using the ``default[value]`` syntax:

.. code-block:: bash

bin/cake bake migration AddActiveToUsers active:boolean:default[true]

will generate::

<?php
use Migrations\BaseMigration;

class AddActiveToUsers extends BaseMigration
{
public function change(): void
{
$table = $this->table('users');
$table->addColumn('active', 'boolean', [
'default' => true,
'null' => false,
]);
$table->update();
}
}

You can combine default values with other options like nullable and indexes:

.. code-block:: bash

bin/cake bake migration AddStatusToOrders status:string:default['pending']:unique

Altering a column
-----------------

Expand Down
11 changes: 10 additions & 1 deletion src/Command/BakeMigrationCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,17 @@ public function getOptionParser(): ConsoleOptionParser

When describing columns you can use the following syntax:

<warning>{name}:{primary}{type}{nullable}[{length}]:{index}:{indexName}</warning>
<warning>{name}:{type}{nullable}[{length}]:default[{value}]:{index}:{indexName}</warning>

All sections other than name are optional.

* The types are the abstract database column types in CakePHP.
* The <warning>?</warning> value indicates if a column is nullable.
e.g. <warning>role:string?</warning>.
* Length option must be enclosed in <warning>[]</warning>, for example: <warning>name:string?[100]</warning>.
* The <warning>default[value]</warning> option sets a default value for the column.
Supports booleans (true/false), integers, floats, strings, and null.
e.g. <warning>active:boolean:default[true]</warning>, <warning>count:integer:default[0]</warning>.
* The <warning>index</warning> attribute can define the column as having a unique
key with <warning>unique</warning> or a primary key with <warning>primary</warning>.
* Use <warning>references</warning> type to create a foreign key constraint.
Expand Down Expand Up @@ -214,6 +217,12 @@ public function getOptionParser(): ConsoleOptionParser
Create a migration that adds a foreign key column (<warning>category_id</warning>) to the <warning>articles</warning>
table referencing the <warning>categories</warning> table.

<warning>bin/cake bake migration AddActiveToUsers active:boolean:default[true]</warning>
Create a migration that adds an <warning>active</warning> column with a default value of <warning>true</warning>.

<warning>bin/cake bake migration AddCountToProducts count:integer:default[0]:unique</warning>
Create a migration that adds a <warning>count</warning> column with default <warning>0</warning> and a unique index.

<info>Migration Styles</info>

You can generate migrations in different styles:
Expand Down
84 changes: 73 additions & 11 deletions src/Util/ColumnParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class ColumnParser
(?:,(?:[0-9]|[1-9][0-9]+))?
\])?
))?
(?::default\[([^\]]+)\])?
(?::(\w+))?
(?::(\w+))?
$
Expand All @@ -54,7 +55,8 @@ public function parseFields(array $arguments): array
preg_match($this->regexpParseColumn, $field, $matches);
$field = $matches[1];
$type = Hash::get($matches, 2, '');
$indexType = Hash::get($matches, 3);
$defaultValue = Hash::get($matches, 3);
$indexType = Hash::get($matches, 4);

$typeIsPk = in_array($type, ['primary', 'primary_key'], true);
$isPrimaryKey = false;
Expand All @@ -80,7 +82,7 @@ public function parseFields(array $arguments): array
'columnType' => $type,
'options' => [
'null' => $nullable,
'default' => null,
'default' => $this->parseDefaultValue($defaultValue, $type ?? 'string'),
],
];

Expand Down Expand Up @@ -114,8 +116,8 @@ public function parseIndexes(array $arguments): array
preg_match($this->regexpParseColumn, $field, $matches);
$field = $matches[1];
$type = Hash::get($matches, 2);
$indexType = Hash::get($matches, 3);
$indexName = Hash::get($matches, 4);
$indexType = Hash::get($matches, 4);
$indexName = Hash::get($matches, 5);

// Skip references - they create foreign keys, not indexes
if ($type && str_starts_with($type, 'references')) {
Expand Down Expand Up @@ -168,7 +170,7 @@ public function parsePrimaryKey(array $arguments): array
preg_match($this->regexpParseColumn, $field, $matches);
$field = $matches[1];
$type = Hash::get($matches, 2);
$indexType = Hash::get($matches, 3);
$indexType = Hash::get($matches, 4);

if (
in_array($type, ['primary', 'primary_key'], true)
Expand Down Expand Up @@ -196,8 +198,8 @@ public function parseForeignKeys(array $arguments): array
preg_match($this->regexpParseColumn, $field, $matches);
$fieldName = $matches[1];
$type = Hash::get($matches, 2, '');
$indexType = Hash::get($matches, 3);
$indexName = Hash::get($matches, 4);
$indexType = Hash::get($matches, 4);
$indexName = Hash::get($matches, 5);

// Check if type is 'references' or 'references?'
$isReference = str_starts_with($type, 'references');
Expand Down Expand Up @@ -250,17 +252,20 @@ public function validArguments(array $arguments): array
*
* @param string $field Name of field
* @param string|null $type User-specified type
* @return array<string|int|array|null> First value is the field type, second value is the field length. If no length
* @return array{0: string|null, 1: int|array<int>|null} First value is the field type, second value is the field length. If no length
* can be extracted, null is returned for the second value
*/
public function getTypeAndLength(string $field, ?string $type): array
{
if ($type && preg_match($this->regexpParseField, $type, $matches)) {
if (str_contains($matches[2], ',')) {
$matches[2] = explode(',', $matches[2]);
$length = $matches[2];
if (str_contains($length, ',')) {
$length = array_map('intval', explode(',', $length));
} else {
$length = (int)$length;
}

return [$matches[1], $matches[2]];
return [$matches[1], $length];
}

/** @var string $fieldType */
Expand Down Expand Up @@ -352,4 +357,61 @@ public function getIndexName(string $field, ?string $indexType, ?string $indexNa

return $indexName;
}

/**
* Parses a default value string into the appropriate PHP type.
*
* Supports:
* - Booleans: true, false
* - Null: null, NULL
* - Integers: 123, -123
* - Floats: 1.5, -1.5
* - Strings: 'hello' (quoted) or unquoted values
*
* @param string|null $value The raw default value from the command line
* @param string $columnType The column type to help with type coercion
* @return string|int|float|bool|null The parsed default value
*/
public function parseDefaultValue(?string $value, string $columnType): string|int|float|bool|null
{
if ($value === null || $value === '') {
return null;
}

$lowerValue = strtolower($value);

// Handle null
if ($lowerValue === 'null') {
return null;
}

// Handle booleans
if ($lowerValue === 'true') {
return true;
}
if ($lowerValue === 'false') {
return false;
}

// Handle quoted strings - strip quotes
if (
(str_starts_with($value, "'") && str_ends_with($value, "'")) ||
(str_starts_with($value, '"') && str_ends_with($value, '"'))
) {
return substr($value, 1, -1);
}

// Handle integers
if (preg_match('/^-?[0-9]+$/', $value)) {
return (int)$value;
}

// Handle floats
if (preg_match('/^-?[0-9]+\.[0-9]+$/', $value)) {
return (float)$value;
}

// Return as-is for SQL expressions like CURRENT_TIMESTAMP
return $value;
}
}
Loading