평범한 이야기들

[TIL] PHP Laravel - Database #5 엘로퀀트(Eloquent) - 2 본문

평범한 개발 이야기/Laravel

[TIL] PHP Laravel - Database #5 엘로퀀트(Eloquent) - 2

songsariya 2021. 2. 16. 18:33
728x90

접근자, 변경자, 속성 값 형 변환을 사용한 커스텀 필드 사용

접근자 ( Getter )

// 모델에서 접근자 정의
class Contact extends Model
{
    // 기본
    public function getNameAttribute($value)
    {
        return $value ?: '(no name provided)';
    }

    // 테이블에 존재하지 않은 값에 접근하는 속성값을 접근자를 이용해 정의
    public function getFullNameAttribute()
    {
        return $this->first_name . ' ' . $this->last_name;
    }
}

// 정의한 접근자 사용
$name = $contact->name;
$name = $contact->full_name;

변경자 ( Setter )

// 모델에서 변경자 정의
class Order extends Model
{
    // 기본
    public function setAmountAttribute($value)
    {
        $this->attributes['amount'] = $value > 0 ? $value : 0;
    }

    //테이블에 존재하지 않은 값을
    public function setWorkgroupNameAttribute($workgroupName)
    {
        $this->attributes['email'] = "{$workgroupName}@company.com";
    }
}

// 정의한 접근자 사용
$order->amount = 15;
$order->workgroup_name = 'SR_';

속성 값 형 변환

class Contact
{
    protected $casts = [
        'vip' => 'boolean',
        'children_names' => 'array',
        'birthday' => 'date',
    ];
}

커스텀 형 변환 (라라벨7 이상)

CastsAttributes 인터페이스를 구현한 형 변환을 정의하는 클래스 작성


<?php

namespace App\Models\Casts;

use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class Json implements CastAttributes
{
    // 주어진 값 형변환
    public function get($model, $key, $value, $attributes)
    {
        return json_decode($value, $key);
    }

    // 주어진 값을 저장하기 위해 준비
    public function set($model, $key, $value, $attributes)
    {
        return json_encode($value, $key);
    }
}

// 클래스를 생성.
class Contact
{
    protected $casts = [
        'options' => Json:class
    ];
}

날짜 변경자

// 해당 칼럼이 타임스탬프 칼럼으로 작동
class Contact
{
    protected $dates= [
        'met_at',
    ];
}

// $dates에는 기본적으로 'created_at','updated_at' 포함되어있다.

쿼리 결과 형 변환

$users = User::select(['id','name','vip'])->withCasts([
    'vip' => 'bool'
])->get();
// vip 속성값은 불리언 값이 된다.

 

엘로퀀트 연관관계

1:1, 1:N, N:N 관계가 존재

일대일 연관관계 ( hasOne() , belongsTo() )

// 하나의 Contact는 하나의 PhoneNumber 모델을 가지고 있다. -> hasOne
class Contact extends Model
{
    public function phoneNumber(){
        return $this->**hasOne**(PhoneNumber::class);

        // 관례에 따르면 contact_id 라는 키로 연결을 시킨다고 생각한다.
        // 칼럼명이 다르다면 2번째 인자를 사용
        return $this->hasOne(PhoneNumber::class, 'owner_id');
    }
}

// 사용방법
$contact = Contact::first();
$contactPhone = $contact->phoneNumber();

일대일 역방향 연관관계

// PhoneNumber 모델에서 Contact 모델에 접근하기 위해
class PhoneNumber extends Model
{
    public function contact(){
        return $this->belongTo(Contact::class);
    }
}

// 사용방법
$contact = $phoneNumber->contact;

 

일대다 연관관계 ( hasMany() ,belongsTo() )

// User가 여러 Contact를 가지는 관계
class User extends Model
{
    public function contacts(){
        return $this->hasMany(Contact::class);
    }
}

// 사용방법
$user = User::first();
$usersContacts = $user->contacts; // 컬렉션 반환

// 컬렉션으로 반환하기 때문에 컬렉션 메서드 가능
$donors = $user->contacts->filter(function($contact){
    return $contact->status == 'donor';
});

