Home 라라벨로 배우는 php 웹 프로그래밍
Post
Cancel

라라벨로 배우는 php 웹 프로그래밍

라라벨로 배우는 실전 PHP 웹 프로그래밍

PART 1 라라벨 입문

10. 엘로퀀트 ORM

10.1 일대다 관계

사용자(일)는 어러 개의 포럼 글(다)을 쑬 수 있고, 특정 포럼 글은 항상 만든 사람이 있다. 이러한 관계를 데이터베이스 시스템 용어로 일대다 관계라고 표현한다.

마이그레이션 클래스 뼈대 코드 만들기

1
$ php artisan make:migration create_articles_table --create=article

/database/migrations/2014_10_12_000000_create_users_table.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php

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

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

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

database/migrations/2021_01_25_115104_create_articles_table.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
<?php

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

class CreateArticlesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('articles', function (Blueprint $table) {
            $table->increments('id');
            $table->unsignedBigInteger('user_id')->index();
            $table->string('title');
            $table->text('content');
            $table->timestamps();

            $table->foreign('user_id')->references('id')->on('users')
            ->onUpdate('cascade')->onDelete('cascade');
        });
    }

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

  • unsigned() 양의 정수로 열 값을 제한한다. 외개키 열은 양의 정수 제약조건을 서선하는 게 좋다.

  • foreign(string|array $columns) 테이블끼리 외래 키 관계를 연결한다. articles.user_id 열은 users.id 열을 참조한다는 의미다. onUpdate(‘cascade’)와 onDelete(‘cascade’)는 users.id 열이 변경되거나 삭제될 때의 동작 옵션을 정의한 것이다.

    NOTE 테이블 간의 관계를 표현할 때의 관례 테이블이름_열이름_foreign 관계 이름이 생성된다. 예제에서는 article_user_id_foreign란 이름으로 만들어 진다.

troubleshooting

외래키로 관계가 연결되는 키의 쌍은 같은 타입이어야지만 마이그레이션에서 오류가 발생하지 않는다. (예 : string user_id 의 외래키는 같은 string 타입의 키)

마이그레이션은 실행해 본다

마이그레이션 실행

1
$  php artisan migrate

10.1.1 관계 연결

이제 모델끼리 관계를 표현하다. Article 모델을 만들고 관계를 표현하는 users() 메서드를 만들었다(대향 할당을 허용하기 위해 $fillable 프로퍼티 값도 채운다)

Article 모델 뼈대 코드 만들기

1
$ php artisan make:model Article

/app/Models/Article.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    protected $fillable = ['title','content'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    use HasFactory;
}

새로만든 user() 메서드를 소리 나는 대로 읽어보자. this belongsto user class(이것은 user클래스에 속해 있습니다.) this 는 Article이다

  • 읽을수 있는 코드라는 의미를 표현한것 같다

user에서 바라보는 Article도 필요하다.

/app/Models/User.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use HasFactory, Notifiable;

   public function articles()
   {
       return $this->hasMany(Article::class);
   }
}

똑같이 articles() 메서드를 소리 나는 대로 읽어 보자. This hasMany Article class ( 이것은 여러 개의 Article class를 가지고 있습니다.) this는 User이다

10.1.2 관계 확인

이제 팅커 콘솔에서 관계를 이용해 새로운 Article 모델을 만든다

모델 간의 관계를 이용해 새로운 레코드 만들기

1
2
3
4
5
6
7
8
9
10
11
$ php arisan tinker
>>> App\User::find(1)->articles()->create([                                     'title'=>'first title',                                                         'content'=>'first content',                                                     ]);

=> App\Article {#4108
     title: "first title",
     content: "first content",
     user_id: 1,
     updated_at: "2021-02-01 14:43:20",
     created_at: "2021-02-01 14:43:20",
     id: 1,
   }

위 사용법이 ORM 사용법이다/.

