eloquent-best-practices은(는) 무엇을 하나요?
Laravel Eloquent ORM 사용 모범 사례: 쿼리 최적화, 관계 관리, N+1 쿼리와 같은 일반적인 함정 회피 방법을 제공합니다.
참고: 이 문서는 Alexey Mezenin의 원본 laravel-best-practices 저장소에서 가져온 내용을 기반으로 Eloquent ORM 관련 모범 사례를 추출하고 정리한 것입니다.
목차
Eloquent ORM 모범 사례
쿼리 최적화
-
Eager Loading 사용 (N+1 문제 방지):
- 관계 데이터를 필요로 할 때는 항상
with()메서드를 사용하여 N+1 쿼리 문제를 방지하세요. - 예시:
// 나쁜 예 $users = User::all(); foreach ($users as $user) { echo $user->profile->bio; } // 좋은 예 $users = User::with('profile')->get(); foreach ($users as $user) { echo $user->profile->bio; }
- 관계 데이터를 필요로 할 때는 항상
-
withCount사용:- 관계 모델의 개수를 셀 때는
withCount()메서드를 사용하여 추가 쿼리를 피하세요. - 예시:
// 나쁜 예 $posts = Post::all(); foreach ($posts as $post) { echo $post->comments->count(); } // 좋은 예 $posts = Post::withCount('comments')->get(); foreach ($posts as $post) { echo $post->comments_count; }
- 관계 모델의 개수를 셀 때는
-
whereHas사용:- 관계 조건에 따라 필터링할 때는
whereHas()메서드를 사용하세요. - 예시:
// 나쁜 예 $users = User::all()->filter(function ($user) { return $user->posts->contains('status', 'published'); }); // 좋은 예 $users = User::whereHas('posts', function ($query) { $query->where('status', 'published'); })->get();
- 관계 조건에 따라 필터링할 때는
-
select사용:- 필요하지 않은 열을 가져오지 않도록
select()메서드를 사용하세요. - 예시:
// 나쁜 예 $users = User::all(); // 좋은 예 $users = User::select('id', 'name', 'email')->get();
- 필요하지 않은 열을 가져오지 않도록
-
chunk또는cursor사용:- 대량의 데이터를 처리할 때는
chunk()또는cursor()메서드를 사용하여 메모리 사용량을 줄이세요. - 예시:
// 나쁜 예 $users = User::all(); foreach ($users as $user) { // 처리 } // 좋은 예 User::chunk(100, function ($users) { foreach ($users as $user) { // 처리 } }); // 또는 cursor 사용 foreach (User::cursor() as $user) { // 처리 }
- 대량의 데이터를 처리할 때는
관계 관리
-
관계 정의시 명확한 이름 사용:
- 관계 메서드 이름은 모델 이름을 기준으로 명확하게 지정하세요.
- 예시:
// 나쁜 예 public function posts() { ... } public function author() { ... } // 좋은 예 public function publishedPosts() { ... } public function postAuthor() { ... }
-
다형성 관계 적절히 사용:
- 여러 모델에서 공유되는 관계(예: 댓글)는 다형성 관계를 사용하세요.
- 예시:
// Comment 모델 public function commentable() { return $this->morphTo(); } // Post 모델 public function comments() { return $this->morphMany(Comment::class, 'commentable'); }
-
hasManyThrough사용:- 중간 테이블을 통해 간접적인 관계를 정의할 때는
hasManyThrough를 사용하세요. - 예시:
// Country -> User -> Post public function posts() { return $this->hasManyThrough(Post::class, User::class); }
- 중간 테이블을 통해 간접적인 관계를 정의할 때는
-
피벗 테이블 작업:
- 다대다 관계에서 중간 테이블의 추가 열에 접근하려면
withPivot()을 사용하세요. - 예시:
// Role 모델 public function users() { return $this->belongsToMany(User::class)->withPivot('expires_at'); }
- 다대다 관계에서 중간 테이블의 추가 열에 접근하려면
함정 피하기
-
all()남용 금지:all()메서드는 모든 레코드를 가져오므로 대규모 데이터셋에서는 피하세요. 대신paginate()또는limit()을 사용하세요.- 예시:
// 나쁜 예 $users = User::all(); // 좋은 예 $users = User::paginate(20);
-
get()vsfirst():- 하나의 레코드만 필요할 때는
first()를 사용하고, 여러 레코드가 필요할 때는get()을 사용하세요. - 예시:
// 나쁜 예 $user = User::where('email', $email)->get()->first(); // 좋은 예 $user = User::where('email', $email)->first();
- 하나의 레코드만 필요할 때는
-
pluck사용으로 단일 열 값 가져오기:- 특정 열의 값만 필요할 때는
pluck()을 사용하세요. - 예시:
// 나쁜 예 $userEmails = User::all()->pluck('email'); // 좋은 예 $userEmails = User::pluck('email');
- 특정 열의 값만 필요할 때는
-
tap사용 (선택적):- 쿼리 빌더 체이닝에서 중간 값을 처리할 때
tap()을 사용할 수 있습니다. - 예시:
// tap 사용 예 $user = tap(User::where('email', $email)->first(), function ($user) { $user->update(['last_login_at' => now()]); });
- 쿼리 빌더 체이닝에서 중간 값을 처리할 때
-
when사용:- 조건부 쿼리를 작성할 때
when()메서드를 사용하세요. - 예시:
// 나쁜 예 $query = User::query(); if ($request->has('role')) { $query->where('role', $request->role); } $users = $query->get(); // 좋은 예 $users = User::when($request->has('role'), function ($query) use ($request) { $query->where('role', $request->role); })->get();
- 조건부 쿼리를 작성할 때