Usando traços para relacionamentos polimórficos no Laravel

Introdução


Vamos falar sobre o possível uso de características junto com as relações polimórficas no Laravel.


O conteúdo do artigo:


  1. Descrição de Domínio
  2. Criação de aplicativo
  3. Estruturas possíveis de banco de dados
  4. Criação de Entidades
  5. Uso de Traços
  6. Testes de escrita

Descrição de Domínio


Vamos desenvolver um sistema no qual alguns funcionários e certas equipes possam ser anexados ao projeto. A essência da área de assunto será funcionários, equipes e projetos: uma equipe é composta por funcionários, funcionários e equipes que podem ser anexados a um projeto. Entre uma equipe e um funcionário, um relacionamento muitos para muitos (digamos que um funcionário possa participar de equipes diferentes), muitos para muitos entre projetos e funcionários, muitos para muitos entre equipes e projetos. Para uma análise mais aprofundada, vamos omitir a implementação da comunicação entre a equipe e os funcionários, focar na atitude de equipes e funcionários em relação ao projeto.


Criação de aplicativo


Os aplicativos Laravel são muito fáceis de construir usando o pacote do criador de aplicativos . Após a instalação, a criação de um novo aplicativo se encaixa em um comando:


laravel new system

Estruturas possíveis de banco de dados


, : -, -, -.


, - - , (, 1 — , 2 — ).


- , — , .



, , , . :


php artisan make:model Employee -f //    
php artisan make:model Team -f //    
php artisan make:model Project -f //    
php artisan make:migration CreateEntitiesTables //     
php artisan make:model Attach -m //    

App/, database/migrations/ database/factories/.


. , : , . — .



<?php

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

class CreateEntitesTables extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('employees', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->timestamps();
        });

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

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

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('employees');
        Schema::dropIfExists('teams');
        Schema::dropIfExists('projects');
    }
}


<?php

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

class CreateAttachesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('attachments', function (Blueprint $table) {
            $table->id();
            $table->morphs('attachable');
            $table->unsignedInteger('project_id');
            $table->timestamps();

            $table->foreign('project_id')->references('id')->on('projects')
                ->onDelete('cascade');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('attachments');
    }
}

, morphs().



:


<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Employee extends Model
{
    protected $fillable = ['name'];
}

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Team extends Model
{
    protected $fillable = ['name'];
}


<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany;

class Project extends Model
{
    protected $fillable = ['name'];

    /**
     * Relation for project attachments
     * @return HasMany
     */
    public function attachments()
    {
        return $this->hasMany(Attach::class);
    }

    /**
     * Relation for project employees
     * @return MorphToMany
     */
    public function employees()
    {
        return $this->morphedByMany(Employee::class, 'attachable', 'attachments');
    }

    /**
     * Relation for project teams
     * @return MorphToMany
     */
    public function teams()
    {
        return $this->morphedByMany(Team::class, 'attachable', 'attachments');
    }    
}


<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Attach extends Model
{
    protected $table = 'attachments';
    protected $fillable = ['attachable_id', 'attachable_type', 'project_id'];
}


<?php

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use Faker\Generator as Faker;

$factory->define(/* (//) */, function (Faker $faker) {
    return [
        'name' => $faker->colorName
    ];
});

, .



Laravel — morphedByMany(), — morphToMany(). , .


app/Traits : Attachable.php


<?php

namespace App\Traits;

use App\Project;
use Illuminate\Database\Eloquent\Relations\MorphToMany;

trait Attachable
{
    /**
     * Relation for entity attachments
     * @return MorphToMany
     */
    public function attachments()
    {
        return $this->morphToMany(Project::class, 'attachable', 'attachments');
    }
}

use.


...
    use Attachable;
...

.



, Laravel PHPUnit . :


php artisan make:test AttachableTest

tests/Feature/. RefreshDatabase.


Verifique a transformação no lado do projeto e a característica por parte da equipe e equipe


<?php

namespace Tests\Feature;

use App\Team;
use App\Employee;
use App\Project;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class OrderTest extends TestCase
{
    use RefreshDatabase;

    /** @test */
    public function polymorphic_relations_scheme(): void
    {
        // Given project
        $project = factory(Project::class)->create();
        // Given team
        $team = factory(Team::class)->create();
        // Given employee
        $employee = factory(Employee::class)->create();

        // When we add team and employee to project
        $project->teams()->save($team);
        $project->employees()->save($employee);

        // Then project should have two attachments
        $this->assertCount(2, $project->attachments);
        $this->assertCount(1, $project->teams);
        $this->assertCount(1, $project->employees);
        $this->assertEquals($team->id, $project->teams->first()->id);
        $this->assertEquals($employee->id, $project->employees->first()->id);
        // Team and employee should have attachment to project
        $this->assertCount(1, $team->attachments);
        $this->assertCount(1, $employee->attachments);
        $this->assertEquals($project->id, $team->attachments->first()->id);
        $this->assertEquals($project->id, $employee->attachments->first()->id);
    }
}

O teste passou!


As características permitem que você não duplique métodos comuns para relacionamentos polimórficos nas classes de modelo; também é possível usá-los se tiver os mesmos campos em muitas tabelas (por exemplo, o autor do registro) - aqui você também pode criar uma característica com o método de conexão.


Ficarei feliz em ouvir seus casos de uso de características no Laravel e PHP.


All Articles