Кастомный конструктор таблиц

Объектно-ориентированное программирование (ООП) является одним из столпов любого языка программирования и одним из самых больших его преимуществ является наследование, а мы можем это использовать для реализации кастомного Blueprint.

Для начала давайте разберем, что такое Blueprint?

Класс Blueprint

В реальных задачах мы часто используем миграции. Когда мы создаем какую-либо миграцию, то в папке миграций автоматически создается файл, имеющий два метода: up и down. Они содержат логику в соответствии с требованиями конкретной миграции.

Чтобы определить новую таблицу и добавить в неё столбцы, мы можем использовать экземпляр Blueprint. Вот как он используется в миграции:

public function up()
{
    Schema::create('roles', function (Blueprint $table) {
        $table->bigIncrements('id');
        $table->string('name');
        $table->timestamps();
    });
}

Проблема

Давайте рассмотрим пример системы управления сотрудниками. У каждого сотрудника (системный администратор, менеджер по персоналу или менеджер проекта) будут разные доступы к разным модулям в системе. Смотрите на рисунки ниже.

Employee_Management_System

Employee_Management_System

Как правило, сотрудник отдела кадров обрабатывает заявления на отпуск, а проект-менеджер отвечает за выставление счетов в рамках проекта. Теперь, чтобы отслеживать изменения, у нас должны быть в каждой модели следующие поля: created_at, created_by, updated_at, updated_by, deleted_at и deleted_by.

Проблема 1. Метод Timestamps() предоставляет поля created_at и updated_at, а метод SoftDeletes() предоставляет поле deleted_at. Нам нужно еще в каждую модель вручную добавить поля created_by, updated_by и deleted_by.

Проблема 2. В случае неблагоприятных обстоятельствах, когда нужно изменить название какого-либо поля, то это будет утомительной задачей, ведь придется сделать это в каждой модели.

Решение

Чтобы решить обе проблемы, мы должны переместить все эти поля в одно место. Это делается путем создания нового класса CustomBlueprint.

namespace App\common;
use Illuminate\Database\Schema\Blueprint;

class CustomBlueprint extends Blueprint
{
    public function commonFields()
    {
        $this->timestamp('created_at')->nullable();;
        $this->unsignedBigInteger('created_by')->nullable();
        $this->timestamp('updated_at')->nullable();;
        $this->unsignedBigInteger('updated_by')->nullable();
        $this->timestamp('deleted_at')->nullable();;
        $this->unsignedBigInteger('deleted_by')->nullable();
        $this->boolean('is_deleted')->default(0);
    }
}

Так как мы расширяем дефолтный класс Blueprint, то по умолчанию мы можем использовать все его поля и методы, такие как string, char, decimal, timestamps() и т.д. Теперь, наш первый шаг — создать метод commonFields(), и объявить все общие поля, необходимые для моделей, в нем. Настало время изменить файл миграции.

public function up()
{
    $schema = DB::connection()->getSchemaBuilder();
}

Нам нужен экземпляр Schema Builder для соединения, и это делается с помощью метода getSchemaBuilder() в методе up.

$schema->blueprintResolver(function ($table, $callback) {
   return new CustomBlueprint($table, $callback);
});

BlueprintResolver используется для установки преобразователя. Возвращая наш новый объект CustomBlueprint, мы можем изменить дефолтное поведение объекта Blueprint.

$schema->create('roles', function (CustomBlueprint $table) {
    $table->bigIncrements('id');
    $table->string('name')->unique();
    $table->commonFields();
});

Теперь мы можем использовать объект класса CustomBlueprint для добавления столбцов вместо дефолтного класса Blueprint. Благодаря методу commonFields() модель «role» получит created_at, updated_by и все другие поля, определенные в нём. После запуска этой мы сможем увидеть следующую структуру.

Database Model

Окончательный файл миграции выглядит так:

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use App\Common\CustomBlueprint;

class CreateRolesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        $schema = DB::connection()->getSchemaBuilder();
        $schema->blueprintResolver(function ($table, $callback) {
            return new CustomBlueprint($table, $callback);
        });
        $schema->create('roles', function (CustomBlueprint $table) {
            $table->bigIncrements('id');
            $table->string('name')->unique();
            $table->commonFields();
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('roles');
    }
}

Если мы будем следовать этой процедуре для каждой модели, то эти поля автоматически добавятся и в них.

Автор: Harsh Shah
Перевод: Demiurge Ash