일대다 관계는 가장 일반적인 관계의 종류로 테이블 A의 한 모델은 테이블 B의 여러 모델과 일치할 수 있지만 테이블 B의 한 모델은 테이블 A의 한 모델과만 일치할 수 있습니다.

예를 들어, Projects 테이블의 한 프로젝트는 Tasks 테이블의 여러 개의 task 를 가질 수 있지만 task 는 하나의 프로젝트에만 속해야 합니다.

 

일대다 관계를 정의하려면 일대일과 마찬가지로 부모가 되는 모델에 자식의 테이블명과 일치하는 메소드를 만들고 hasMany($related, $foreignKey = null, $localKey = null) 를 사용하여 자식 모델을 기술해 주면 되며 필수인 첫 번째 파라미터는 관련된 테이블 모델명입니다.

class Project extends Model
{
    public function tasks()
    {
        return $this->hasMany(Task::class);
    }
}
CODE

만약 참조 키 이름은 다음과 같은 로직에 의해서 결정되며 정확한 참조 키 이름을 알고 싶으면 다음 코드를 artisan tinker 에서 돌려서 알아 낼 수 있습니다. 

>>> snake_case(class_basename('App\ArticleCategory')).'_id';             
=> "article_category_id"
CODE

 참조 키 이름이  관례와 다를 경우 두 번째 파라미터로 명시적인 참조키 이름을 지정해 주면 됩니다.

 

Tasks 테이블은 belongsTo($related, $foreignKey = null, $otherKey = null, $relation = null) 메소드를 사용하여 첫 번째 파라미터로 부모 모델 클래스를 지정해 줍니다.

class Task extends Model
{
    public function project()
    {
        return $this->belongsTo(Project::class);
    }
}
CODE

Eloquent ORM 은 관계가 지정된 컬럼명을 관례에 따라 snake case 명명법으로 참조모델명_id 로 찾으므로 Task 모델의 프로젝트 테이블에 대한 참조키는 project_id 가 되며 만약 레거시 테이블이라 관례에 따라 작명되지 않았다면 hasMany() 나 belongsTo()의 두번째 파라미터에 해당 컬럼명을 지정해 주면 됩니다.

다음은 Tasks 테이블의 Projects 테이블에 대한 참조키 이름이 project_fk_name 일 경우 관계를 정의하는 예제입니다.

class Task extends Model
{
    public function project()
    {
        return $this->belongsTo(Project::class, 'project_fk_name');
    }
}
CODE

이제 테스트를 위해 tinker 를 구동하고 다음과 같이 findOrFail() 메소드를 호출한 후에 tasks 프로퍼티를 호출합니다.

>>> $tasks = App\Project::findOrFail(1)->tasks
>>> get_class($tasks)
=> "Illuminate\Database\Eloquent\Collection"
CODE

실행 결과를 확인해 보면 $tasks 변수의 타입은 "Illuminate\Database\Eloquent\Collection" 이며 project_id 가 1 인 App\Task 객체들을 담고 있는 것을 알 수 있습니다.

여기에서 중요한 점은 tasks() 가 아닌 tasks 로 사용해야 하는 점이며 라라벨은 PHP의 동적 프로퍼티(Dynamic Property) 기능을 사용하여 모델 간의 관계에 따라 객체들을 자동으로 가져오게 됩니다.

 

동적 프로퍼티는 사용이 쉽지만 다양한 조건으로 검색할 수 없으므로 만약 모델을 가져올 때 추가 검색 조건(예: name 컬럼으로 정렬)이 필요할 경우는 동적 프로퍼티가 아닌 메소드 호출을 하고 조건문을 추가 해야 합니다.

tinker 에서 다음과 같이 tasks() 를 호출하고 get_class()로 객체의 타입을 확인해 보면 $tasks 변수는 콜렉션이 아니라  Illuminate\Database\Eloquent\Relations 을 상속받은 HasMany 객체라는 것을 알 수 있습니다.

>>>  $tasks = App\Project::findOrFail($id)->tasks()
>>> get_class($tasks)
=> "Illuminate\Database\Eloquent\Relations\HasMany"
CODE

 

Relations 객체는 내부에 Query Builder 를 내장하고 있으므로 다양한 쿼리 조건을 추가할 수 있으며 예로 project id 를 받아서 하위 task 를 name 항목으로 정렬하는 기능을 만들 경우 tasks() 호출후 orderBy 메소드를 추가 호출해 주면 됩니다.

테스트를 위해 아래의 코드를 Orm 컨트롤러의 getOneToMany() 메소드에 추가해 봅시다.

public function getOneToMany($id)
{
    $tasks = Project::findOrFail($id)->tasks()->orderBy('name')->get();
    dd($tasks);		
}
CODE

 

이제 브라우저로  http://homestead.app/orm/one-to-many/3 에 연결해 보면 일대다 관계이므로 여러 개의 자식 모델을 name 컬럼으로 정렬하여 가져온 것을 알 수 있습니다.

 

만약 Task 의 부모 모델을 찾으려면 다음과 같이 Task 모델의 project() 메소드를 호출하면 됩니다.

>>> App\Task::find(1)->project
=> <App\Project #0000000002558acd00000000675df7f7> {
       id: 1,
       user_id: 1,
       name: "개인",
       is_public: 0,
       created_at: "2015-07-25 00:26:57",
       updated_at: "2015-07-25 00:26:57"
   }
CODE