ORM 환경에서 일대일 관계 설정 시 hasOne과 belongsTo 중 어떤 메서드를 사용해야 하는지는 전적으로 외래키(Foreign Key)의 물리적 위치에 의해 결정됩니다. 기준 모델 클래스 내에서 관계를 선언할 때, 데이터베이스 스키마의 컬럼 배치를 정확히 파악해야 매핑 오류를 방지할 수 있습니다.
1. 외래키 위치를 통한 관계 구분 기준
관계 정의는 항상 기준이 되는 모델 내부에 작성됩니다. 이때 연결 대상 테이블에 외래키가 있는지, 현재 테이블에 외래키가 있는지에 따라 메서드가 나뉩니다.
hasOne: 외래키가 연결 대상(자식) 테이블에 존재할 때
현재 모델의 기본키를 참조하는 외래키 컬럼이 상대방 테이블에 정의되어 있는 경우 사용합니다. 현재 레코드가 대상 레코드를 소유하는 구조입니다.
// members 테이블 (기준 모델)
// id, member_code, joined_at
// profiles 테이블 (대상 모델)
// id, bio, avatar_url, member_id (외래키)
// App\Models\Member.php
public function profile()
{
// hasOne(대상모델, 대상테이블외래키, 기준모델기본키)
return $this->hasOne(Profile::class, 'member_id', 'id');
}
belongsTo: 외래키가 현재(기준) 테이블에 존재할 때
현재 모델의 테이블에 상대방의 기본키를 참조하는 외래키 컬럼이 들어있는 경우 사용합니다. 현재 레코드가 대상 레코드에 종속되는 구조입니다.
// orders 테이블 (기준 모델)
// id, order_number, customer_id (외래키), total_amount
// customers 테이블 (대상 모델)
// id, full_name, email
// App\Models\Order.php
public function customer()
{
// belongsTo(대상모델, 현재테이블외래키, 대상모델기본키)
return $this->belongsTo(Customer::class, 'customer_id', 'id');
}
2. 관계 호출 패턴 및 컬렉션 처리
정의된 관계를 실제 애플리케이션 레이어에서 활용할 때는 모델 인스턴스를 통해 접근해야 합니다. 관계 메서드가 누락된 모델에서 호출 시 예외가 발생하며, 일대다(hasMany)로 확장할 경우 반환 타입이 컬렉션으로 변경되므로 데이터 추출 방식이 달라집니다.
// 관계 메서드가 정상 정의된 모델 인스턴스에서만 체이닝 가능
$account = Account::find(204);
$securityLevel = $account->setting->auth_level; // hasOne 단일 객체 접근
// belongsTo 역방향 참조
$invoice = Invoice::find(88);
$issuerName = $invoice->branch->branch_name; // 외래키가 invoices 테이블에 위치
// hasMany (일대다) 관계와 조건부 데이터 추출
// 하나의 프로젝트(Project)에 여러 작업자(Worker)가 할당된 구조
$activeWorkerIds = Project::find(7)
->workers()
->where('is_active', true)
->pluck('worker_code');
// Collection을 순수 배열로 재구성
$workerList = [];
foreach ($activeWorkerIds as $code) {
$workerList[] = $code;
}
var_dump($workerList);
외래키가 상대방 테이블에 있으면 hasOne, 자신의 테이블에 있으면 belongsTo를 선언하는 것이 ORM 관계 설계의 기본 규칙이며, 이 원칙은 Laravel Eloquent 및 ThinkPHP 등 주요 PHP ORM에서 동일하게 적용됩니다.