App\User::find(1)까지는 1번 기본 키를 가진 User 인스턴스를 방환한다는 것을 이미 알고 있다. 거기에 Articles() 관계를 체인하고, create() 메서드로 새로운 Acticle 모델을 만들었다. user_id 값은 넣지 않았지만, 데이터베이스를 확인해 보면 articles.user_id열에 값이 기록된 것을 확인할 수 있다.

모델 간의 관계를 이용한 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>> App\User::find(1)->articles()->get();
=> App\Article {#4111
     id: 1,
     user_id: 1,
     title: "first title",
     content: "first content",
     created_at: "2021-02-01 14:42:46",
     updated_at: "2021-02-01 14:42:46",
   }
>>> 

>>> App\User::find(1)->articles()->first();
=> App\Article {#4111
     id: 1,
     user_id: 1,
     title: "first title",
     content: "first content",
     created_at: "2021-02-01 14:42:46",
     updated_at: "2021-02-01 14:42:46",
   }
>>> 

이 예제를 통해 두 모델 간의 관계가 어떻게 상호 동작하는지 이해 할수 있다.

10.2 다대다 관계 연결

포럼 글이 한개 이사으이 태그를 가지는 시나리오를 사정해 보자. 그런데 포람 글 1번에 태그 1번을 할당했다고 해서, 포럼 글 2번에 대그 1번을 할당할 수 없는 것은 아니다. 두 모델은 다대다 관계를 현성한다.

데이터베이스 시스템에서 다대다 관계를 표현하기 위해서는 총 세 개의 테이블이 필요하다. 우선 시나리오에는 articles, article_tag, tag 테이블이 필요하다. 이제 마이그레이션알 만들어 본다

다대다 관계 실습을 위한 마이그레이션 클래스 만들기

1
2
3
4
$ php artisan make:migration create_tags_table --create=tags
Created Migration: 2021_02_01_145454_create_tags_table
$ php artisan make:migration create_article_tag_table --create=article_tag
Created Migration: 2021_02_01_145550_create_article_tag_table

myapp/database/migrations/2021_02_01_145454_create_tags_table.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

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

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

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

/myapp/database/migrations/2021_02_01_145550_create_article_tag_table.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php

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

class CreateArticleTagTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('article_tag', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('article_id')->unsigned();
            $table->integer('tag_id')->unsigned();

            $table->foreign('article_id')->references('id')->on('articles')->onDelete('cascade');
            $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
        });
    }

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

article_tag 테이블에 timestamps() 메서드는 넣지 않았다.

10.2.1 관계연결

모델을 만들고 내용을 채워 보자

Tag 모델 뼈대 코드 만들기

1
$ php artisan make:model Tag

app/Tag.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Tag extends Model
{
    protected $fillable =['name','slug'];
    //

    public function articles()
    {
        return $this -> belongsToMany(Article::class);
    }
}

Article 모델과 관계를 맺기 위해서 belongsToMany(string $related, string $table= null, string $foreignKey = null, string $otherKey = null) 메서드를 이용했다. 소리가나는 대로 읽으면 “This belong to many article”(나는 여러개의 article에 속합니다)이다

이번에서는 Articel 모델에 반대 관계를 만들어 보자

app/Article.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    protected $fillable =['title','content'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function tags()
    {
        return $this->belongsToMany(Tag::class);
    }

}

여기서도 같은 belongsToMany() 메서드를 사용한다. 마찬가지로 소리내어 읽으면 “This belong to many tag”(나는 여러 개의 tag에 속합니다.)이다

10.2.2 관계 확인

팅커 콜솔에서 실험할 태그와 포럼 글을 만들자.

다대다 관계 테스트를 위한 데이터 만들기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
>>> App\Tag::create([
... 'name'=>'Foo',
... 'slug'=>'foo',
... ]);

=> App\Tag {#3959
     name: "Foo",
     slug: "foo",
     updated_at: "2021-02-01 15:32:51",
     created_at: "2021-02-01 15:32:51",
     id: 1,
   }
>>> App\Tag::create([                                                           'name'=>'Bar',                                                                  'slug'=>'bar',                                                                  ]);