$lifetimeValue = $contact->orders->reduce(function($carry, $order){
    return $carry + $order->amount;
}, 0);

일대다 역방형 연관관계

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

// 사용방법
$userName = $contact->user->name;

// 연관관계 쿼리 빌더로 사용
$donors = $user->contacts()->where('status', 'donor')->get();

// 연관관계 존재하는 레코드만 조회
$postsWithComments = Post::has('comments')->get();

$postsWithManyComments = Post::has('comments', '>=', 5)->get(); // 기준조건 추가

$usersWithPhoneBooks = User::has('contacts.phoneNumbers')->get(); // 중첩된 연관관계를 조건으로 사용

//전화번호 문자열에 867-5309'가 포함된 전화번호를 가진 모든 연락처 조회
$jennyIGotYourNumber = Contact::whereHas('phoneNumbers',function($query){
    $query->where('number', 'like', '%867-5309%');
});

 

연결을 통한 다수 연관관계

ex) 하나의 사용자(User) 모델 이 다수의 연락처(Contact)를 가지고 있고 Contact는 여러 개의 PhoneNumber를 가지고 있다고 가정

hasManyThrought() hasOneThrought() 메서드를 이용

class User extends Model
{
    public function phoneNumbers()
    {
        return $this->hasManyThrought(PhoneNumber::class, Contact::class);
    }

// 사용방법
$user->phone_numbers // 연관관계로 접근 가능

 

다대다 연관관계 (belongsToMany() )

class User extends Model
{
    public function contacts()
    {
        return $this->belongsToMany(Contact::class);
    }
}

class Contact extends Model
{
    public function contacts()
    {
        return $this->belongsToMany(User::class);
    }
}

다대다 관계에서는 피벗 테이블이 필요함

라라벨 관례에 의해 알파벳 순서대로 정렬해 위와 같은 경우 contaact_user 테이블이 필요함 (contact_id, user_id 칼럼 필요로 함)

피벗 테이블명을 변경하려면 belongsToMany() 메서드의 두 번째 인자에 테이블명을 넘겨줘야 함

// 사용법.
$user = User::fisrt();
$user->contacts->each(function ($contact) {
    // 작업 실행
});

$contact = Contact::first();
$contact->users->each(function ($user) {
    // 코드 실행
});

피벗 테이블에서 데이터 조회

public function contacts()
{
    return $this->belongsToMany(Contact::class)
                            ->withTimestamps(); // created_at, updated_at 칼럼 여부를 알려줌
                            ->withPivot('status', 'preferred_greeting'); // withPivot() 메서드로 특정 필드를 정의
}

// 사용예
$user = User::first();

$user->contacts->each(function($contact){
    echo sprintf('연락처가 이 사용자와 연결된 시각 : %s', $contact->pivot->created_at);  
});

// pivot 속성을 가진다.
// as() 메서드를 이용하면 pivot 속성 이름을 변경할 수 있다.
return $this->belongsToMany(Contact::class)
                            ->withTimestamps(); // created_at, updated_at 칼럼 여부를 알려줌
                            ->as('membership');

User::first()->groups->each(function($contact){
    echo sprintf('연락처가 이 사용자와 연결된 시각 : %s', $contact->membership->created_at);  
});

 

다형성 연관관계

사용자(User)가 연락처(Contact)와 이벤트(Event)를 즐겨찾기 (Star) 할 수 있는 시스템으로 예를 듬

stars 에는 id 칼럼과 starrabled_id, starrable_type 칼럼이 추가되어야 함

class Star extends Model
{
    public function starrable()
    {
        return $this->morphTo();
    }
}

class Contact extends Model
{
    public function stars()
    {
        return $this->morphMany(Star::class, 'starrable');
    }
}

class Event extends Model
{
    public function stars()
    {
        return $this->morphMany(Star::class, 'starrable');
    }
}

// Star 모델 데이터 생성
$contact = Contact::first();
$contact->stars()->create();

// 연락처에서 즐겨찾기를 모두 찾기
$contact = Contact::first();
$contact->stars->each(function($star){
    // 필요한 코드
});

특정 연락처를 즐겨찾기 한 사람을 조회하는 경우

