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
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
private const array INDEXES = [
'cfv_field_integer_index' => 'value_integer',
'cfv_field_decimal_index' => 'value_decimal',
'cfv_field_date_index' => 'value_date',
'cfv_field_datetime_index' => 'value_datetime',
];

public function up(): void
{
$tableName = config('laravel-custom-fields.tables.values');

foreach (self::INDEXES as $name => $column) {
if (Schema::hasIndex($tableName, $name)) {
continue;
}

Schema::table($tableName, function (Blueprint $table) use ($name, $column): void {
$table->index(['custom_field_id', $column], $name);
});
}
}
};
11 changes: 7 additions & 4 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,13 @@ php artisan vendor:publish --tag="laravel-custom-fields-migrations"
php artisan migrate
```

Two migrations are published, `create_custom_fields_table` and
`create_custom_field_values_table`. They are published one file at a time and receive a
fresh timestamp, so they run after the migrations already in your application. Publish
them after the tables of the models that will own custom fields exist.
Three migrations are published, `create_custom_fields_table`,
`create_custom_field_values_table` and `add_typed_indexes_to_custom_field_values_table`.
The third one is for installs that ran the first two before 0.7.0 and does nothing on a
fresh install, where the values table is created with every index. They are published one
file at a time and receive a fresh timestamp, so they run after the migrations already in
your application. Publish them after the tables of the models that will own custom fields
exist.

> [!IMPORTANT]
> Set `key_type` and `morph_key_type` in the published configuration **before** you run
Expand Down
37 changes: 12 additions & 25 deletions docs/upgrading.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,33 +96,20 @@ fields run as a subquery on the field and the typed column, and no longer scan e
of the field.

An application that ran the migration before 0.7.0 does not get them, because a migration
that already ran is not run again. Create a migration of your own with
`php artisan make:migration add_typed_value_indexes` and paste this in. It reads the same
table name the package reads, so it follows a changed `tables.values` configuration.

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::table(config('laravel-custom-fields.tables.values'), function (Blueprint $table): void {
$table->index(['custom_field_id', 'value_integer'], 'cfv_field_integer_index');
$table->index(['custom_field_id', 'value_decimal'], 'cfv_field_decimal_index');
$table->index(['custom_field_id', 'value_date'], 'cfv_field_date_index');
$table->index(['custom_field_id', 'value_datetime'], 'cfv_field_datetime_index');
});
}
};
that already ran is not run again. A third migration,
`add_typed_indexes_to_custom_field_values_table`, adds them. Publish it and run it:

```bash
php artisan vendor:publish --tag="laravel-custom-fields-migrations"
php artisan migrate
```

A new install needs nothing. If you already added an index on one of these columns
yourself, drop it first or remove its line from the migration above.
Publishing copies only the file your application does not have yet, so the two migrations
you already published are left alone. The migration reads the values table name from the
`tables.values` configuration and creates each index only when it is missing. An index you
added by hand under the same name is skipped, and an index you added under a different name
is left in place, so the column then carries both. A new install needs nothing, the first
migration already creates all six indexes and the third one does nothing.

## Data notes

Expand Down
1 change: 1 addition & 0 deletions src/CustomFieldsServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public function configurePackage(Package $package): void
->hasMigrations([
'2026_01_01_000000_create_custom_fields_table',
'2026_01_01_000001_create_custom_field_values_table',
'2026_01_01_000002_add_typed_indexes_to_custom_field_values_table',
]);
}

Expand Down
88 changes: 88 additions & 0 deletions tests/Feature/IndexMigrationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

declare(strict_types=1);

use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider;
use PlinCode\CustomFields\CustomFieldsServiceProvider;

function typedIndexMigration(): Migration
{
$migration = require dirname(__DIR__, 2).'/database/migrations/2026_01_01_000002_add_typed_indexes_to_custom_field_values_table.php';

expect($migration)->toBeInstanceOf(Migration::class);

return $migration;
}

/** @return array<int, string> */
function valueIndexNames(): array
{
$names = array_map(
static fn (array $index): string => strtolower($index['name']),
Schema::getIndexes(config('laravel-custom-fields.tables.values')),
);

sort($names);

return $names;
}

/** @return array<int, string> */
function typedIndexNames(): array
{
return [
'cfv_field_date_index',
'cfv_field_datetime_index',
'cfv_field_decimal_index',
'cfv_field_integer_index',
];
}

it('publishes the index migration under the migrations tag', function (): void {
$published = array_map(
'basename',
array_keys(ServiceProvider::pathsToPublish(CustomFieldsServiceProvider::class, 'laravel-custom-fields-migrations')),
);

expect($published)->toHaveCount(3)
->and($published[2])->toBe('2026_01_01_000002_add_typed_indexes_to_custom_field_values_table.php');
});

it('leaves the six indexes of a fresh install untouched', function (): void {
typedIndexMigration()->up();

expect(array_values(array_filter(
valueIndexNames(),
static fn (string $name): bool => str_starts_with($name, 'cfv_field_') && ! str_ends_with($name, '_unique'),
)))->toBe([
'cfv_field_boolean_index',
'cfv_field_date_index',
'cfv_field_datetime_index',
'cfv_field_decimal_index',
'cfv_field_integer_index',
'cfv_field_string_index',
]);
});

it('recreates the typed indexes an older install lacks', function (): void {
Schema::table(config('laravel-custom-fields.tables.values'), function ($table): void {
foreach (typedIndexNames() as $name) {
$table->dropIndex($name);
}
});

expect(valueIndexNames())->not->toContain(...typedIndexNames());

typedIndexMigration()->up();

expect(valueIndexNames())->toContain(...typedIndexNames());
});

it('can run twice without failing', function (): void {
typedIndexMigration()->up();
typedIndexMigration()->up();

expect(valueIndexNames())->toContain(...typedIndexNames());
});
5 changes: 4 additions & 1 deletion tests/Feature/ServiceProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,11 @@ function publishedTargets(string $tag): array
->each->toEndWith('lang/vendor/laravel-custom-fields');
});

it('publishes both migrations under the migrations tag', function (): void {
it('publishes the three migrations under the migrations tag', function (): void {
expect(publishedPaths('laravel-custom-fields-migrations'))->toBe([
'database/migrations/2026_01_01_000000_create_custom_fields_table.php',
'database/migrations/2026_01_01_000001_create_custom_field_values_table.php',
'database/migrations/2026_01_01_000002_add_typed_indexes_to_custom_field_values_table.php',
]);
});

Expand All @@ -92,6 +93,7 @@ function publishedTargets(string $tag): array
'lang',
'database/migrations/2026_01_01_000000_create_custom_fields_table.php',
'database/migrations/2026_01_01_000001_create_custom_field_values_table.php',
'database/migrations/2026_01_01_000002_add_typed_indexes_to_custom_field_values_table.php',
]);
});

Expand All @@ -101,6 +103,7 @@ function publishedTargets(string $tag): array
expect($files)->toBe([
'2026_01_01_000000_create_custom_fields_table.php',
'2026_01_01_000001_create_custom_field_values_table.php',
'2026_01_01_000002_add_typed_indexes_to_custom_field_values_table.php',
]);
});

Expand Down
4 changes: 3 additions & 1 deletion tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,9 @@ protected function rebuildPackageTables(): void
}

foreach (array_reverse($migrations) as $migration) {
$migration->down();
if (method_exists($migration, 'down')) {
$migration->down();
}
}

foreach ($migrations as $migration) {
Expand Down
Loading