=> App\Tag {#4108
     name: "Bar",
     slug: "bar",
     updated_at: "2021-02-01 15:33:19",
     created_at: "2021-02-01 15:33:19",
     id: 2,
   }
>>> App\Tag::all();

=> Illuminate\Database\Eloquent\Collection {#3894
     all: [
       App\Tag {#3179
         id: 1,
         name: "Foo",
         slug: "foo",
         created_at: "2021-02-01 15:32:51",
         updated_at: "2021-02-01 15:32:51",
       },
       App\Tag {#3164
         id: 2,
         name: "Bar",
         slug: "bar",
         created_at: "2021-02-01 15:33:19",
         updated_at: "2021-02-01 15:33:19",
       },
     ],
   }
>>> 

>>> App\User::find(1)->articles()->create([
... 'title'=> 'Second title',
... 'content'=>'Second title',
... ]);

=> App\Article {#4103
     title: "Second title",
     content: "Second title",
     user_id: 1,
     updated_at: "2021-02-01 15:36:09",
     created_at: "2021-02-01 15:36:09",
     id: 7,
   }
>>> 


포럼 글에 태그를 부여해 보자

다대다 관계 테스트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
>>> $article = App\Article::find(1);

=> App\Article {#4118
     id: 1,
     user_id: 1,
     title: "first title",
     content: "first content",
     created_at: "2021-02-01 14:42:46",
     updated_at: "2021-02-01 14:42:46",
   }
>>> $article->tags()->sync([1,2]);

=> [
     "attached" => [
       1,
       2,
     ],
     "detached" => [],
     "updated" => [],
   ]

>>> $article->tags->pluck('name','id');

=> Illuminate\Support\Collection {#4105
     all: [
       1 => "Foo",
       2 => "Bar",
     ],
   }

1번 Article 인스턴스를 얻은 후, tags() 관계를 체인했다. 이어서 여기에 sync(\Illuminata\databe\Eloquent\Collectionarray $ids) 메서드를 체인했다. 우리는 1번 포험 글에는 1번과 2번 태스를 연결 했다.

attach()와 detach() 메서드가 있지만, 나는 sync()메서드를 주로 쓴다. 어떤 포럼 글에서 태그 1번과 2번을 이미 할당했는데, attach([2])메서드를 호출하면, 중복 레코드가 생길 수 있다. attach()나 detach()메서드는 호출할 때마다 레코드가 이미 있는지 확인을 해야한다.

중복 문제를 피라기 위해서는 피벗 테이블(articlea_tag) 의 article_id 와 tag_id 열을 동시에 기본 키로 지정하는 방법도 있다. 하지만 중복된 레코드 삽입 요청이 들어오면 데이터베이스 오류가 발생한다. 그러므로 중복 레코드를 피하는 안정함을 얻는 대신 자유도를 버여햐만 한다.

반면, sync() 메서드는 테이블에 저장된 값보다 메서드 인자로 넘어온 값을 항상 우선한다. 레코드를 삭제하고 인장로 받은 값을 다시 입력하기 때문에 쉽게 최신 상태를 유지할 수 있다.

10.2.3 다대다 관계 작동 원리

피벗 테이블에 답이 있다. aritcle_id 열은 article.id 열의 외래 키, tag_id 는 tags.id 열의 외래 키로 동작한다. 피벗 테이블이 두 테이블 간의 관계를 연결해 주는 것이다.

article_tag 피벗 테이블

1
2
3
4
5
6
7
8
mysql> select * from article_tag;
+----+------------+--------+
| id | article_id | tag_id |
+----+------------+--------+
|  1 |          1 |      1 |
|  2 |          1 |      2 |
+----+------------+--------+
2 rows in set (0.00 sec)
This post is licensed under CC BY 4.0 by the author.

라라벨로 배우는 php 웹 프로그래밍

라라벨로 배우는 php 웹 프로그래밍

Comments powered by Disqus.

Trending Tags