- يتيح لك Laravel تنفيذ كل شيء بدءًا من محركات البحث البسيطة باستخدام AJAX وحتى عمليات البحث المتقدمة في النص الكامل باستخدام Laravel Scout ومحركات البحث الخارجية مثل Algolia أو Meilisearch أو Elasticsearch.
- بالنسبة للعمليات البحثية الخفيفة، فإن التصفية على الواجهة الأمامية باستخدام Alpine.js أو باستخدام طلبات الجلب الأصلية تتجنب التحميل الزائد على الخادم وتحسن تجربة المستخدم في القوائم الصغيرة.
- يقوم Laravel Scout بمركزية التكامل مع محركات البحث المختلفة ويجعل من السهل وضع علامة على النماذج باعتبارها قابلة للبحث وإدارة الفهارس وتشغيل الاستعلامات بشكل موحد.
- يجب أن يعتمد اختيار المحرك (SaaS أو مفتوح المصدر أو قاعدة البيانات) على حجم البيانات وتعقيد عمليات البحث ومتطلبات الأداء والصيانة للمشروع.
عندما تبدأ العمل مع Laravel وتحتاج إلى محرك بحث فوري الاستجابة ، قد تجد نفسك تائهاً بين آلاف الطرق الممكنة: AJAX مع fetch، وjQuery، وAlpine.js، وScout مع Algolia أو Meilisearch، وفلترة الواجهة الأمامية، وغيرها. لكن الخبر السار هو أن نظام Laravel البيئي يوفر لك عملياً كل ما تحتاجه لبناء محرك بحث سلس وسريع دون الشعور بالإرهاق.
ستتعلم في هذه المقالة كيفية تطبيق أنواع مختلفة من البحث الفوري في Laravel ، بدءًا من الإكمال التلقائي التقليدي باستخدام AJAX وصولًا إلى البحث في النصوص الكاملة باستخدام Laravel Scout ومحركات البحث مثل Algolia وMeilisearch وقاعدة البيانات نفسها، أو حتى Elasticsearch. كما ستتعرف على بدائل خفيفة الوزن باستخدام Alpine.js لتصفية البيانات مباشرةً في المتصفح عند التعامل مع مجموعات بيانات صغيرة.
ما هو البحث في الوقت الحقيقي في Laravel وكيف تعمل الأساسيات؟
تعتمد فكرة البحث الفوري على أنه بمجرد أن يكتب المستخدم في حقل نصي ، يتم تشغيل استعلام وتحديث النتائج دون إعادة تحميل الصفحة. من الناحية التقنية، يتضمن ذلك ثلاثة مكونات رئيسية: الواجهة الخلفية لـ Laravel، وجافا سكريبت المتصفح، وتبادل البيانات بتنسيق JSON.
من جهة، يعمل Laravel كطبقة خادم مسؤولة عن استقبال الطلبات، وتفسير معايير البحث (النص المُدخل)، والاستعلام عن قاعدة البيانات، وإرجاع استجابة مُهيكلة، عادةً بتنسيق JSON. يمكن أن تُشير هذه الاستجابة إلى نجاح العملية، أو وجود خطأ، أو عدم العثور على نتائج.
من جهة أخرى، يتولى جافا سكريبت مهمة الاستماع إلى أحداث المستخدم على حقل البحث، وإرسال طلبات غير متزامنة (AJAX) إلى الخادم، وعرض البيانات المُسترجعة على الصفحة دون الحاجة إلى إعادة تحميل المتصفح بالكامل. ويمكن تحقيق ذلك باستخدام وظيفة fetch الأصلية، أو jQuery AJAX، أو مكتبات تفاعلية صغيرة مثل Alpine.js.
باستخدام هذه الآلية الأساسية، يمكنك بناء أي شيء بدءًا من خاصية الإكمال التلقائي البسيطة مع عدد قليل من السجلات ، وصولًا إلى محرك بحث متقدم للنصوص الكاملة مع مراعاة الصلة والترقيم والفلاتر، بالاعتماد على مكتبات مثل Laravel Scout ومحركات البحث الخارجية المحسّنة لعمليات البحث.
النموذج والطرق ووحدة التحكم لمحرك بحث أساسي في الوقت الفعلي
قبل أن تتعمق في جافا سكريبت، تحتاج إلى أن يكون جانب Laravel منظمًا جيدًا: نموذج Eloquent للبحث فيه، ومسارات واضحة، ووحدة تحكم مخصصة للتعامل مع منطق البحث في الوقت الفعلي.
الخطوة الأولى هي إنشاء نموذج Eloquent يُمثل الجدول الذي ستُجري فيه البحث. تخيّل جدولًا للدول ونموذجًا بسيطًا جدًا يُسمى Country ، بدون طوابع زمنية ومع إمكانية التعيين الجماعي:
مثال على نموذج Eloquent بسيط لعمليات البحث :
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Pais extends Model
{
use HasFactory;
protected $guarded = [];
public $timestamps = false;
}
يشير هذا إلى أن نموذج Pais موجود في مساحة اسم Laravel القياسية ، ويرث من Model، ويسمح بتعيين أي حقل باستخدام create() مع ترك المصفوفة المحمية فارغة. بتعطيل الطوابع الزمنية باستخدام public $timestamps = false، تتجنب المشاكل في حال عدم احتواء الجدول على عمودي created_at و updated_at.
تتمثل الخطوة التالية في تحديد المسارات التي ستتعامل مع كل من عرض محرك البحث وطلبات AJAX . يجمع مخطط شائع جدًا بين مسار GET لعرض الصفحة ومسار POST مصمم لاستقبال الاستعلامات في الوقت الفعلي.
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\BuscadorController;
Route::get('/', function () {
return view('welcome');
});
Route::get('buscador', [BuscadorController::class, 'index']);
Route::post('buscador', [BuscadorController::class, 'buscar']);
يُعيد المسار الرئيسي صفحة ترحيبية، بينما يُخصّص عنوان URL /search لوظيفة البحث . تعرض دالة index() في وحدة التحكم النموذج وحقل البحث، بينما تعالج دالة search() الطلبات غير المتزامنة المُرسلة من المتصفح.
في وحدة التحكم، يمكنك تطبيق نمط عملي للغاية: قم بإعداد مصفوفة استجابة افتراضية في حالة حدوث خطأ ، ولا تقم بالكتابة فوقها إلا عندما يكون طلب AJAX صالحًا بالفعل ويتم تنفيذ الاستعلام بدون مشاكل.
قد يكون لوحدة التحكم بنية مشابهة لما يلي:
namespace App\Http\Controllers;
use App\Models\Pais;
use Illuminate\Http\Request;
class BuscadorController extends Controller
{
public function index()
{
return view('welcome');
}
public function buscar(Request $request)
{
$response = [
'success' => false,
'message' => 'Hubo un error',
];
if ($request->ajax()) {
$data = Pais::where('nombre', 'like', $request->texto.'%')
->take(10)
->get();
$response = [
'success' => true,
'message' => 'Consulta correcta',
'data' => $data,
];
}
return response()->json($response);
}
}
عند هذه المرحلة، تكون قد أكملت دورة الواجهة الخلفية: استقبال طلب AJAX، والتحقق من كونه طلب AJAX بالفعل، والاستعلام باستخدام `where like`، وتحديد عدد النتائج إلى قيمة معقولة باستخدام `take(10)` لتجنب إرهاق قاعدة البيانات. تُرسل الاستجابة دائمًا بتنسيق JSON، مما يُبسط عمل الواجهة الأمامية بشكل كبير.
عرض الشفرة وجلب JavaScript للبحث التفاعلي
بعد تجهيز النموذج والمسارات ووحدة التحكم، حان الوقت لبناء الجزء المرئي: نموذج يحتوي على حقل بحث وكتلة لعرض النتائج ، بالإضافة إلى جافا سكريبت المسؤولة عن إجراء الطلبات في الخلفية.
يمكن أن تكون واجهة Blade بسيطة للغاية، حيث تعتمد على رمز CSRF الذي يقوم Laravel بحقنه للتحقق من صحة طلبات POST وعلى حقل إدخال بحث سهل الاستخدام:
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<strong><meta name="csrf-token" content="{{ csrf_token() }}"></strong>
<title>Laravel</title>
</head>
<body>
<form action="" method="post">
<input type="search" name="texto" id="buscar">
</form>
<div id="resultado"></div>
<script>
window.addEventListener('load', function () {
const buscar = document.getElementById('buscar');
const resultado = document.getElementById('resultado');
buscar.addEventListener('keyup', function () {
fetch('/buscador', {
method: 'post',
body: JSON.stringify({ texto: buscar.value }),
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': document.head.querySelector('[name~="csrf-token"][content]').content,
},
})
.then(response => response.json())
.then(data => {
let html = '';
if (data.success) {
html += '<ul>';
for (let i in data.data) {
html += '<li>' + data.data[i].nombre + '</li>';
}
html += '<ul>';
} else {
html += 'No existen resultados';
}
resultado.innerHTML = html;
});
});
});
</script>
</body>
</html>
في هذا المثال، يستمع البرنامج النصي لحدث الضغط على مفتاح في حقل البحث ، بحيث يؤدي كل ضغطة مفتاح إلى إرسال طلب جلب إلى المسار /search. يُرسل النص الحالي للحقل بتنسيق JSON، وتُضمّن رؤوس رئيسية مثل X-Requested-With للإشارة إلى أنه AJAX، بالإضافة إلى رمز CSRF لتجاوز الحماية الأصلية لـ Laravel.
عند وصول الاستجابة، يتم تحويلها إلى JSON، ويتم إنشاء قائمة HTML صغيرة بالنتائج بشكل ديناميكي ، أو يتم عرض رسالة مثل "لم يتم العثور على نتائج" إذا لم تُرجع الاستعلامات أي بيانات. كل هذا يحدث دون إعادة تحميل الصفحة، بطريقة سلسة للمستخدم.
يمكن تحسين هذا النمط بشكل أكبر من خلال تفاصيل تجربة المستخدم الصغيرة، مثل إضافة تأخير بين ضغطات المفاتيح، أو عرض مؤشر التحميل، أو معالجة أخطاء الشبكة لمنع ظهور الواجهة متجمدة عند حدوث عطل ما.
البحث المباشر باستخدام Laravel و AJAX باستخدام jQuery
على الرغم من أن تقنية fetch قد اكتسبت شعبية كبيرة، إلا أن jQuery AJAX لا تزال تحظى بشعبية واسعة في المشاريع القديمة أو الفرق التي تستخدمها بالفعل. الفكرة هي نفسها تمامًا: التقاط ما يكتبه المستخدم، وإرسال طلب غير متزامن، وتحديث DOM.
تتضمن عملية سير العمل النموذجية باستخدام jQuery في Laravel للبحث المباشر عادةً هذه الخطوات الأساسية: تحديد مسار معين، وإنشاء وحدة تحكم مخصصة، وبناء عرض Blade مع إدخال البحث ، وأخيرًا إضافة كود jQuery الذي يقوم بتشغيل AJAX أثناء الكتابة.
تتم العملية كالتالي: عندما يبدأ المستخدم بالكتابة، يرسل jQuery استعلامًا إلى الخادم يحتوي على سلسلة البحث. يقوم Laravel بتصفية المعلومات في قاعدة البيانات، ويعيد كائن JSON يحتوي على النتائج المطابقة، ويقوم jQuery بتحديث حاوية HTML على الصفحة لعرض النتائج المطابقة، كل ذلك في غضون أجزاء من الثانية.
تتمثل ميزة استخدام jQuery في تبسيطها الكبير لبنية AJAX وسهولة قراءتها إذا كانت المكتبة موجودة بالفعل في مشروعك. مع ذلك، فإنها تُضيف تبعية إضافية قد لا تكون ضرورية إذا كنت تستخدم JavaScript الحديثة ووظائف جلب البيانات الأصلية.
التصفية والبحث في الوقت الفعلي على الواجهة الأمامية باستخدام Alpine.js
عندما تكون البيانات المراد عرضها صغيرة نسبيًا (على سبيل المثال، أقل من 50 عنصرًا )، فليس من المجدي دائمًا إنشاء نظام خلفي يتضمن عمليات بحث معقدة. في هذه الحالات، يُعدّ خيار التصفية مباشرةً في المتصفح باستخدام Alpine.js خيارًا مناسبًا للغاية ، دون الحاجة إلى إرسال طلبات إلى الخادم أثناء كتابة المستخدم.
الفكرة هي حساب سلسلة بحث لكل عنصر مسبقًا (على سبيل المثال، الاسم والوصف والفئة بأحرف صغيرة)، وحفظها في سمة data-search-text، والسماح لـ Alpine.js بمعالجة إظهار أو إخفاء العناصر بناءً على النص المدخل في حقل البحث.
يمكن أن يكون لمكون Alpine.js بنية مشابهة لما يلي: filterItems
{
search: '',
hasResults: true,
selectedValue: '',
init() {
this.$watch('search', () => this.filterItems());
this.$nextTick(() => this.$refs.searchInput?.focus());
},
filterItems() {
const searchLower = this.search.toLowerCase().trim();
const cards = this.$el.querySelectorAll('.item-card');
let visibleCount = 0;
cards.forEach(card => {
const text = card.dataset.searchText || '';
const isVisible = searchLower === '' || text.includes(searchLower);
card.style.display = isVisible ? '' : 'none';
if (isVisible) visibleCount++;
});
this.hasResults = visibleCount > 0;
},
}
في العرض، تحتوي كل بطاقة أو صف من البيانات على سمة data-search-text مع النص المُعد مسبقًا بأحرف صغيرة ، وبالتالي يتم اختزال عامل التصفية إلى دالة includes() في جافا سكريبت، وهو أمر سريع جدًا للقوائم القصيرة:
<input type="search" x-model="search" x-ref="searchInput" placeholder="Buscar..." />
<div>
<div class="item-card" data-search-text="formulario contacto simple">
<h3>Formulario de contacto</h3>
<p>Formulario de contacto simple</p>
</div>
</div>
بالإضافة إلى ذلك، يمكنك عرض كتلة حالة فارغة فقط عندما لا توجد نتائج لمصطلح البحث الحالي ، مما يدعو المستخدم إلى تعديل النص أو مسح الحقل باستخدام زر يقوم ببساطة بإعادة تعيين البحث إلى سلسلة فارغة.
يتميز هذا الأسلوب بمزايا واضحة: لا يتطلب البحث أي استدعاءات للخادم ، والتفاعل فوري تقريبًا، ويبقى المنطق محليًا للغاية ويسهل تصحيحه. إنه مثالي للمحددات السريعة، ونوافذ اختيار العناصر، أو الكتالوجات الصغيرة المضمنة في صفحة Laravel.
Laravel Scout: البحث عن النص الكامل باستخدام محركات متخصصة
عندما يصبح الأمر جديًا وتحتاج إلى بحث نصي كامل سريع ودقيق وقابل للتوسع ، فإن الخيار الأمثل في Laravel هو Laravel Scout. Scout هي طبقة تكامل تُمكّنك من ربط نماذج Eloquent الخاصة بك بسهولة بمحركات البحث مثل Algolia وMeilisearch وقاعدة البيانات ومجموعات البيانات المخزنة في الذاكرة، أو حتى Elasticsearch عبر وحدات تحكم خارجية.
للبدء باستخدام Scout، تتمثل الطريقة المعتادة في إنشاء مشروع Laravel جديد أو إعادة استخدام مشروع موجود ، ثم تشغيله باستخدام Docker (على سبيل المثال، باستخدام Laravel Sail)، وبعد ذلك تثبيت المكتبة باستخدام Composer. بمجرد الانتهاء من ذلك، يتم نشر ملف التكوين scout.php وتعديل متغيرات البيئة وفقًا لبرنامج التشغيل الذي ترغب في استخدامه.
تتضمن آلية العمل النموذجية تثبيت Scout باستخدام Composer، ونشر إعداداته، وتفعيل قائمة انتظار الفهرسة باستخدام `SCOUT_QUEUE=true` في ملف `.env`، بحيث تتم معالجة العمليات كثيفة الموارد في الخلفية، مما يُحسّن أوقات استجابة التطبيق. بالإضافة إلى ذلك، يجب التأكد من أن `DB_HOST` يُشير إلى قاعدة البيانات المستخدمة، وهو أمر بالغ الأهمية خاصةً عند استخدام حاويات Docker.
لكي يشارك نموذج ما في عمليات بحث Scout، يجب تحديده صراحةً على أنه قابل للبحث بإضافة السمة `Searchable` . على سبيل المثال، إذا كان لديك نموذج `Train` يمثل جدولًا للقطارات مع حقل `title`، فيمكنك تعريفه على النحو التالي:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Train extends Model
{
use Searchable;
protected $fillable = ['title'];
public function searchableAs()
{
return 'trains_index';
}
}
تتيح لك طريقة `searchableAs` تخصيص اسم الفهرس في محرك البحث ، بدلاً من استخدام الاسم الافتراضي المستمد من النموذج. ومن ثم، يتولى Scout مزامنة عمليات الإنشاء والتحديث والحذف مع الفهرس البعيد أو المحلي، وذلك حسب برنامج التشغيل المُختار.
Laravel Scout مع Algolia: بحث SaaS فائق السرعة
Algolia هي خدمة برمجية كخدمة (SaaS) تركز على توفير عمليات بحث سريعة ودقيقة للغاية عبر كميات هائلة من البيانات . تتميز بلوحة تحكم عبر الويب لإدارة الفهارس وقواعد الصلة والمترادفات وغيرها، وتتكامل بسلاسة مع Laravel عبر Scout وعميل PHP الرسمي.
لاستخدام Algolia مع Scout، ستحتاج إلى تثبيت عميل PHP الخاص بها باستخدام Composer، وتسجيل بيانات اعتمادك في ملف .env (معرّف التطبيق ومفتاح API الخاص بالمسؤول)، وتكوين SCOUT_DRIVER=algolia لإخبار Scout باستخدام هذا المحرك. يمكنك الحصول على كل من معرّف التطبيق ومفتاح المسؤول من لوحة تحكم Algolia.
بمجرد إعداد البيئة، يمكنك استخدام طرق مثل Train::search('text')->paginate(6) مباشرة في وحدات التحكم الخاصة بك لإجراء عمليات البحث على الحقول المفهرسة، واستلام النتائج بتنسيق Eloquent المقسم إلى صفحات جاهزة للتمرير إلى عرض Blade.
على سبيل المثال ، يمكنك أن يكون لديك وحدة تحكم فهرس تسرد جميع القطارات أو تجري بحثًا إذا تم تمرير معلمة البحث عن العنوان، وطريقة إنشاء لإدراج قطارات جديدة في الفهرس:
public function index(Request $request)
{
if ($request->has('titlesearch')) {
$trains = Train::search($request->titlesearch)->paginate(6);
} else {
$trains = Train::paginate(6);
}
return view('Train-search', compact('trains'));
}
public function create(Request $request)
{
$this->validate($request, ['title' => 'required']);
Train::create($request->all());
return back();
}
في العرض المقابل، يمكنك دمج نموذج لإضافة قطارات جديدة مع نموذج GET آخر يحتوي على حقل بحث بالعنوان يُفعّل البحث عند الإرسال. بعد ذلك، ما عليك سوى المرور على مجموعة القطارات وعرض حقولها في جدول، مستفيدًا من روابط الترقيم التي يُنشئها Laravel.
Scout مع Meilisearch وقاعدة البيانات والمجموعات
إذا كنت تفضل تجنب الخدمات الخارجية، فإن Meilisearch محرك بحث مفتوح المصدر يمكنك تثبيته محليًا أو على بنيتك التحتية. يتكامل Scout مع Meilisearch بطريقة مشابهة جدًا لـ Algolia، وذلك ببساطة عن طريق تغيير برنامج التشغيل وإضافة المتغيرين MEILISEARCH_HOST و MEILISEARCH_KEY إلى ملف .env.
لاستخدامه، ثبّت عميل Meilisearch PHP، وعيّن SCOUT_DRIVER=meilisearch ، ووجّه MEILISEARCH_HOST إلى عنوان URL الخاص بالمثيل (على سبيل المثال، http://127.0.0.1:7700). إذا كانت لديك سجلات بالفعل، يمكنك فهرستها باستخدام الأمر php artisan scout:import "App\Models\Train" لتكون متاحة للمحرك.
بالنسبة للتطبيقات الصغيرة أو متوسطة الحجم، يمكنك أيضًا اختيار برنامج تشغيل قاعدة بيانات Scout ، الذي يستفيد من فهارس البحث النصي الكامل وعبارات LIKE في قاعدة بيانات MySQL أو PostgreSQL. في هذه الحالة، لست بحاجة إلى خدمة خارجية؛ ما عليك سوى تعيين SCOUT_DRIVER=database لكي يستخدم Scout قاعدة البيانات نفسها كمحرك بحث.
خيار آخر مثير للاهتمام هو مُشغّل المجموعات، الذي يعمل مع مجموعات Eloquent الموجودة في الذاكرة . يقوم هذا المُشغّل بتصفية النتائج باستخدام عبارة `where` وطرق تصفية المجموعات، وهو متوافق مع أي قاعدة بيانات يدعمها Laravel. يمكنك تفعيله باستخدام `SCOUT_DRIVER=collection` أو بتعديل ملف تكوين Scout إذا كنت ترغب في تخصيصه بشكل أكبر.
التكامل مع Elasticsearch باستخدام Explorer
إذا كانت احتياجات البحث لديك تتطلب التعامل مع كميات هائلة من البيانات وتحليلات فورية ، فإن Elasticsearch خيار كلاسيكي. في بيئة Laravel، تتمثل إحدى الطرق الحديثة لدمجه مع Scout في استخدام وحدة تحكم Explorer، التي تعمل كجسر بين نماذجك ومجموعة Elasticsearch.
يتضمن هذا عادةً العمل مع Docker وملف docker-compose غنيّ يُشغّل، بالإضافة إلى الخدمات المعتادة (Laravel، MySQL، Redis، Meilisearch، إلخ)، حاويات Elasticsearch وKibana . ثم تقوم بتثبيت حزمة jeroen-g/explorer عبر Composer ونشر ملف التكوين الخاص بها لتحديد النماذج التي يجب فهرستها.
في ملف config/explorer.php، يمكنك تسجيل نماذجك تحت مفتاح indexes، على سبيل المثال بإضافة App\Models\Train::class . بالإضافة إلى ذلك، يمكنك تغيير برنامج التشغيل من Scout إلى Elasticsearch في ملف .env باستخدام SCOUT_DRIVER=elastic بحيث يشير كل شيء إلى Elasticsearch.
ضمن نموذج التدريب، عليك تطبيق واجهة Explored وتجاوز دالة mappableAs ، التي تحدد خريطة الحقول المراد إرسالها إلى الفهرس. مثال بسيط على ذلك:
use JeroenG\Explorer\Application\Explored;
use Laravel\Scout\Searchable;
class Train extends Model implements Explored
{
use Searchable;
protected $fillable = ['title'];
public function mappableAs(): array
{
return [
'id' => $this->id,
'title' => $this->title,
];
}
}
من هنا، يمكنك إطلاق عمليات البحث على Elasticsearch باستخدام واجهة Scout نفسها ، والاستفادة من أوقات الاستجابة المنخفضة للغاية وقوة الاستعلام الكاملة لهذا المحرك، ولكن دون مغادرة نظام Laravel البيئي.
بفضل كل هذه الأساليب - بدءًا من الإكمال التلقائي الأساسي باستخدام fetch أو jQuery، إلى تصفية الواجهة الأمامية باستخدام Alpine.js، إلى عمليات البحث في النصوص الكاملة باستخدام Laravel Scout وبرامج التشغيل المختلفة - يمنحك Laravel مجموعة كبيرة من الخيارات لتنفيذ عمليات البحث في الوقت الفعلي المصممة خصيصًا لحجم مشروعك، والأداء الذي تحتاجه، والبنية التحتية التي ترغب في صيانتها.