  1. stars 테이블에 user_id 칼럼 추가
  2. User 모델과 Star 모델을 일대 다 연관관계로 연결
  3. stars 테이블은 User, Contact, Event 사이를 나타내는 피벗 테이블로 됨
class Star extends Model
{
    public function starrable()
    {
        return $this->morphTo();
    }

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

class User extends Model
{
    public function stars()
    {
        return $this->hasMany(Star::class);
    }
}
$user = User::first();
$event = Event::first();
$event->start()->create(['user_id' => $user->id]);

다대다 다형성 연관관계

Contact와 Event에 각각 태그를 달 수 있는 상황

  • contacts 테이블, events 테이블, tags 테이블 필요
  • tag_id, taggable_id, taggalbe_type 칼럼을 가지고 있는 taggable 테이블 필요함.
// 클래스 정의
class Contact extends Model
{
    public function tags()
    {
        return $this->morphToMany(tag::class, 'taggable');
    }
}

class Event extends Model
{
    public function tags()
    {
        return $this->morphToMany(tag::class, 'taggable');
    }
}

class Tag extends Model
{
    public function contacts()
    {
        return $this->morphedByMany(Contact::class, 'taggable');
    }
    public function events()
    {
        return $this->morphedByMany(Contact::class, 'taggable');
    }
}

//태그 생성 후 연락처 연결
$tag = Tag::firstOrCreate('name'=>'likes-cheese']);
$contact = Contact::first();
$contact->tags()->attach($tag->id);

// 연락처에서 태그 목록 조회
$contact = Contact::fisrt();

$contact->tags->each(function($tag){
    // 작업
});

// 태그에서 연락처 목록 조회
$tag = Tag::first();
$tag->contacts->each(function ($contact){
    // 작업
});

 

하위 모델에서 상위 모델의 타임스탬프 값 갱신

belongsTo belongsToMany 연관관계로 연결된 경우 모델이 변경될 대마다 연관된 모델도 변경됐다고 작업을 해줄 수 있음

class PhoneNumber extends Model
{
    protected $touches = ['contact']; // 속성 추가

    public function contacts() {
        return $this->belongsTo(Contact::class);
    }
}

 

lazy로딩과 eager 로딩

엘로퀀트는 연관관계 모델을 지연로딩(lazy loading) 기법을 사용해 가져옴

→ 모델의 인스턴스를 가져왔을 때 연관관계에 있는 모델을 함께 불러오지 않음

→ 모델에서 연관관계 모델에 접근할 때 로딩이 이루어짐

// $contact 에서 phone_numbers를 통해 계속 쿼리를 호출하게 된다.
$contacts = Contact::all();

foreach($contacts as $contact) {
    foreach($contact->phone_numbers as $phone_number) {
        echo $phone_number->number;
    }
}

이런 단점을 해결하기 위해 eager 로딩 (연관관계 모델을 함께 사용하고 있다는 걸 미리 알면 적용)

$contacts = Contact::with('phoneNumbers')->get();

모델을 조회할 때 with() 메서드를 사용하면 연관된 모델 데이터를 함께 가져옴

eager 로딩 제약 추가

$contacts = Contact::with(['phoneNumbers' => function($query){
    $query->where('milable',true); // 조건 추가
}]);

지연 eager 로딩

$contacts = Contact::all();

if($showPhoneNumbers) {
    $contacts->load('phoneNumber');
}

// 연관관계를 조회하지 않았을 때에만 eager 로딩을 해오기를 원한다며 loadMissing() 메서드 사용
$contacts = Contact::all();

if($showPhoneNumbers) {
    $contacts->loadMissing('phoneNumber');
}

 

엘로퀀트 이벤트

엘로퀀트 모델은 특정 작업이 발생할 때마다 애플리케이션 내부의 이벤트를 발생

자세한 이벤튼 내용은 ( 라라벨문서 )

728x90
Comments