illuminatech/enum-seeder

允许轻松创建字典(枚举)类型表的数据库种子文件

1.0.3 2024-03-25 11:42 UTC

This package is auto-updated.

Last update: 2024-08-25 12:29:03 UTC


README

Laravel 枚举种子器


此扩展允许轻松创建字典(枚举)类型表的数据库种子文件,例如状态、类型、类别等。

有关许可信息,请查看LICENSE文件。

Latest Stable Version Total Downloads Build Status

安装

安装此扩展的首选方式是通过composer

运行以下命令:

php composer.phar require --prefer-dist illuminatech/enum-seeder

或将其添加到您的composer.json文件中的"require"部分:

"illuminatech/enum-seeder": "*"

to the "require" section of your composer.json.

用法

几乎每个项目都需要指定所谓的“字典”或“枚举”实体,例如状态、类型、类别等。将此类数据作为PHP 枚举基于类的枚举来维护并不总是实用的。有时必须将其放入数据库表中。例如:当我们需要提供系统管理员编辑特定类别或状态的可读标题或描述的能力时,或者启用/禁用特定记录,或者简单地保持数据库完整性。

显然,将字典(枚举)保存在数据库表中会创建一个同步问题。随着我们项目的演变,新的类别和状态可能会出现,一些可能会变得过时。因此,我们需要一个工具,该工具允许更新字典(枚举)表中的数据。此包提供此类工具。

想法是创建一种特殊的数据库种子,它可以以同步特定枚举表与预定义数据的方式被多次调用,而不会创建冗余记录或破坏完整性。您可以扩展Illuminatech\EnumSeeder\EnumSeeder来创建此类种子。例如

<?php

namespace Database\Seeders;

use Illuminatech\EnumSeeder\EnumSeeder;

class ItemCategorySeeder extends EnumSeeder
{
    protected function table(): string
    {
        return 'item_categories';
    }
    
    protected function rows() : array
    {
        return [
            [
                'id' => 1,
                'name' => 'Consumer goods',
                'slug' => 'consumer-goods',
            ],
            [
                'id' => 2,
                'name' => 'Health care',
                'slug' => 'health-care',
            ],
            // ...
        ];
    }
}

// ...

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        // always synchronize all dictionary (enum) tables:
        $this->call(ItemCategorySeeder::class);
        $this->call(ItemStatusSeeder::class);
        $this->call(ContentPageSeeder::class);
        // ...
    }
}

以这种方式定义的种子后,您可以在每次项目更新后调用以下命令:

php artisan migrate --seed

结果,表'item_categories'将始终与ItemCategorySeeder::rows()中的值保持最新。如果您需要添加新的项目类别,只需将另一条记录添加到ItemCategorySeeder::rows()并重新运行种子即可。它将优雅地添加缺少的记录,同时保持已存在的记录不变。

您可以通过重写Illuminatech\EnumSeeder\ControlsWorkflow中的方法来控制种子选项。

注意!请确保不要为字典(枚举)表的主键(id)设置序列(自增),否则EnumSeeder可能无法正确处理其数据同步。

字典(枚举)表的数据库迁移示例

<?php

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

class CreateItemStatusTable extends Migration
{
    public function up()
    {
        Schema::create('item_statuses', function (Blueprint $table) {
            $table->unsignedSmallInteger('id')->primary(); // no sequence (autoincrement)
            $table->string('name');
            // ...
        });
    }
}

提示:请记住,主键字段不一定要始终是整数 - 您也可以使用字符串,这样可以使数据库记录更易于阅读。例如

<?php

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

class CreateItemStatusTable extends Migration
{
    public function up()
    {
        Schema::create('item_statuses', function (Blueprint $table) {
            $table->string('id', 50)->primary();
            $table->string('name');
            // ...
        });
    }
}

处理过时的记录

默认情况下,Illuminatech\EnumSeeder\EnumSeeder 不会删除在 rows() 方法中未指定的记录,因为数据库可能已经通过外键引用了这些记录,删除枚举值可能会造成数据丢失。但是,如果您确定自己的操作,可以通过 shouldDeleteObsolete() 方法来控制删除功能。例如

<?php

use Illuminatech\EnumSeeder\EnumSeeder;

class ContentPageSeeder extends EnumSeeder
{
    protected function table(): string
    {
        return 'content_pages';
    }
    
    protected function rows() : array
    {
        return [
            [
                'id' => 1,
                'name' => 'About Us',
                'slug' => 'about-us',
                'content' => '<div>...</div>',
            ],
            [
                'id' => 2,
                'name' => 'How it works',
                'slug' => 'how-it-works',
                'content' => '<div>...</div>',
            ],
            // ...
        ];
    }
    
    protected function shouldDeleteObsolete(): bool
    {
        return true; // always delete records from 'content_pages', which 'id' is missing at `rows()`
    }
}

每次调用 ContentPageSeeder 时,它将删除 'content_pages' 表中所有缺少 rows() 方法声明中 'id' 的记录。

注意:请记住,您可以在 shouldDeleteObsolete() 中指定复杂的逻辑以满足您的需求。例如,您可以允许在 "local" 环境中删除过时的行,而在 "prod" 中禁止。

<?php

use Illuminatech\EnumSeeder\EnumSeeder;

class ItemStatusSeeder extends EnumSeeder
{
    protected function table(): string
    {
        return 'item_statuses';
    }
    
    protected function rows() : array
    {
        return [
            // ...
        ];
    }
    
    protected function shouldDeleteObsolete(): bool
    {
        return $this->container->environment('local'); // allows deletion of the records only in "local" environment
    }
}

