البحث في أكثر من موديل في لارافيل

البحث في أكثر من موديل في لارافيل

2024-10-18 وقت القراءه : 5 دقائق

ببعض الأحيان قد. نظطر للقيام بعملية بحث في أكثر من Eloquent models مثل ( posts, authors, books )، وواحده من هذه الحلول هي إنشاء جملة query لكل model.


لنفرض أن لدينا الجداول التالية posts, authors, books

ولدينا form  واحد في ملف blade ومن خلال هذا الفورم يتم البحث في جميع الجداول أعلاه

ما هي الحلول المتاحة أمامنا، بداية سوف أبدأ من أبسط فكرة.


الطريقة الأولى - إنشاء ثلاثة جمل query

تعتبر هذه الطريقة الأسل لأداء المهمة

public function index(Request $request)
{
    $results['posts'] = Post::where('title', 'like', '%' . $request->search . '%')->get();
    $results['authors'] = Author::where('author_name', 'like', '%' . $request->search . '%')->get();
    $results['books'] = Book::where('book_name', 'like', '%' . $request->search . '%')->get();

    return view('articles', compact('results'));
}

وفي ملف blade يمكن عمل foreach وتحقق من نتائج كل query

    <div class="font-bold mt-2 mb-2">Posts:</div>
    @if ($results['posts']->count())
        <ul class="list-inside">
            @foreach ($results['posts'] as $post)
                <li class="list-disc">{{ $post->title }}</li>
            @endforeach
        </ul>
    @else
        <span class="text-danger">No results.</span>
    @endif


    <div class="font-bold mt-2 mb-2">Authors:</div>
    @if ($results['authors']->count())
        <ul class="list-inside">
            @foreach ($results['authors'] as $author)
                <li class="list-disc">{{ $author->author_name }}</li>
            @endforeach
        </ul>
    @else
        <span class="text-danger">No results.</span>
    @endif


    <div class="font-bold mt-2 mb-2">Books:</div>
    @if ($results['books']->count())
        <ul class="list-inside">
            @foreach ($results['books'] as $book)
                <li class="list-disc">{{ $book->book_name }}</li>
            @endforeach
        </ul>
    @else
        <span class="text-danger">No results.</span>
    @endif

تعتبر الطريقة أعلاه صحيحة، وتؤدي الغرض بشكل سليم، لكنها تعتبر طويلة من حيث جمل eloquent وكذلك التحقق و foreach في ملف الـ blade.



الطريقة الثانية : إستخدام حزمة Spatie Laravel Searchable

حيث تساعدنا هذه الحزمة في إستخدام جملة eloquent واحده، وكذلك foreach واحده في ملف blade

بداية تثبيت الحزمة

composer require spatie/laravel-searchable

في الموديل نقوم بعمل implement للـ Searchable interface ومن ثم إضافة الدالة getSearchResult لتحديد الـ field الذي سيتم البحث به.

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Spatie\Searchable\Searchable;
use Spatie\Searchable\SearchResult;

class Post extends Model implements Searchable
{
    public function getSearchResult(): SearchResult
    {
        return new \Spatie\Searchable\SearchResult(
            $this,
            $this->title,
        );
    }
}


<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Spatie\Searchable\Searchable;
use Spatie\Searchable\SearchResult;

class Author extends Model implements Searchable
{
    public function getSearchResult(): SearchResult
    {
        return new \Spatie\Searchable\SearchResult(
            $this,
            $this->author_name,
        );
    }
}


<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Spatie\Searchable\Searchable;
use Spatie\Searchable\SearchResult;

class Book extends Model implements Searchable
{
    public function getSearchResult(): SearchResult
    {
        return new \Spatie\Searchable\SearchResult(
            $this,
            $this->book_name,
        );
    }
}


ومن ثم في الـ Controller في دالة البحث يتم أخذ new object من search ونستخدم registerModel

use Spatie\Searchable\Search;
public function index(Request $request)
{
    $results = (new Search())
        ->registerModel(Post::class, 'title')
        ->registerModel(Author::class, 'author_name')
        ->registerModel(Book::class, 'book_name')
        ->search(request('search'));

    return view('articles', compact('results'));
}

الأن نأتي للجزء الأهم وهو ملف blade حيث إنه بشكل تلقائي يتم عمل group للبيانات

@forelse($results->groupByType() as $type => $modelSearchResults)
    <div class="font-bold mt-2 mb-2">{{ ucfirst($type) }}</div>
    <ul class="list-inside">
        @foreach($modelSearchResults as $searchResult)
            <li class="list-disc">
                {{ $searchResult->title }}
            </li>
        @endforeach
    </ul>
@empty
    No results.
@endforelse

وكما نلاحظ إنه في ملف blade تم إستخدام foreach واحده، عوضاً عن إستخدام ثلاثة foreach في الطريقة الأولى.


كما يمكن أيضا تمرير أكثر من attribute للبحث

$searchResults = (new Search())
   ->registerModel(User::class, 'first_name', 'last_name')
   ->search('john');



الأن السؤال : إذا كان هناك نتائج تتعلق بـ Post Model يجب أن يتم توجيه المستخدم عند الضغط على النتيجة إلى post/id وإذا كانت النتائج تتعلق بـ Book Model يجب أن يتم توجيه المستخدم إلى book/id وهكذا، كيف ممكن عمل ذلك؟.

بداية نحتاج لإضافة الخاصية url في الدالة getResult التي تم إنشاؤها في كل موديل

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Spatie\Searchable\Searchable;
use Spatie\Searchable\SearchResult;

class Post extends Model implements Searchable
{
    public function getSearchResult(): SearchResult
    {
        return new \Spatie\Searchable\SearchResult(
            $this,
            $this->title,
            '/post/'.$this->id
        );
    }
}


كما نلاحظ إنه تم إضافة post/$this->id وهذا سيكون مسار عند الضغط على إي نتيجة تتعلق بالـ Post Model

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Spatie\Searchable\Searchable;
use Spatie\Searchable\SearchResult;

class Author extends Model implements Searchable
{
    public function getSearchResult(): SearchResult
    {
        return new \Spatie\Searchable\SearchResult(
            $this,
            $this->author_name,
            '/author/'.$this->id
        );
    }
}


<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Spatie\Searchable\Searchable;
use Spatie\Searchable\SearchResult;

class Book extends Model implements Searchable
{
    public function getSearchResult(): SearchResult
    {
        return new \Spatie\Searchable\SearchResult(
            $this,
            $this->book_name,
            '/book/'.$this->id
        );
    }
}

وفي ملف blade يجب إضافة 

<a href="{{ $searchResult->url }}">{{ $searchResult->title }}</a>
@forelse($results->groupByType() as $type => $modelSearchResults)
    <div class="font-bold mt-2 mb-2">{{ ucfirst($type) }}</div>
    <ul class="list-inside">
        @foreach($modelSearchResults as $searchResult)
            <li class="list-disc">
               <a href="{{ $searchResult->url }}">{{ $searchResult->title }}</a>
            </li>
        @endforeach
    </ul>
@empty
    No results.
@endforelse



التعليقات
Muna
منذ سنة

Can it used for api

tohami
منذ سنة

كلام جميل ومختصر و كليين كوود بس انت هنا في البلييد قرات ال title الذي هوو attribute في post model {{ $searchResult->title }} كيف ممكن اقراء داتا من الموديلات الاخري مع العلم ان ال foreach واحدة

Mohammad Salloum
منذ سنة

شكراً لك مقالة مفيدة

إضافة تعليق
Loading...