Tìm kiếm bài viết

Laravel:Calling Eloquent from Blade: 6 Tips for Performance

12.10.2020

5.0/5 (1 Reviews)

Một trong những vấn đề phổ biến nhất về hiệu suất mà tôi đã thấy trong Laravel là sử dụng các phương thức (method) và quan hệ (relationship) Eloquent từ Blade

    Một trong những vấn đề phổ biến nhất về hiệu suất mà tôi đã thấy trong Laravel là sử dụng các phương thức (method) và quan hệ (relationship) Eloquent từ Blade, tạo ra các vòng lặp và truy vấn bổ sung không cần thiết. Trong bài viết này, tôi sẽ chỉ ra các kịch bản khác nhau và cách xử lý chúng hiệu quả.

    Scenario 1: Loading belongsTo() Relationship: don’t forget Eager Loading

    Có lẽ, trường hợp điển hình nhất - bạn looping với @foreach thông qua các records và trong một số cột bạn cần hiển thị bản ghi gốc của nó với một số trường.

    @foreach ($sessions as $session)
    <tr>
      <td>{{ $session->created_at }}</td>
      <td>{{ $session->user->name }}</td>
    </tr>
    @endforeach
    

    Và, dĩ nhiên, session phụ thuộc (belongs to) vào user, trong app/Session.php :

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

    Bây giờ, Code có thể trông vô hại và chính xác, nhưng tùy thuộc vào controller code, chúng ta có thể có một vấn đề hiệu năng rất lớn ở đây.

    Cách sai trong Controller:

    public function index()
    {
        $sessions = Session::all();
        return view('sessions.index', compact('sessions');
    }
    

    Cách đúng :

    public function index()
    {
        $sessions = Session::with('user')->get();
        return view('sessions.index', compact('sessions');
    }
    

    Để ý sự khác biệt? Chúng ta đang loading relationship với truy vấn main Eloquent, nó được gọi là

    Nếu chúng ta không làm điều đó, vòng lặp foreach trong Blade của chúng ta sẽ gọi một truy vấn SQL cho mỗi session, yêu cầu user của nó trực tiếp từ cơ sở dữ liệu trong mỗi vòng lặp. Vì vậy, nếu bạn có một bảng có 100 session, thì bạn sẽ có 101 truy vấn - 1 cho session list và 100 cho user related.

    Vì vậy, đừng quên sử dụng Eager Loading.

     

    Scenario 2: Loading hasMany() Relationship

     

    Một kịch bản điển hình khác là bạn cần liệt kê tất cả các mục con trong vòng lặp của các bản ghi cha.

    @foreach ($posts as $post)
    <tr>
      <td>{{ $post->title }}</td>
      <td>
        @foreach ($post->tags as $tag)
          <span class="tag">{{ $tag->name }}</span>
        @endforeach
      </td>
    </tr>
    @endforeach
    

    Đoán xem - điều tương tự áp dụng ở đây. Nếu bạn không sử dụng Eager Loading, thì mỗi post sẽ có một request đến cơ sở dữ liệu.

    Vì vậy, trong Controller của bạn, bạn nên làm điều này:

    public function index()
    {
        $posts = Post::with('tags')->get(); // not just Post::all()!
        return view('posts.index', compact('posts'));
    }
    

    Scenario 3. NOT Using Brackets in hasMany() Relationship

     

    Hãy để tưởng tượng bạn có một Poll với votes và muốn hiển thị tất cả các polls với votes họ có.

    Tất nhiên, bạn đang thực hiện Eager Loading trong Controller:

    public function index()
    {
        $polls = Poll::with('votes')->get();
        return view('polls', compact('polls'));
    }
    

    Và sau đó trong Blade file bạn đang hiển thị nó như sau:

    @foreach ($polls as $poll)
        <b>{{ $poll->question }}</b> 
        ({{ $poll->votes()->count() }})
        <br />
    @endforeach
    

    Có vẻ ổn, phải không? Nhưng để ý ->votes(), với dấu ngoặc. Nếu bạn để nó như thế này, thì VẪN sẽ có một query cho mỗi poll. Bởi vì nó không nhận được dữ liệu relationship được tải, thay vào đó, nó gọi method từ Eloquent một lần nữa.

    Vì vậy hãy thực hiện việc này: {{$ poll-> phiếu-> Count ()}}. Không có dấu ngoặc.

    Và, nhân tiện, cùng áp dụng cho belongsTo relationship. Không sử dụng dấu ngoặc trong khi load các relationships trong Blade.

    Offtopic: trong khi lướt StackOverflow, tôi đã thấy các ví dụ thực sự thậm chí còn tồi tệ hơn về nó. Giống như: {{ $poll->votes()->get()->count() }} hoặc @foreach ($poll->votes()->get() as $vote) …. Hãy thử điều đó với và xem số lượng SQL query.

    Scenario 4. What if Relationship May Be Empty?

     

    Một trong những lỗi phổ biến nhất trong Laravel là “trying to get property of non-object”, bạn đã thấy nó trước đây trong các dự án của mình chưa? (thôi nào, đừng nói dối)

    Thông thường nó đến từ một cái gì đó như thế này:

    <td>{{ $payment->user->name }}</td>
    

    Không có gì đảm bảo rằng user của payment đó tồn tại. Có lẽ nó đã bị soft-deleted? Có thể thiếu foreign key trong cơ sở dữ liệu, cho phép ai đó xóa người dùng vĩnh viễn?

    Bây giờ, giải pháp phụ thuộc vào phiên bản Laravel/PHP. Trước Laravel 5.7, cú pháp điển hình hiển thị default value là:

    {{ $payment->user->name or 'Anonymous' }}
    

    Từ Laravel 5.7, theo PHP operator thông thường đã được giới thiệu trong PHP 7:

    {{ $payment->user->name ?? 'Anonymous' }}
    

    Nhưng bạn có biết bạn cũng có thể gán giá trị mặc định ở cấp độ Eloquent không?

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

    Phương thức withDefault() này sẽ trả về empty model của class User, nếu relationship không tồn tại.

    Không chỉ vậy, bạn cũng có thể fill vào default model đó bằng các giá trị!

    public function user()
    {
        return $this->belongsTo(User::class)
          ->withDefault(['name' => 'Anonymous']);
    }
    

    Scenario 5. Avoiding Where Statements in Blade with Extra Relationships

     

    Đã bao giờ bạn thấy code trong Blade như sau:

    @foreach ($posts as $post)
        @foreach ($post->comments->where('approved', 1) as $comment)
            {{ $comment->comment_text }}
        @endforeach
    @endforeach
    

    Vì vậy, bạn có thể filter các comment (eager loading, dĩ nhiên, phải không?) Với một điều kiện khác where(‘approved’, 1)

    Nó hoạt động và nó không gây ra bất kỳ vấn đề nào về hiệu năng, nhưng sở thích cá nhân của tôi (và cả nguyên tắc MVC) nói rằng logic phải nằm ngoài View, ở đâu đó, tốt, "logic" layer. Đó có thể là Eloquent model, nơi bạn có thể chỉ định separate relatopnship cho các approved comments trong app/Post.php.

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
    
    public function approved_comments()
    {
        return $this->hasMany(Comment::class)->where('approved', 1);
    }
    

    Và sau đó bạn load relationship đặc biệt này trong Controller/Blade

    $posts = Post::with(‘approved_comments’)->get();
    

    Scenario 6. Avoiding Very Complex Conditions with Accessors

     

    Gần đây trong một dự án tôi có một task: liệt kê các jobs, với envelope icon cho các mesage và với price cho job nên được lấy LAST message có chứa price đó. Nghe có vẻ phức tạp. Nhưng này, đời thực cũng khá phức tạp!

    Trong code đầu tiên tôi đã viết một cái gì đó như thế này:

    @foreach ($jobs as $job)
        ...
        @if ($job->messages->where('price is not null')->count())
            {{ $job->messages->where('price is not null')->sortByDesc('id')->first()->price }}
        @endif
    @endforeach
    

    Oh, kinh hoàng. Tất nhiên, bạn cần kiểm tra xem price có tồn tại hay không, sau đó lấy last message với price đó, nhưng, nó không nên có trong Blade.

    Vì vậy, tôi đã kết thúc bằng cách sử dụng trong Eloquent và định nghĩa nó trong app/Job.php:

    public function getPriceAttribute()
    {
        $price = $this->messages
            ->where('price is not null')
            ->sortByDesc('id')
            ->first();
        if (!$price) return 0;
    
        return $price->price;
    }
    

    Dĩ nhiên, với những tình huống phức tạp như vậy, nó cũng dễ dàng dẫn tới vấn đề query N + 1 hoặc chỉ tình cờ khởi động các query nhiều lần. Vì vậy, vui lòng sử dụng Laravel Debugbar để tìm lỗ hổng.

    Ngoài ra, tôi có thể đề xuất một package có tên là .

    Bonus. Tôi muốn để lại cho bạn ví dụ tồi tệ nhất về code mà tôi đã thấy trên Laracasts, trong khi nghiên cứu chủ đề này. Ai đó muốn lời khuyên cho code dưới đây. Thật không may, code như thế này được nhìn thấy trong các dự án live quá thường xuyên. Bởi vì, tốt, nó hoạt động được (Đừng thử nó ở nhà)

    @foreach($user->payments()->get() as $payment)
    <tr>
        <td>{{$payment->type}}</td>
        <td>{{$payment->amount}}$</td>
        <td>{{$payment->created_at}}</td>
        <td>
            @if($payment->method()->first()->type == 'PayPal')
                <div><strong>Paypal: </strong> 
                {{ $payment->method()->first()->paypal_email }}</div>
            @else
                <div><strong>Card: </strong>
                {{ $payment->payment_method()->first()->card_brand }} **** **** **** 
                {{ $payment->payment_method()->first()->card_last_four }}</div>
            @endif
        </td>
    </tr>
    @foreach
    
    CÓ THỂ BẠN QUAN TÂM

    Bài Viết Cùng Chuyên Mục

    XEM THÊM
    thumbnail

    Kubernetes bài 6 - Vận hành k8s Day-Two Operations và Quản trị bằng GitOps

    22.05.2026

    Khi cụm Kubernetes của bạn đã được bảo mật cấu hình, tối ưu tài nguyên và thiết lập tự phục hồi, câu hỏi đặt ra là làm sao để duy trì sự ổn định đó trong nhiều năm tiếp theo mà không bị phụ thuộc

    thumbnail

    Kubernetes bài 5 - bảo mật Cloud Native và chuẩn DevSecOps cho K8s

    22.05.2026

    Việc siết chặt an ninh (Hardening) không phải là cấu hình một vài thông số rồi bỏ đó, mà là một tư duy phòng thủ chiều sâu.

    thumbnail

    Kubernetes bài 4 - Tối ưu Resource Auto-Healing và Scale Zero-Downtime

    22.05.2026

    Bài viết này sẽ đi sâu vào các cơ chế ở tầng Kernel giúp hệ thống tự phục hồi, chống lại các đợt tấn công cạn kiệt tài nguyên và cập nhật phiên bản mới mà người dùng không hề hay biết.

    thumbnail

    Kubernetes bài 3 - Bảo mật cấu hình k8s và config Security trên Production

    22.05.2026

    Kubernetes giải quyết bài toán này bằng hai đối tượng chuyên biệt nhưng nếu không hiểu rõ bản chất bảo mật ở tầng dưới, bạn đang tự tay dâng toàn bộ chìa khóa hệ thống cho hacker.

    thumbnail

    Kubernetes bài 2 - Mạng lưới k8s và luồng Traffic ở Packet Level

    22.05.2026

    Pod không chỉ là một container: Rất nhiều người nhầm lẫn Pod 1-1 với Container. Thực chất, Pod là đơn vị triển khai nhỏ nhất, có thể chứa một hoặc nhiều container

    Mục lục bài viết