删除过时记录不是唯一的方法。为了保持数据库的完整性,最好简单地标记过时记录为已过时,例如执行“软删除”。这可以通过 shouldUpdateObsoleteWith() 方法实现。例如

<?php

use Illuminatech\EnumSeeder\EnumSeeder;

class ItemStatusSeeder extends EnumSeeder
{
    protected function table(): string
    {
        return 'item_statuses';
    }
    
    protected function rows() : array
    {
        return [
            // ...
        ];
    }
    
    protected function shouldUpdateObsoleteWith(): array
    {
        // following attributes will be applied to the records, which 'id' is missing at `rows()`
        return [
            'deleted_at' => now(),
        ];
    }
}

记录创建的常用数据

您可以在 shouldCreateWith() 方法中简化特定 seeder 的 rows() 方法,以提取常用属性。它定义了每个创建记录的默认属性值,除非它被 rows() 中的条目显式覆盖。例如

<?php

use Illuminatech\EnumSeeder\EnumSeeder;

class ItemCategorySeeder extends EnumSeeder
{
    protected function table(): string
    {
        return 'item_categories';
    }
    
    protected function rows() : array
    {
        return [
            [
                'id' => 1,
                'name' => 'Active Category',
                // no need to specify 'is_active' and 'created_at' all the time
            ],
            [
                'id' => 2,
                'name' => 'Inactive Category',
                'is_active' => false, // overrides the value from `shouldCreateWith()`
            ],
            // ...
        ];
    }
    
    protected function shouldCreateWith(): array
    {
        // applies following attributes per each new created record:
        return [
            'is_active' => true,
            'created_at' => now(),
        ];
    }
}

现有记录的更新

每次枚举 seeder 执行时,如果它们不匹配,它将更新现有记录以使用 rows() 中的实际数据。您可以通过定义 shouldUpdateExisting() 方法来禁用更新。例如

<?php

use Illuminatech\EnumSeeder\EnumSeeder;

class ItemCategorySeeder extends EnumSeeder
{
    protected function table(): string
    {
        return 'item_categories';
    }
    
    protected function rows() : array
    {
        return [
            // ...
        ];
    }
    
    protected function shouldUpdateExisting(): bool
    {
        return false; // disable existing records update
    }
}

然而,您可能需要允许更新某些属性,同时禁止更新其他属性。例如,如果您设置了内容页面,您可能希望通过 seeder 控制特定页面的活动状态,而由管理员编辑的内容字段应保持不变。这可以通过使用 shouldUpdateExistingOnly() 方法实现。例如

<?php

use Illuminatech\EnumSeeder\EnumSeeder;

class ContentPageSeeder extends EnumSeeder
{
    protected function table(): string
    {
        return 'content_pages';
    }
    
    protected function rows() : array
    {
        return [
            [
                'id' => 1,
                'is_active' => true,
                'slug' => 'about-us',
                'title' => 'About Us', // default value, will be applied only on creation
                'content' => '<div>...</div>', // default value, will be applied only on creation
            ],
            // ...
        ];
    }
    
    protected function shouldUpdateExistingOnly(): array
    {
        // only 'is_active' and 'slug' will be synchronized, while 'title' and 'content' remains intact
        return [
            'is_active',
            'slug',
        ];
    }
}

您可以使用 shouldUpdateExistingWith() 指定应在每次行更新中应用的属性。例如

<?php

use Illuminatech\EnumSeeder\EnumSeeder;

class ContentPageSeeder extends EnumSeeder
{
    protected function table(): string
    {
        return 'content_pages';
    }
    
    protected function rows() : array
    {
        return [
            // ...
        ];
    }
    
    protected function shouldUpdateExistingWith(): array
    {
        // attribute values to be applied per each row update.
        return [
            'updated_at' => now(),
        ];
    }
}

请注意!方法 shouldUpdateExistingOnly()shouldUpdateExistingWith() 优先于 shouldUpdateExisting()。如果至少定义了其中之一,则忽略 shouldUpdateExisting() 的返回值,并无论如何更新记录。

Eloquent 枚举 seeder

对于枚举播种,您可能更愿意操作 Eloquent 模型而不是普通表。通过活动记录模型操作数据允许您使用其全部功能,如“事件”、“时间戳”和“软删除”。您可以使用 Illuminatech\EnumSeeder\EloquentEnumSeeder 为特定 Eloquent 模型设置枚举 seeder。例如

<?php

use App\Models\ItemCategory;
use Illuminatech\EnumSeeder\EloquentEnumSeeder;

class ItemCategorySeeder extends EloquentEnumSeeder
{
    protected function model(): string
    {
        return ItemCategory::class;
    }
    
    protected function rows(): array
    {
        return [
            [
                'id' => ItemCategory::CONSUMER_GOODS,
                'name' => 'Consumer goods',
                'slug' => 'consumer-goods',
            ],
            [
                'id' => ItemCategory::HEALTH_CARE,
                'name' => 'Health care',
                'slug' => 'health-care',
            ],
            // ...
        ];
    }
}

请注意!请记住禁用枚举 Eloquent 模型的 Illuminate\Database\Eloquent\Model::$incrementing,否则 EloquentEnumSeeder 可能无法正确处理其数据同步。例如

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class ItemCategory extends Model
{
    /**
     * {@inheritdoc}
     */
    public $incrementing = false; // disable auto-increment

    // ... 
}

请注意!如果您正在使用字符串值作为枚举表的键,您还应该相应地调整 Illuminate\Database\Eloquent\Model::$keyType。例如

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class ItemCategory extends Model
{
    /**
     * {@inheritdoc}
     */
    public $incrementing = false; // disable auto-increment
    
    /**
     * {@inheritdoc}
     */
    protected $keyType = 'string';  // setup 'string' type for primary key

    // ... 
}