البحث في الموقع
المحتوى عن 'eloquent'.
-
يأتي Laravel بإطار العمل Eloquent مضمّنا مبدئيا؛ وهو ما يتيح بناء نماذج تُعالّج فيها البيانات وتُطبّق عليها القواعد التي تحكُم سير التطبيق. يُيسّر Eloquent كثيرا من الأمور على المطوِّر؛ إلا أنّه يمكن أن يكون أحيانا غير مناسب لتعقيد المشروع الذي تعمل عليه. يوفّر Laravel آلية للتعامل مع قواعد البيانات مباشرة دون الحاجة لربط كائنات التطبيق بجداول قاعدة البيانات. تُسمى هذه الآلية منشئ الاستعلامات Query builder. كانت الاستعلامات المعدّة بمنشئ الاستعلامات ترجع النتائج، في الإصدارات السابقة (قبل الإصدار 5.3) من Laravel، على هيئة مصفوفة Array؛ إلا أن الأمر تغيّر في الإصدار 5.3 وأصبحت أغلب دوالّ بناء الاستعلامات ترجع نتيجة من نوع Collection (مجموعة). سنرى في هذا المقال أمثلة تفصيلية حول هذا التغيير، ولكن قبل ذلك سنعرّج على مقارنة بين Eloquent ومنشئ الاستعلامات ومتى يكون استخدام أحدهما أنسب. أيهما أستخدم.. Eloquent أم منشئ الاستعلامات؟ يخضع تطوير البرمجيّات لإكراهات كثيرة، وهو ما يعني أن عليك أحيانا كتابة استعلامات SQL للتخاطب مع قاعدة البيانات مباشرة. يتيح Eloquent آلية أنيقة للتعامل مع قاعدة البيانات بربطها بأصناف التطبيق ممّا يقلّل كثيرا من الحاجة لتدخّل المطوّر في تفاصيل تنفيذ العمليّات. تشمل التسهيلات التي يتيحها Eloquent: التحقق المُستَتِر Dirty check: تحديث الحقول التي تغيّرت قيمتها فقط دون غيرها، عند تنفيذ استعلامات UPDATE. ربط النماذج بأحداث Events؛ مثلا لإشعار المستخدمين أو تحديث إحصائيات عند إنشاء مستخدم جديد. إدارة الحذف اللطيف Soft delete: تعليم تسجيلة على أنها محذوفة بدلا من حذفها فعلا. تنفيذ آليات التحميل الحثيث Lazy loading (طلب البيانات عند الحاجة إليها). يمكن - بالطبع - تطبيق هذه الآليات بنفسك دون الاعتماد على Eloquent؛ إلا أن ذلك سيأخذ وقتا. من الجيد أن يكون لديك سبب مقنع للوقت الذي ستمضيه في هذه المهمة بدلا من التركيز على أمور *قد* تكون أهم. سيكون مثاليّا جدا الحصولُ على كلّ هذه التسجيلات بدون دفع ثمن، إلا أن الأمور في الواقع ليست مثالية. الثمن الذي تدفعه مقابل التسهيلات التي يتضمّنها Eloquent - وأطر عمل ORM الأخرى، بغضّ النظر عن لغة البرمجة - هو سرعة تنفيذ الاستعلامات. على الجهة الأخرى، تمكّن كتابة الاستعلامات من الوصول إلى أقصى سرعة ممكنة في التنفيذ، مقابل التضحية بالجهد والوقت أثناء التطوير. لا يوجد مانع من مزج جميع الوسائل المتاحة أمامك؛ بل إن الأمر محبّذ. مثلا؛ استخدم Eloquent عندما تتعامل مع كيانات منفردة (استمارة تسجيل على موقع مثلا)، الجأ لمنشئ الاستعلامات في المهامّ التي تتطلب وقتا للتنفيذ (كيانات بالجملة Batch processing أو إعداد تقارير على سبيل المثال) واكتب استعلامات SQL مباشرة عندما تكون أمام استعلام شديد التعقيد. الجديد في Laravel 5.3 نستخدم منشئ الاستعلامات في المثال التالي للحصول على كافة المستخدمين (جدول users في قاعدة البيانات): DB::table('users')->get(); تكافئ التعليمة السابقة في نتيجتها تنفيذ الاستعلام التالي: SELECT * FROM users; كانت النتيجة، في الإصدارات السابقة من Laravel، من نوع Array (مصفوفة)؛ إلا أن هذا الأمر تغيّر في الإصدار 5.3 لتصبح النتيجة من نوع Collection (مجموعة). يعني هذا أنه للوصول إلى العنصر الأول في مجموعة المستخدمين السابقة فإننا يمكن أن نكتب: DB::table('users')->get()->first(); بدلا من: DB::table('users')->get()[0]; يجعل هذا التغيير من منشئ الاستعلامات متناسقا في نوع النتيجة مع Eloquent الذي يرجع مجموعة كائنات؛ كما أنه يمكّن من استخدام الدوال الموجودة مبدئيا للتعامل مع المجموعات؛ ومن بينها الدالة all التي ترجع مصفوفة مكافئة للمجموعة؛ مثلا ترجع التعليمة التالية مصفوفة بالمستخدمين: DB::table('users')->get()->all(); إن كان لديك تطبيق مبني على اعتبار أن منشئ الاستعلامات يُرجع النتائج على هيئة مصفوفات، فأسهل طريقة لتعديل الشفرة المصدرية للتوافق مع تغيير نوع النتيجة إلى مجموعة بدلا من مصفوفة؛ هي استخدام الدالة all. ملحوظة 1: لا ينطبق التغيير المذكور أعلاه (إرجاع مجموعة بدلا من مصفوفة) على دوال الصنف DB التي تتعامل مع استعلامات SQL مباشرة. مثلا؛ ترجع التعليمة التالية مصفوفة: DB::select('SELECT name FROM users WHERE id = ?', [1]); ملحوظة 2: انتبه عند استخدام استعلامات SQL مباشرة من ثغرات الحَقن SQL injection. استخدم ربط المتغيّرات Binding (كما في الملحوظة 1) بدلا من تمرير المتغيّر مباشرة كما في الاستعلام التالي: DB::select("SELECT name FROM users WHERE id = '$id' "]); قد يمثل الاستعلام الأخير خطرا إن لم يُتحقّق من المتغيّرid$ قبل استخدامه في الاستعلام. يتحقّق Laravel تلقائيا من المُدخلات عند ربط المتغيّرات كما في استعلام الملحوظة 1. راجع مقال استخدام Eloquent ORM للتعامل مع قاعدة البيانات في Laravel 5.
-
يحدُث كثيراً أن ينقسم مجتمع المطوّرين حول تقنيّات يراها بعضهم غير ذات جدوى أو على الأقل ليست بالأهميّة التي يروّج لها مؤيّدوها. تنطبق هذه الملاحظة على أطر العمل التي تعمل على ربط العلاقات بالكائنات Object relational mapping (أو ORM اختصارا)، ومن بينها Eloquent الذي يُستخدَم مبدئيًّا في Laravel؛ دون أن يعني ذلك عدم إمكانيّة استخدامه خارج Laravel كما سنرى في هذه السّلسلة. ليست أطر عمل ORM بالتقنيّة الجديدة، فهي توجد منذ سنين وأصبحت جزءًا من عمليّة التطوير بالنسبة للكثير من المبرمجين؛ كما أنها حسّنت من أدائها وتغلّبت على الكثير من النواقص مع الزمن. يتناول هذا الدّرس، الأوّل من سلسلة دروس عن الإطار Eloquent ORM، الأسباب التي أدّت إلى ظهور أطر عمل ORM والهدف من ورائها. التصوّر البرمجي Programming paradigm وإدارة البيانات العلاقيّة تُكتَب أغلب تطبيقات PHP الحديثة بأسلوب ذي تصوّر Paradigm كائنيّ التوجّه Object oriented، بينما تستخدم قواعدَ بيانات علاقيّة لإدارة البيانات وتخزينها. دور أُطُر عمل ORM هو تسهيل الانتقال بين هذيْن العالميْن أثناء تشغيل التطبيق. البرمجة كائنية التوجّه تعتمد لغات البرمجة على أفكار ونُظُم تحدّد طريقة عملها وأساليب كتابة البرامج التي تعمل بها. تُسمّى هذه الأساليب والقواعد بالتصورات البرمجيّة. يمكن للغةِ برمجة أن تُصنَّف في أكثر من تصوّر؛ إلّا أن كل لغة تركّز غالبًا على بضعة تصوّرات، ممّا يجعل كتابة برامج وفقَ هذه التصوّر ات أسهل بالنسبة لمبرمجي اللغة. تدعم لغة PHP البرمجيّة ميزات من تصوّرات برمجيّة عدّة؛ إلا أنها تُركّز على تصوّريْن: التصوّر الإجرائي Procedural الذي يمكن عدّه التصوّر الأساسي للغة منذ بداياتها. التصوّر كائنيّ التوجه Object oriented الذي اعتمده مصمّمو لغة البرمجة - فعليًّا - في الإصدار الخامس (سنة 2004). يُؤسَّس التصوّر الإجرائي على التصريح بكيفيّة إجراء العمليّات، خطوة بخطوة، واستخدام الحلقات Loops، العبارات الشرطيّة Conditions وتجميع الأوامر في سياقات محليّة Local context أو ضمن وحدات Modules يمكن استدعاءها عند الحاجة. أما التصوّر كائنيّ التوجه فيقوم على جمع البيانات والإجراءات (الدوالّ) ضمن كائن Object واحد يمكنه تطبيق الإجراءات المضمَّنة فيه على البيانات التي تخصّه. أنظمة قواعد بيانات العلاقيّة يعتمد النموذج العلاقي على نظريّة المجموعات Set theory والمنطق الرياضي من أجل تجميع البيانات ضمن جداول Tables؛ هي في الواقع علاقات Relations وفق التعريف الرياضي، ومنه التسميّة. يهدف النموذج العلاقي إلى تقديم طريقة تمكّن المستخدمين من تحديد البيانات المُعدّة للتخزين في قاعدة البيانات وتلك التي يريدون الحصول عليها من القاعدة، دون الاهتمام بالكيفيّة التي يستخدمها نظام الإدارة للإجابة على هذه الطلبات. يسمّى التصوّر البرمجي الذي يهتمّ بماهيّة المهمّة دون التركيز كثيرًا على كيفيّة أدائها بالتصوّر التقريري Declarative paradigm. يمكن عدّ لغة الاستعلام SQL تطبيقًا عمليًّا للنموذج العلاقي، وإن كانت تنحرف أحيانًا عن هذا النموذج. ربط العلاقات بالكائنات Object relational mapping تنبني شفرة التطبيقات التي تعتمد على تصوّر كائنيّ التوجه على أسس نظريّة تختلف عن تلك التي تُبنى عليها قواعد البيانات (النموذج العلاقي)؛ وهو ما يعني أنه على المطورين إيجاد طريقة للتخاطب مع قاعدة البيانات انطلاقًا من الشفرة البرمجيّة. يستخدم المبرمجون عادةً كائنًا مخصّصًا - يُسمّى كائن الوصول إلى البيانات Data access object، أو DAO اختصارا - لتخزين البيانات في القاعدة أو جلبها منها ثم تقديمها لبقيّة التطبيق بصيغة تناسبه. تُكتَب في هذا الصّنف استعلامات SQL المطلوب تنفيذها للتخاطب مع قاعدة البيانات، إما مباشرة أو عبر مكتبة وسيطة. يُساعد استخدام كائن DAO على الفصل بين أجزاء التطبيق واحترام مبادئ نمط التطوير Model-Vue-Controller (أو MVC اختصارا)؛ إلا أنّ كائنات DAO تتطلّب الكثير من الوقت، إذ يتحتّم على المطوِّر كتابة الكثير من التعليمات لتخزين البيانات في القاعدة أو جلبها منها. تأتي أطُر عمل ORM - ومن بينها Eloquent - للتخفيف من عبء هذه المهمّة. إطار العمل Eloquent ORM يسعى Laravel إلى أن يكون إطار عمل سهلَ الاستخدام يوفّر على المطوّرين الوقت ويرفع من إنتاجيّتهم؛ لذا جعل من Eloquent إطار عمل ORM الذي يستخدمه مبدئيًّا، لما يتميّز به من سهولة الاستخدام ووضوح آليّة العمل. طُوِّر Eloquent وفقا لنمط التسجيلة النشطة Active record الذي يقوم على وجود صنف Class لكلّ جدول في قاعدة البيانات، بحيث يُنشَأ كائن من هذا الصّنف لكل تسجيلة (سطر في الجدول). يعني هذا أن بيانات الكائن تحمل نسخة طبق الأصل من تسجيل الجدوَل. يُسمِّى Eloquent الصّنفَ الذي يطابق الجدول بالنموذج Model. يحوي كلّ كائن من هذا الصنف خاصيّات Attributes بعدد وأسماء توافق حقول (أعمدة) الجدول؛ وتمكّن الدوالّ التي يوفّرها من القيام بعمليّات من قبيل إدراج التسجيلة، التعديل عليها أو حذفها. إذا كان لدينا - مثلا - جدول في قاعدة البيانات باسم Address نحتفظ فيه بعناوين أشخاص، فإن إدراج عنوان جديد في قاعدة البيانات سيكافئ إنشاء كائن من الصنف (النموذج) Address وملئه بالبيانات التي نريد ثم استدعاء دالة التخزين في هذا الكائن. يضيف Eloquent وظائف مساعدة مثل التحقّق من أن الكائنات تحترم قواعد معيّنة قبل تخزينها في الجدول؛ وهو ما يعني التقليل من الأخطاء على مستوى قاعدة البيانات. قد تجد نفسك أمام حالات معقّدة لا تؤدّي فيها هذه الطريقة (تطبيق الدوالّ على الصنف المماثل لجدول قاعدة البيانات) النتيجة المطلوبة؛ يمكنك في هذه الحالة استخدام منشئ الاستعلامات Query builder وكتابة استعلامات SQL مباشرة. يُنصَح بتجنّب Eloquent عند تنفيذ سلسلة طويلة من المهامّ تلقائيًّا، دون تدخّل يدوي. يُناسب Eloquent المعاملات Transactions، وهي مجموعة إجراءات تُنفَّذ في قاعدة البيانات على أنّها وِحدة لا تتجزّأ: إما أن تُطبَّق جميع الإجراءات الموجودة في المعاملة أو لا تُطبَّق أي منها.
-
قبل أن نذهب إلى الموضوع الرئيسيّ للمقال، سأعطيك لمحة قصيرة عن مشاكل التصميم التي قد تواجهها. لقد اشتكى لي أحد زبائني بأن بعض الصفحات تفتح ببطء شديد. وعندما أقول ببطء شديد، فإنني أعني ذلك! فقررت أن أصحح تلك الصفحة (بعمل debugging)، وما رأيته قد صدمني. لقد أظهر لي قسم الاستعلامات (queries) أن تلك الصفحة كانت تنفَّذ بعد القيام بكم هائل من الاستعلامات تعدّى 16500 استعلامًا!! لقد وجدت أن جزءًا من النصّ البرمجي هو سبب تلك المشكلة. لقد كانت هناك ثلاث حلقات foreach تستعلم عن خاصيّة والخواص الفرعيّة التابعة لها. لقد كانت تعمل جيدًا إلى أن صار في قاعدة البيانات 5500 عنصرًا. وفيما يلي ما كان يحدث: $main_object = MainObject::all(); foreach($main_object as $object) { echo $object->some_property; foreach($object->related_object as $related) { echo $related->some_property; echo $related->another_property; } foreach($object->another_related as $another) { echo $another->some_property; echo $another->another_property; } }إذا كان الاستعلام ;()main_object = MainObject::all$ يعيد 5500 نتيجة، فستعيد حلقة foreach الأولى ذلك القدر أيضًا، وكذلك بالنسبة للثانية والثالثة. باستخدام ORM، كثيرًا ما يقع المبرمجون في فخّ كتابة استعلامات قواعد بيانات غير كفؤة، وتجعلها ORM أكثر صعوبة في الاكتشاف. تُسمّى هذه المشكلة بمشكلة N+1 (بالإنجليزيّة N+1 problem). وأظن المطور السابق لم يكن على علم بذلك. ولتفادي هذه المشكلة، نستخدم التحميل الحثيث (eager loading). ما هو التحميل الحثيث؟لتبسيط الأمر، التحميل الحثيث طريقة تُعنى بعمل كل شيء عند الطلب. وهذه الطريقة أيضًا على العكس تمامًا من التحميل الكسول (lazy loading) عندما ننفذ المهام عند الحاجة. يساعدنا التحميل الحثيث على تجنب مشكلات الأداء، كما رأيت في مثالي أعلاه. ستفهم الأمر أكثر من خلال مثال، لذا لنتخيل الوضع التالي: لدينا نموذج علاقة هيئة محسّنة (بالإنجليزيّة: Enhanced Entity Relationship، واختصارًا EER)، بثلاث هيئات، كلّ منها مرتبطة بالأخرى. يمكنك أن تقرأ EER كما يلي: يمكن لكل عضو أن يملك العديد من المحلات، ولكن المحل الواحد ملك لعضو واحد فقط. يمكن للمحل الواحد أن يحوي العديد من المنتجات، ولكن المنتج الواحد لا يكون إلّا في محل واحد. الخطوة التالية هي إنشاء نماذج Eloquent لهذه الهيئات: العضو: <?php namespace App; use Illuminate\Database\Eloquent\Model; class Member extends Model { protected $fillable = ['username', 'email', 'first_name', 'last_name']; public function stores() { return $this->hasMany('App\\Store'); } }المحلّ: <?php namespace App; use Illuminate\Database\Eloquent\Model; class Store extends Model { protected $fillable = ['name', 'slug', 'site', 'member_id']; public function member() { return $this->belongsTo('App\\Member'); } public function products() { return $this->hasMany('App\\Product'); } }المُنتَج: <?php namespace App; use Illuminate\Database\Eloquent\Model; class Product extends Model { protected $fillable = ['name', 'short_desc', 'long_desc', 'price', 'store_id', 'member_id']; public function store() { return $this->belongsTo('App\\Store'); } }تخيّل أنك تبني تطبيقًا يسمح لمستخدميك أن يُنشئوا محالّهم التجاريّة الخاصّة. يمكن للمستخدمين –كما هو الحال بالنسبة للمحال الأخرى كلها طبعًا– أن يُنشئوا منتَجات عديدة. وأيضًا، يمكننا أن ننشئ صفحة واحدة تعرض كل المحلات وأفضل المنتجات لكل محلّ. شيء من قبيل هذا: يمكن أن ينتهي بك المطاف إلى الحصول على شيء كهذا في المتحكّم لديك: <?php namespace App\Http\Controllers; use App\Repositories\StoreRepository; class StoresController extends Controller { protected $stores; function __construct(StoreRepository $stores) { $this->stores = $stores; } public function index() { $stores = $this->stores->all(); return \View::make('stores.index')->with('stores', $stores); } }وفي العرض الذي ستقدم فيه تلك البيانات: @foreach($stores as $store) <h1>{{ $store->name }}</h1> <span>Owner: {{ $store->member->first_name . ' ' . $store->member->last_name }}</span><br> <h2>Products:</h2> @foreach($store->products as $product) <h3>{{$product->name}}</h3> <span>{{$product->short_desc}}</span><br/><br/> <span>Price: {{$product->price}}</span><br/> <?php Debugbar::info('Product displayed'); ?> @endforeach <br/>========================<br/> @endforeachوالنتيجة كالتالي: ومن اجل هذا المثال، زوّدت قاعدة البيانات بخمس مستخدمين، وثلاثة محالّ، وأربعة منتَجات. يقوم الاستعلام الأول باستدعاء كل المحال من قاعدة البيانات، وهذا هو الجزء +1 من مشكلة N+1. في هذا المثال تحديدًا، حرف N يمثّل عدد المحلات التي أرجعها لنا الاستعلام الأول، حيث أنها تمثل عدد المرات التي سنقوم فيها بالاستعلام select * from على جدولي products و members. وبما أن لدينا 3 محلات، فسنستعلم 3 مرات على جدول المستخدمين، وثلاث مرات على جدول المنتجات. وفي النهاية، قمنا بتنفيذ الاستعلامات بعدد مرات قدره 3+3+1. تخيل الآن ما الذي يمكن أن يحدث لو كان لديك 5000 أو 10000 محل؟ سيكون لديك في تلك الحالة عشرة آلاف إلى عشرين ألف استعلام في كل مرة يقوم فيها أحد المستخدمين بزيارة الصفحة. وماذا لو كانت لديك عشرة آلاف أو مئة ألف زيارة كلّ أربع وعشرين ساعة؟ هذا كابوس! من الواضح الآن أن هذا التوجّه مدمّر للأداء. وبغض النظر عن نوع قاعدة البيانات التي تستخدمها، وعن مدى قوة الخادم الذي لديك، فستصل دائمًا إل تلك النقطة التي يقف فيها العتاد القوي لديك عاجزًا. يمكنك أن تحسّن الأداء بعمل cache لهذه الاستعلامات، باستخدام Redis على سبيل المثال. سيؤدي هذا الغرض، ولكن لبعض الوقت فقط. وبتلك الطريقة، أنت فقط تؤجل النهاية الحتميّة التي ستكلّفك الكثير من المال والوقت، وفي الغالب ستفقد بعض الزبائن، أو أنّ قاعدة بياناتك ستضعف كثيرًا. وهنا يأتي التحميل الحثيث لينقذك من هذه الورطة. استخدام التحميل الحثيث في Laravel بسيط للغاية. العلاقات التي ترغب أن يتم تحميلها بشكل حثيث تحددها في طريقة with كما يلي: $stores = Store::with('member','products')->get();الآن، بدل استخدام 7 استعلامات، قلّلنا باستخدام التحميل الحثيث عدد الاستعلامات إلى 3 فقط: وستكون ثلاثة استعلامات حتى ولو كانت لديك عشرة آلاف مدخلة في جدول المحلات. وكما ترى، فإن الاستخدام السليم للتحميل الحثيث يمكن أن يؤدي إلى تحسين أداء تطبيقك بقدر هائل. ولكي نحصل على تحسن للأداء بالفعل، فعلينا أن نوجد فهرسًا لحقل الهويّة id في جدولي members و products. ومع وجود كمّ هائل من السجلات، فإن تنفيذ (... ,'in( '1', '2 على حقل غير مفهرس سيأخذ وقتًا طويلًا. وبعد هذه المقدمة عن التحميل الحثيث، هيا بنا نرى كيف يمكننا أن نستخدم العلاقات مع المستودعات. تمديد فئة المستودعسأريك طريقة واحدة يمكنك فيها أن تستخدم العلاقات في فئات مستودعات concrete. وهنا مثال عن النتيجة النهائيّة: function __construct(StoreRepository $stores) { $this->stores = $stores; } public function index() { $stores = $this->stores->with('member', 'products')->all(); .... }وكما ترى هنا، لدينا طريقة with يمكنك أن تسلسل فيها نموذج العلاقات. ستكون هذه الطريقة شبيهة بطريقة with في Laravel’s Query Builder. public function with($relations) { if (is_string($relations)) $relations = func_get_args(); $this->with = $relations; return $this; }نحتاج الآن لأن نربط كلّ علاقة من العلاقات التي قمنا بتقديمها بالنموذج: protected function eagerLoadRelations() { if(!is_null($this->with)) { foreach ($this->with as $relation) { $this->model->with($relation); } } return $this; }وها هو ذا، والشيء الوحيد الذي تبقّى هو أن نحدّث طريقة مستودع ()all (وأي شيء آخر ترغب بتحديثه) لاستخدام التحميل الحثيث: public function all($columns = array('*')) { $this->applyCriteria(); $this->newQuery()->eagerLoadRelations(); return $this->model->get($columns); }وكما سبق وذكرت، فيمكنك أن تضيف عدّة علاقات ضمن طريقة ()with. وفيما يلي مثال على StoresControler: <?php namespace App\Http\Controllers; use App\Repositories\StoreRepository; class StoresController extends Controller { protected $stores; function __construct(StoreRepository $stores) { $this->stores = $stores; } public function index() { $stores = $this->stores->with('member', 'products')->all(); return \View::make('stores.index')->with('stores', $stores); } }وفي العرض يمكنك أن تعرض البيانات بالطريقة التي تريدها، ولغرض التجربة يكفي هذا: @foreach($stores as $store) <h1>{{ $store->name }}</h1> <span>Owner: {{ $store->member->first_name . ' ' . $store->member->last_name }}</span><br> <h2>Products:</h2> @foreach($store->products as $product) <h3>{{$product->name}}</h3> <span>{{$product->short_desc}}</span><br/><br/> <span>Price: {{$product->price}}</span><br/> <?php Debugbar::info('Product displayed'); ?> @endforeach <br/>========================<br/> @endforeachوكما هو متوقع، لدينا الآن هذه الاستعلامات الثلاثة فقط: الخلاصةيمكنك باستخدام التحميل الحثيث أن تحسّن أداء تطبيقك. وأحيانًا، عندما يكبر التطبيق، حتى التحميل الحثيث ليس كافيًا للحفاظ على أعلى أداء. في الدرس التالي سأريك كيف يمكنك تجميل مستودعاتك لتقوم بعمل cache للاستعلامات من أجل أداء أفضل. ترجمة -وبتصرف- للمقال: Using Repository Pattern in Laravel 5 - Eloquent Relations and Eager Loading.
- 1 تعليق
-
- 1
-
- علاقات
- قاعدة بيانات
- (و 10 أكثر)
-
أنشأنا في الدرس السابق بنية قاعدة البيانات الخاصة بمشروع Larashop. نكمل في هذا الدرس حديثنا عن قواعد البيانات في Laravel بشرح كيفية إدراج تسجيلات في قواعد البيانات وكيفية استخراجها منها باستخدام إطار العمل Eloquent. هذا الدرس جزء من سلسلة تعلم Laravel والتي تنتهج مبدأ "أفضل وسيلة للتعلم هي الممارسة"، حيث ستكون ممارستنا عبارة عن إنشاء تطبيق ويب للتسوق مع ميزة سلة المشتريات. يتكون فهرس السلسلة من التالي: مدخل إلى Laravel 5.تثبيت Laravel وإعداده على كلّ من Windows وUbuntu.أساسيات بناء تطبيق باستخدام Laravel.إنشاء روابط محسنة لمحركات البحث (SEO) في إطار عمل Laravel.نظام Blade للقوالب.تهجير قواعد البيانات في Laravel. استخدام Eloquent ORM لإدخال البيانات في قاعدة البيانات، تحديثها أو حذفها. (هذا الدرس)إنشاء سلة مشتريات في Laravel.الاستيثاق في Laravel.إنشاء واجهة لبرمجة التطبيقات API في Laravel.إنشاء مدوّنة باستخدام Laravel.استخدام AngularJS واجهةً أمامية Front end لتطبيق Laravel.الدوّال المساعدة المخصّصة في Laravel.استخدام مكتبة Faker في تطبيق Laravel لتوليد بيانات وهمية قصدَ الاختبار. سنغطي في هذا الدرس المواضيع التالية: نماذج Eloquentأعراف التسمية.أسماء الجداول والمفاتيح الخارجية.الأختام الزمنية.إطار العمل Eloquentقراءة البيانات READ.تحديث البيانات UPDATE.حذف البيانات DELETE.إدراج البيانات INSERT.تنفيذ استعلامات SQL في Laravel.نماذج Eloquent لمشروع Larashop.استخدام النماذج في المتحكمات.إظهار بيانات النماذج في العروض.إطار عمل Eloquentيأتي Laravel مضمنًّا بإطار عمل Eloquent الذي يُستخدَم للتخاطب مع قاعدة البيانات وتنفيذ عمليات مثل الإدراج Insert، التحديث Update أو الحذف Delete على الجداول. Eloquent هو إطار عمل لربط كائنات التطبيق بعلاقات (جداول) قاعدة البيانات Object Relational Mapper, ORM. يقوم مبدأ ربط العلاقات بالكائنات على تنفيذ نمط ActiveRecord (التسجيلة النشطة)؛ الذي هو وسيلة للوصول إلى البيانات في قاعدة البيانات، حيث يضمن جدول بيانات (أو علاقة في قاعدة البيانات بصفة عامة) في صنف Class وتُربط كل تسجيلة من جدول البيانات بكائن Object من الصنف. بهذه الطريقة يُصبح التعامل مع الصنف مماثلا للتعامل مع الجدول في قاعدة البيانات. مثلا، لإدراج تسجيلة جديدة في جدول قاعدة البيانات ننشئ - في التطبيق - كائنا جديدا من الصنف المربوط بالجدول. وإذا أردنا أن نحدّث بيانات تسجيلة من الجدول نحدّث خاصيّات الكائن المربوط بها. نماذج Eloquentتُستخدَم النماذج في بنية MVC للتفاعل مع مصادر البيانات (قواعد البيانات، ملفات نصية، … إلخ). تُعرَّف النماذج في Laravel بتمديد الصنف Illuminate\Database\Eloquent\Model الذي يوفّر دوال جاهزة للاستخدام من أجل التفاعل مع مصدر البيانات. أعراف التسمية في Eloquentتوجد بعض الأعراف التي يفترض إطار العمل Eloquent مبدئيا اتّباعها، مع وجود إمكانية لتغييرها حسب الرغبة. في العرف أن اسم النموذج يكون كلمة مفردة تبدأ بحرف كبير Upper case، بينما اسم الجدول في قاعدة البيانات كلمة للجمع بأحرف صغيرة Lower case. يعتمد Laravel هذا العرف فيربط تلقائيا بين النموذج ذي الاسم المفرد والجدول الذي يكون اسمه جمعا لاسم النموذج. نستخدم أداة Artisan لإنشاء نموذج لجدول التصنيفات categories باسم Category (مفرد categories): php artisan make:model Categoryينشئ الأمر ملفا للنموذج على المسار app/Category.php. نفتح الملف لرؤية محتواه: <?php namespace App; use Illuminate\Database\Eloquent\Model; class Category extends Model { // }نعرف فضاء الأسماء الذي يتبع له النموذج: ;namespace App.نستورد فضاء الأسماء الخاص بـ Eloquent بالتعليمة: use Illuminate\Database\Eloquent\Model;.يمدّد الصنفُ Category صنفَ نماذج Eloquent الذي تحدثنا عنه أعلاه: class Category extends Model.من المتعارف عليه أيضا أن حقل المفتاح الرئيس للجدول يُسمّى id، ومن الممكن مثل ما هو الحال مع اسم الجدول، تحديدُ اسم مغاير لحقل المفتح الرئيس. يمكن تحديد اسم الجدول في النموذج بغض النظر عن المتعارف عليه بإعطاء قيمة للمتغير table$، نفس الشيء بالنسبة للمفتاح الرئيس مع المتغير primaryKey$: protected $primaryKey = 'id'; protected $table = 'categories';تسجيلات الأختام الزمنيةيفترض Laravel إضافةَ الحقلين created_at وupdated_at إلى جداول قاعدة البيانات. تُدرج قيمتا الحقلين عند إنشاء تسجيلة جديدة في الجدول، وتحدّث قيمة updated_at عند تحديث قيمة التسجيلة. إن لم تضف هذين الحقلين في جداول قاعدة البيانات فيمكن تعطيل الإعداد المبدئي على النحو التالي: public $timestamps = false;استخدام Eloquentنكمل كتابة الشفرة المصدرية للنموذج Category ليصبح كما يلي: <?php namespace App; class Category extends Model { protected $primaryKey = 'id'; protected $table = 'categories'; }عرّفنا حقل المفتاح الرئيس للجدول: ;'$primaryKey = 'id. ليس هذا ضروريا هنا ما دام اسم الحقل يوافق العُرْف (id). نفس الملحوظة تنطبق على تعريف اسم الجدول في التعليمة الموالية.قراءة محتوى جدول في قاعدة البياناتسنرى، في هذه الفقرة، كيفية العثور على جميع التصنيفات التي نحتفظ بها في جدول التصنيفات. ملحوظة: نطبع النتائج في الأمثلة أدناه مباشرة من الشيفرة المصدرية للمسار دون الاستعانة بالمتحكم رغم أن ذلك مخالف لمبدأ MVC، إلا أن الغرض هنا هو رؤية عمل Eloquent. سنعود لترتيب الأمور في ما بعد. افتح ملف المسارات routes.php وأضف المسار التالي: Route::get('/read', function() { $category = new App\Category(); $data = $category->all(array('name','id')); foreach ($data as $list) { echo $list->id . ' ' . $list->name . '</br>'; } }); ننشئ كائنا جديدا من صنف النموذج: $category = new App\Category();التعليمة التالية تستدعي الدالة all في الكائن الذي أنشأناه للتو، وتمرر له مصفوفة تحدد الحقول التي نود الحصول عليها؛ في حالتنا حدّدنا الحقلين id (المعرِّف) وname (اسم التصنيف). إن لم تُحدَّد معطيات للمصفوفة فستُرجع الدالة جميع الحقول. نستخدم حلقة تكرارية foreach لإظهار النتائج التي تحصلنا عليها.احفظ التعديلات ثم افتح الرابط http://larashop.dev/read في المتصفح. ستحصُل على لائحة شبيهة بالتالي (قد تختلف النتيجة لديك قليلا): 5 CLOTHING 4 FASHION 3 KIDS 1 MEN 2 WOMENتحديث تسجيلاتسنحدّث في هذه الفقرة تسجيلة في جدول التصنيفات باستخدام المعرِّف id. نختار معرّف إحدى التصنيفات الظاهرة في نتيجة المثال السابق، مثلا التصنيفKIDS ذو المعرِّف 3. نعيد فتح ملف المسارات routes.php ونضيف مسارا جديدا كما يلي: Route::get('/update', function() { $category = App\Category::find(4); $category->name = 'KIDS 2'; $category->save(); $data = $category->all(array('name','id')); foreach ($data as $list) { echo $list->id . ' ' . $list->name . '</br>'; } });نستدعي الدالة find الموجودة في النموذج مع تمرير معرّف التصنيف إليها. ترجع الدالة كائنا من صنف Category يحوي بيانات التصنيف المستقاة من تسجيلة في الجدول categories.نعيّن الاسم الجديد للتصنيف بالتعديل على الخاصية name في الكائن category$.لتُعتمَد التعديلات وتخزَّن في السجل نستدعي الدالة save من الكائن category$.نستخدم حلقة تكرارية foreach لإظهار النتائج التي تحصلنا عليها.احفظ الملف ثم افتح الرابط http://larashop.dev/update في المتصفح. لاحظ تغيّر اسم التصنيف من KIDS إلى KIDS 2. حذف تسجيلة من جدول في قاعدة البياناتنختار أحد التصنيفات لحذفه (مثلا، التصنيف CLOTHING ذو المعرف 5). افتح ملف المسارات routes.php وأضف المسار التالي: Route::get('/delete', function() { $category = App\Category::find(5); $category->delete(); $data = $category->all(array('name','id')); foreach ($data as $list) { echo $list->id . ' ' . $list->name . '</br>'; } }); يوجد شبه كبير بين تحديث تسجيلة وحذفها. الفرق هو أنه بعد إيجاد التصنيف (الدالة find) نستدعي الدالة delete في الكائن category$ لحذف التسجيلة المربوطة به. افتح الرابط http://larashop.dev/delete ولاحظ أن التصنيف لم يعد موجودا. إدراج تسجيلةلإدراج تسجيلة جديدة في جدول قاعدة البيانات ننشئ كائنا جديدا من صنف Category ونعيّن خواصّه ثم نخزنه في الجدول، كما يلي: Route::get('/insert', function() { $category= new App\Category; $category->name='Music'; $category->save(); return 'category added'; }); بالذهاب إلى الرابط http://larashop.dev/insert تظهر رسالة category added دلالةً على إضافة التصنيف. يمكن أيضا التحقق من إضافة التصنيف من سطر أوامر MySQL أو بالذهاب إلى الرابط http://larashop.dev/read. توجد طريقة أخرى لاستخدام سطر برمجي واحد لإدراج تصنيف في تسجيلة. نستخدم لهذا الغرض الدالة create من نموذج التصنيف Category؛ ولكن يجب قبل ذلك التعديل على النموذج Category لتمكين الإسناد الشامل Mass assignment (تحديد قيم معطيات عدّة مرة واحدة). نفتح ملف النموذج لتحريره ثم نضيف السطر التالي: protected $fillable = array('name', 'created_at_ip', 'updated_at_ip'); يعرف المتغير fillable$ مصفوفة بالحقول التي يمكن تعيين قيمها دفعة واحدة. احفظ ملف النموذج ثم افتح ملف المسارات routes.php وعدل المسار insert/ ليصبح التالي: Route::get('/insert', function() { App\Category::create(array('name' => 'New Music')); return 'category added'; }); نستدعي الدالة create من الصنف Category ونمرر لها مصفوفة بحقول التسجيلة الجديدة مع قيمها. ينبغي أن تكون الحقول الممررة قابلة للإسناد الشامل، أي مذكورة في المتغير fillable$. لاحظ أن إنشاء الكائن وتعيين قيم خاصياته ثم تخزين محتوياته في قاعدة البيانات كل هذا تم من خلال استدعاء الدالة create. ملحوظة 1: عند طلب الرابط http://larashop.dev/insert في المرة الأولى تظهر الرسالة category added ولكن عند طلب نفس الرابط مرة أخرى دون تعديل المسار تظهر صفحة بيضاء دلالة على عدم إدراج التسجيلة. يعود السبب في ذلك إلى أننا أثناء تعريف الجدول categories في الدرس السابق علّمنا الحقل name بالدالة unique أي أنه لا يمكن لتسجيلتين من هذا الجدول أن يكون لهما نفس الاسم. ملحوظة 2: الإسناد الشامل غير ممكّن مبدئيا لأسباب أمنية ويجب عند تمكينه اختيار حقول fillable$ بعناية حتى لا تمثل خطرا أمنيا. يجب دائما تطهير Sanitize البيانات التي يستقيها التطبيق من المستخدم. تنفيذ استعلامات SQL مباشرةقد ترغب، لسبب أو آخر، في التعامل المباشر مع قاعدة البيانات باستخدام استعلامات SQL؛ يوفر Laravel صنف DB لهذا الغرض. استعلامات SELECTتُستخدَم دالة select لتنفيذ استعلامات القراءة من قاعدة البيانات على النحو التالي: $category = DB::select('SELECT name FROM categories WHERE id = ?', [1]);تأخذ الدالة DB::select معطيين: الأول هو استعلام القراءة المراد تنفيذه، والثاني معطيات نود استخدامها في الاستعلام. بالنسبة للمعطيات المراد استخدامها في الاستعلام فتُحدّد أماكنها بعلامة ? وتكون قيمتها في مصفوفة تمرّر في المعطى الثاني للدالة DB::select. ينفذ السطر أعلاه استعلام SQL التالي: SELECT name FROM categories WHERE id = 1;نفرض أننا نريد تنفيذ استعلام SQL التالي: SELECT name FROM categories WHERE id BETWEEN 1 AND 4;يطلب الاستعلام قيمتين (1 و4). يُترجم الاستعلام في النموذج على النحو التالي: $category = DB::select('SELECT name FROM categories WHERE id BETWEEN ? AND ?', [1,4]);تُبدل أول علامة ? في الاستعلام بأول عنصر من المصفوفة في معطى الدالة الثاني، وعلامة ? الثانية بالمعطى الموالي وهكذا. يمكن أيضا استخدامُ متغيرات بدلا من ? على النحو التالي: $category = DB::select('SELECT name FROM categories WHERE id BETWEEN :id1 AND :id2', ['id1' => 1, 'id2' => 4]);لاحظ استخدام : قبل أسماء المتغيرات في استعلام SQL. استعلامات INSERTتُستخدم دالة DB::insert لإدراج تسجيلات في الجدول بنفس طريقة استخدام DB::select للقراءة منه: $inserted=DB::insert('INSERT INTO categories (name) VALUES (?)', ['TEST']);ترجع الدالة DB::insert عدد التسجيلات التي أدرجها الاستعلام. استعلامات UPDATEلتحديث تسجيلة في جدول بقاعدة البيانات نستخدم الدالة DB::update: $affected = DB::update('UPDATE categories SET name = 'TEST UPDATE' WHERE name = ?', ['TEST']);ترجع الدالة DB::update عدد التسجيلات التي حدثها الاستعلام. استعلامات DELETEتوفر الدالة DB::delete إمكانية تنفيذ استعلامات DELETE على النحو التالي: $deleted = DB::delete('DELETE FROM categories WHERE id = ?', [13]);ترجع الدالة DB::delete عدد التسجيلات التي حذفها الاستعلام. استعلامات عامةبالنسبة للاستعلامات التي لا ترجع أية قيمة فيمكن استخدام الدالة DB::statement: DB::statement('drop tables drinks');نماذج Eloquent لمشروع Larashopنشرع الآن بعد أن رأينا آلية عمل Eloquent ببناء بقية نماذج مشروع Larashop. نفذ الأوامر التالية لإنشاء نماذج للعلامات التجارية، المنتجات ومنشورات المدونة على التوالي: php artisan make:model Brand php artisan make:model Product php artisan make:model Postنعرّف في كل ملف نموذج حقل المفتاح الرئيس، اسم الجدول وأسماء الحقول المسموح بإسنادها fillable. ملف Brand.php: <?php namespace App; class Brand extends Model { protected $primaryKey = 'id'; protected $table = 'brands'; protected $fillable = array('name', 'created_at_ip', 'updated_at_ip'); } ملف Product.php: <?php namespace App; class Product extends Model { protected $primaryKey = 'id'; protected $table = 'products'; protected $fillable = array('name', 'title', 'description','price','category_id','brand_id','created_at_ip', 'updated_at_ip'); } ملف Post.php: <?php namespace App; class Post extends Model { protected $primaryKey = 'id'; protected $table = 'posts'; protected $fillable = array('url', 'title', 'description','content','blog','created_at_ip', 'updated_at_ip'); } استخدام النماذج في المتحكماتتقضي بنية MVC وعادات البرمجة الصحيحة أن تكون المتحكمات هي من يتعامل مع النماذج. سنعدّ متحكم المشروع Front.php للعثور على البيانات من النماذج وتمريرها إلى العروض لتظهر لدى المتصفح. عدّل ملف Front.php كالتالي: <?php namespace App\Http\Controllers; use App\Brand; use App\Category; use App\Product; use App\Http\Controllers\Controller; class Front extends Controller { var $brands; var $categories; var $products; var $title; var $description; public function __construct() { $this->brands = Brand::all(array('name')); $this->categories = Category::all(array('name')); $this->products = Product::all(array('id','name','price')); } public function index() { return view('home', array('title' => 'Welcome','description' => '','page' => 'home', 'brands' => $this->brands, 'categories' => $this->categories, 'products' => $this->products)); } public function products() { return view('products', array('title' => 'Products Listing','description' => '','page' => 'products', 'brands' => $this->brands, 'categories' => $this->categories, 'products' => $this->products)); } public function product_details($id) { $product = Product::find($id); return view('product_details', array('product' => $product, 'title' => $product->name,'description' => '','page' => 'products', 'brands' => $this->brands, 'categories' => $this->categories, 'products' => $this->products)); } public function product_categories($name) { return view('products', array('title' => 'Welcome','description' => '','page' => 'products', 'brands' => $this->brands, 'categories' => $this->categories, 'products' => $this->products)); } public function product_brands($name, $category = null) { return view('products', array('title' => 'Welcome','description' => '','page' => 'products', 'brands' => $this->brands, 'categories' => $this->categories, 'products' => $this->products)); } public function blog() { return view('blog', array('title' => 'Welcome','description' => '','page' => 'blog', 'brands' => $this->brands, 'categories' => $this->categories, 'products' => $this->products)); } public function blog_post($id) { return view('blog_post', array('title' => 'Welcome','description' => '','page' => 'blog', 'brands' => $this->brands, 'categories' => $this->categories, 'products' => $this->products)); } public function contact_us() { return view('contact_us', array('title' => 'Welcome','description' => '','page' => 'contact_us')); } public function login() { return view('login', array('title' => 'Welcome','description' => '','page' => 'home')); } public function logout() { return view('login', array('title' => 'Welcome','description' => '','page' => 'home')); } public function cart() { return view('cart', array('title' => 'Welcome','description' => '','page' => 'home')); } public function checkout() { return view('checkout', array('title' => 'Welcome','description' => '','page' => 'home')); } public function search($query) { return view('products', array('title' => 'Welcome','description' => '','page' => 'products')); } } في المتحكم: نستورد النماذج: use App\Brand; use App\Category; use App\Product; نعرف متغيرات لتحميل بيانات النماذج: var $brands; var $categories; var $products; يعرف المتغيران title$ وdescription$ على التوالي عنوانا ووصفا لأغراض التحسين لمحركات البحث. نُعرّف باني Constructor صنف المتحكم: public function __construct(){…}تحمّل هذه الدالة البيانات من النماذج إلى المتغيرات المعرّفة سابقا. نحمّل في الدالة index العرض home مع تمرير مصفوفة معطيات تمثل بيانات النماذج مع بيانات أخرى لغرض التحسين لمحركات البحث.عرض بيانات النماذج في العروضنستخدم الحلقات التكرارية في قوالب Blade من أجل عرض بيانات النماذج التي مررها المتحكم إلى العروض. على سبيل المثال: @foreach ($brands as $brand) <li><a href='{{url("products/brands/$brand->name")}}'> <span class="pull-right">(50)</span>{{$brand->name}}</a></li> @endforeachتوجد عروض المشروع في الملف المرفق. خاتمةمن السهل إنشاء نماذج Eloquent واستخدامها؛ كل ما عليك فعله هو تمديد صنف Model والبدء باستخدام النموذج. يوفر Laravel أيضا إمكانية تخصيص استعلامات SQL دون المرور بـEloquent لمن يرغب في ذلك. ملف مرفق: عروض المشروع. ترجمة -وبتصرف- لمقال Laravel 5 Eloquent ORM لصاحبه Rodrick Kazembe.
-
تحتاج أغلب الشركات العاملة على الشبكة للانطلاق في التسويق بالمحتوى. يُعرَّف التسويق بالمحتوى بأنه "مقاربة تسويق ترتكز على إنتاج محتوى جيد، مناسب وذي قيمة ثم توزيعه من أجل جذب فئة محدّدة بدقة والحفاظ عليها، وفي آخر المطاف الحصول على إجراء ذي مردود ربحي من الزبون". هذا الدرس جزء من سلسلة تعلم Laravel والتي تنتهج مبدأ "أفضل وسيلة للتعلم هي الممارسة"، حيث ستكون ممارستنا عبارة عن إنشاء تطبيق ويب للتسوق مع ميزة سلة المشتريات. يتكون فهرس السلسلة من التالي: مدخل إلى Laravel 5.تثبيت Laravel وإعداده على كلّ من Windows وUbuntu.أساسيات بناء تطبيق باستخدام Laravel.إنشاء روابط محسنة لمحركات البحث (SEO) في إطار عمل Laravel.نظام Blade للقوالب.تهجير قواعد البيانات في Laravel. استخدام Eloquent ORM لإدخال البيانات في قاعدة البيانات، تحديثها أو حذفها. إنشاء سلة مشتريات في Laravel.الاستيثاق في Laravel. إنشاء واجهة لبرمجة التطبيقات API في Laravel.إنشاء مدوّنة باستخدام Laravel. (هذا الدرس)استخدام AngularJS واجهةً أمامية Front end لتطبيق Laravel.الدوّال المساعدة المخصّصة في Laravel.استخدام مكتبة Faker في تطبيق Laravel لتوليد بيانات وهمية قصدَ الاختبار. نفرض مثلا أنك طورت مشروع Larashop لبيع الملابس على الشبكة. يقوم مبدأ التسويق عبر المحتوى على التأسيس لنفسك بوصفك خبيرا في المجال، فتكتُب - مثلا - عن الملابس وكيفية اختيارها والحفاظ عليها وتقدم نصائح لزوار موقعك؛ مما يرفع من احتمالات الشراء من متجرك، وهو ما يمثل قيمة تجارية للموقع. تعمل مدونات المؤسسات على تنفيذ هذا المبدأ فتنشر محتوى مناسبا لجمهورها مما يكون له الأثر الإيجابي على المؤسسة أو الشركة. لا يقتصر المحتوى المنشور في المدونة على المحتوى الكتابي بل يتعداه للمرئي مثل مقاطع الفيديو، الصوتيات والصور. يغطي الدرس المواضيع التالية: أهم خاصيات المدونةالمنشورالتصنيفاتالوسوم Tagsعوامل التحسين لمحركات البحثعنوان المنشوروصف Metaدور الشبكات الاجتماعيةتهجيرات قاعدة البيانات الخاصة بمدونة Larashop.خصائص المدونةنذكر في هذه الفقرة الخصائص التي نريد تواجدها في مدونتنا. المنشورات Postsيتمثل المحتوى الأساسي للمدونة في المنشورات، إذ تحوي المعلومات التي نريد نشرها. سنحصُل على المنشورات من قاعدة البيانات وسنستخدم HTML لتهيئتها. التصنيفاتتُستخدَم التصنيفات لتجميع المنشورات ذات القاسم المشترك. مثلا يمكن أن تنشئ تصنيفا لملابس الرجال، آخر للملابس النسائية وثالث لملابس الأطفال وهكذا. الوسومتشبه الوسوم التصنيفات، إلا أنها أكثر تخصيصا. يمكنك مثلا إنشاء وسم للملابس الشتوية ثم وضعه على جميع المنشورات بغض النظر عن تصنيفها. يمكن للمنشور الواحد أن يرتبط بأكثر من وسم؛ يمكن أن تضيف للمنشور وسما بنوعية القماش المستخدم، وآخر للفصل المناسب لارتدائه. عوامل التحسين لمحركات البحثلا نريد لمنشوراتنا أن تظهر في آخر صفحة من مليون نتيجة في محركات البحث، سيكون جيدا أن نظهر في الصفحة الأولى لذا يجب الاهتمام بالتحسين لمحركات البحث، راجع مقال إنشاء روابط محسنة لمحركات البحث (SEO) في إطار عمل Laravel 5. بالنسبة لمدونتنا فسنهتم بالعاملين: عنوان المنشور، وهو العنوان الذي سيظهر في محركات البحث، يُستحسن ألا يتعدى 56 محرفا.وصف Meta، يظهر تحت العنوان في نتيجة محركات البحث. من الأفضل ألا يتعدى 160 محرفا.التهجيرات الخاصة بمنشورات مدونة Larashopحان الآن وقت التنفيذ. في ما يلي جداول قاعدة البيانات التي نحتاج لإنشائها للمدونة. الحقول التالية مشتركة بين جميع الجداول التسلسل الحقل نوع البيانات الوصف1created_atTimestampختم زمني لوقت إنشاء التسجيلة2updated_atTimestampختم زمني لوقت تحديث التسجيلةجدول تصنيفات المدونةالتسلسل الحقل نوع البيانات الوصف1idINTمعرّف التصنيف، مفتاح رئيس2categoryVARCHARاسم التصنيفجدول وسوم المدونةالتسلسل الحقل نوع البيانات الوصف1idINTمعرّف الوسم مفتاح رئيس2tagVARCHARاسم الوسمجدول الوسوم والمنشورات: بما أنه يمكن أن يكون للمنشور أكثر من وسم، فيجب إنشاء جدول خاص للربط بين الوسم والمنشور.التسلسل الحقل نوع البيانات الوصف1idINTمعرّف الحقل، مفتاح رئيس2post_idINTمفتاح خارجي إلى معرّف المنشور3tag_idINTمفتاح خارجي إلى معرّف الوسمجدول المنشوراتالتسلسل الحقل نوع البيانات الوصف1idINTمعرّف المنشور، مفتاح رئيس2urlVarchar(255)رابط المنشور3titleVarchar(140)عنوان المنشور4descriptionVarchar(170)وصف المنشور5contentTextمحتوى المنشور6blogTinyint(1)يحدد طبيعة المنشور7category_idINTمعرّف تصنيف المنشور، مفتاح خارجي8imageVarchar(255)رابط صورة المنشورملف التهجير الخاص بمنشورات المدونةأنشأنا في درس تهجير قواعد البيانات فيLaravel 5 ملف التهجير الخاص بالمنشورات إلا أننا لم نضف معرف التصنيف category_id ولا صورة المنشور image؛ سننشئ في هذا الدرس ملف تهجير لإضافة هذين الحقلين. نفذ الأمر التالي في مجلد التطبيق: php artisan make:migration add_category_id_image_to_posts --table=postsافتح ملف التهجير المنشأ بالأمر السابق وعدله ليصبح كالتالي: <?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class AddCategoryIdImageToPosts extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('posts', function (Blueprint $table) { $table->string('image')->nullable()->after('content'); $table->unsignedInteger('category_id')->nullable()->after('blog'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('posts', function (Blueprint $table) { $table->dropColumn('image'); $table->dropColumn('category_id'); }); } } نضيف حقلا جديدا لصورة المنشور، يُضاف الحقل بعد حقل المحتوى content ويمكن أن يكون فارغا: $table->string('image')->nullable()->after('content');نضيف أيضا حقلا لمعرِّف التصنيف بعد حقل blog $table->unsignedInteger('category_id')->nullable()->after('blog');نفذ الأمر التالي لشغيل ملف التهجير وإضافة الحقول: php artisan migrateملف التهجير الخاص بتصنيفات المدونةننتقل الآن لملف التهجير الذي سيتولى إنشاء جدول تصنيفات المنشورات. سنستخدم هذا الملف أيضا لإدراج تسجيلات في الجدول، كل تسجيلة تمثل تصنيفا: php artisan make:migration blog_categoriesعدل ملف التهجير كالتالي: <?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class BlogCategories extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('blog_categories', function (Blueprint $table) { $table->increments('id'); $table->string('category')->unique(); $table->timestamps(); }); DB::table('blog_categories')->insert([ 'category' => "WOMEN" ]); DB::table('blog_categories')->insert([ 'category' => "MEN" ]); DB::table('blog_categories')->insert([ 'category' => "KIDS" ]); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('blog_categories'); } } ملف التهجير الخاص بالوسومالخطوة التالية هي إنشاء تهجير لوسوم المدونة: php artisan make:migration blog_tagsعدل ملف التهجير كالتالي: <?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class BlogTags extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('blog_tags', function (Blueprint $table) { $table->increments('id'); $table->string('tag')->unique(); $table->timestamps(); }); DB::table('blog_tags')->insert([ 'tag' => "Pink" ]); DB::table('blog_tags')->insert([ 'tag' => "T-Shirt" ]); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('blog_tags'); } }ملف التهجير الخاص بربط المنشورات والوسومآخر ملف من ملفات التهجير هو الملف الخاص بجدول الوسوم-المنشورات. ننشئه بالأمر التالي: php artisan make:migration blog_post_tagsعدّل الملف على النحو التالي: <?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class BlogPostTags extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('blog_post_tags', function (Blueprint $table) { $table->increments('id'); $table->unsignedInteger('post_id'); $table->unsignedInteger('tag_id'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('blog_post_tags'); } }أنشأنا ملفات التهجير، ننفذ أمر migrate لتطبيق التهجيرات: php artisan migrateملحوظة: يجب دائما البدء بتهجيرات الجداول التي لا تحتاج لجداول أخرى، بمعنى أنه لا توجد بها مفاتيح خارجية Foreign keys. لتهجير جدول به مفتاح خارجي لجدول آخر يجب أن يكون هذا الجدول الأخير قد تم إنشاؤه. بذر جدول منشورات المدونةسنستخدم مكتبة Faker لإضافة منشورات وهمية نختبر بها مدونتنا. نفذ الأمر التالي لإنشاء ملف بذر لمنشورات المدونة: php artisan make:seeder BlogPostsTableSeederافتح الملف database/seeds/BlogPostsTableSeeder.php وعدله ليصبح كما يلي: <?php use Illuminate\Database\Seeder; class BlogPostsTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $faker = Faker\Factory::create(); for ($i = 0; $i < 10; $i++){ DB::table('posts')->insert([ //, 'url' => $faker->unique()->word, 'title' => $faker->unique()->sentence($nbWords = 6), 'description' => $faker->paragraph($nbSentences = 3), 'content' => $faker->text, 'image' => $faker->randomElement($array = array ('blog-one.jpg','blog-two.jpg','blog-three.jpg')), 'blog' => '1', 'category_id' => $faker->numberBetween($min = 1, $max = 3), ]); } } }نولد كلمة عشوائية فريدة لاستخدامها عنوانا للمنشور: 'url' => $faker->unique()->wordنختار صورة عشوائية من بين ثلاث صور لاستخدامها صورة للمنشور: 'image' => $faker->randomElement($array = array ('blog-one.jpg','blog-two.jpg','blog-three.jpg'))أنشأنا في تهجير تصنيفات المدونة ثلاثة تصنيفات، لذا سيقتصر اختيار معرفات التصنيفات على المجال [1,3]: 'category_id' => $faker->numberBetween($min = 1, $max = 3),سنستخدم الحقل created_at لعرض تاريخ المنشور في المدونة: 'created_at' => $faker->dateTime($max = 'now'),نفذ أمر artisan التالي لتطبيق البذر: php artisan db:seed --class=BlogPostsTableSeederبذر جدول الوسوم-المنشوراتننتقل الآن لبذر الجدول blog_post_tags الذي يُستخدَم لربط المنشورات بالوسوم. php artisan make:seeder BlogPostTagsTableSeederافتح ملف البذر وعدله كالتالي: <?php use Illuminate\Database\Seeder; class BlogPostTagsTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $faker = Faker\Factory::create(); for ($i = 1; $i < 11; $i++){ DB::table('blog_post_tags')->insert([ //, 'post_id' => $i, 'tag_id' => $faker->numberBetween($min = 1, $max = 2), ]); } } }أدرجنا عند بذر جدول الوسوم BlogPostsTableSeeder.php عشر تسجيلات فقط، لذا حرصنا ألا تتعدى قيمة معرف المنشور post_id في جدول الوسوم-المنشورات هذا الحد: for ($i = 1; $i < 11; $i++)بالنسبة لمعرّف الوسوم فقد حددنا المجال بـ[1,2] لأننا أثناء تهجير الوسوم أضفنا وسمين في جدول قاعدة البيانات. نفذ الأمر التالي لتطبيق بذر جدول الوسوم-المنشورات: php artisan db:seed --class=BlogPostTagsTableSeederنماذج المدونةننتقل بعد تنفيذ التهجيرات إلى إنشاء نماذج المدونة. سننشئ النماذج التالية: نموذج المنشور Post وسيكون مسؤولا عن التفاعل مع جدول المنشورات posts.نموذج تصنيف المدونة BlogCategory وهو مسؤول عن التفاعل مع جدول تصنيفات المدونة blog_categories.نموذج وسم المدونة BlogTag ويُعنى بالتخاطب مع جدول وسوم المدونة blog_tags.نموذج وسم منشورات المدونة BlogPostTag ويتفاعل مع جدول وسوم منشورات المدونة blog_post_tags.نفذ الأوامر التالية لإنشاء النماذج: php artisan make:model Post php artisan make:model BlogCategory php artisan make:model BlogTag php artisan make:model BlogPostTagلم نضف أمر إنشاء نموذج لمنشور المدونة لأننا أنشأناه خلال درس Eloquent من هذه السلسة. نبدأ بالتعديل على النماذج. نموذج منشور المدونةسنضيف إلى مدونتنا إمكانية الانتقال إلى المنشور السابق أو التالي؛ نستخدم معرّف المنشور لتحديد المنشور السابق و التالي. ننشئ دالتين لهذا الغرض: prevBlogPostURL (المنشور السابق) وnextBlogPostURL (المنشور التالي). <?php namespace App; use Illuminate\Database\Eloquent\Model; class Post extends Model { protected $primaryKey = 'id'; protected $table = 'posts'; protected $fillable = array('url', 'title', 'description','content','blog','created_at_ip', 'updated_at_ip'); public static function prevBlogPostUrl($id) { $blog = static::where('id', '<', $id)->orderBy('id', 'desc')->first(); return $blog ? $blog->url : '#'; } public static function nextBlogPostUrl($id) { $blog = static::where('id', '>', $id)->orderBy('id', 'asc')->first(); return $blog ? $blog->url : '#'; } public function tags() { return $this->belongsToMany('App\BlogTag','blog_post_tags','post_id','tag_id'); } }استخدمنا دالتي where وfirst، وهما دالّتان توفرهما نماذج Eloquent، للحصول على معرّفيْ المنشورين السابق والتالي. بالنسبة لـwhere في دالة prevBlogPostUrl استخدمنا عامل المقارنة > (أصغر من) للحصول على المنشورات ذات المعرف الأصغر من معرف المنشور الحالي ثم رتبناها تنازليا (معطى desc في orderBy) وأخذنا العنصر الأول first. $blog = static::where('id', '<', $id)->orderBy('id', 'desc')->first();نفس المبدأ في دالة nextBlogPostUrl مع استخدام عامل المقارنة أكبر من < والترتيب التصاعدي asc. إن أردنا ترجمة التعليمة باستعلامات SQL فسنحصُل على التالي (مثال مع منشور ذي معرّف 3): SELECT * FROM posts where id < 3 ORDER BY id LIMIT 1;العلاقة بين الوسوم والمنشورات هي من النوع متعدّد إلى متعدّد Many to many: يمكن أن يوجد أكثر من وسم على المنشور، كما يمكن لوسم أن يوجد على أكثر من منشور. تُنفّذ هذه العلاقة بإدخال جدول وسيط للربط بين الجدولين posts وblog_tags. استخدمنا دالة belongsToMany في Eloquent لتعريف علاقة من هذا النوع: $this->belongsToMany('App\BlogTag','blog_post_tags','post_id','tag_id');تأخذ الدالة أربعة معطيات. الأول اسم النموذج الذي يرتبط بالنموذج الحالي بهذه العلاقة (BlogTag)، الثاني اسم الجدول الوسيط (blog_post_tags)، الثالث المفتاح الخارجي في الجدول المصدر (جدول المنشورات إذا كنا في نموذج المنشور) والرابع المفتاح الخارجي للجدول الوجهة (أي جدول الوسوم). تعريف الدالة ()tags واستخدام belongsToMany داخلها بالطريقة السالفة الذكر يجعل الحصول على وسوم منشور بسهولة كتابة: $tags = $post->tags استخدام هذه الدالة يكافئ تنفيذ استعلام SQL التالي: SELECT `blog_tags`.*, `blog_post_tags`.`post_id` AS `pivot_post_id`, `blog_post_tags`.`tag_id` AS `pivot_tag_id` FROM `blog_tags` INNER JOIN `blog_post_tags` ON `blog_tags`.`id` = `blog_post_tags`.`tag_id` WHERE `blog_post_tags`.`post_id` = ? ; قد يقنعك النظر في الاستعلام أعلاه بجدوى استخدام نماذج Eloquent. نموذج تصنيف المدونةنموذج التصنيفات سهل ولا يحتاج لأي شيء خاص: <?php namespace App; use Illuminate\Database\Eloquent\Model; class BlogCategory extends Model { protected $fillable = array('category'); }نموذج وسم المدونةتنطبق على نموذج الوسم علاقة متعدّد إلى متعدّد التي تنطبق على نموذج المنشور، لذا سنضيف دالة posts إلى النموذج لتعريف العلاقة بين جدولي الوسوم والمنشورات. <?php namespace App; use Illuminate\Database\Eloquent\Model; class BlogTag extends Model { protected $fillable = array('tag'); public function posts() { return $this->belongsToMany('App\Post','blog_post_tags','post_id','tag_id'); } }لاحظ استخدام belongsToMany بنفس طريقة استخدامها في نموذج المنشور. نموذج وسم منشورات المدونةيمثل هذا النموذج الجدول الوسيط blog_post_tags. <?php namespace App; class BlogPostTag extends Model { protected $fillable = array('post_id', 'tag_id'); }دوال المتحكم الخاصة بالمدونةتوجد في المتحكم Front دالتان تختصان بالمدونة: blog وblog_post. أضف السطر التالي لاستيراد نوذج المنشور Post إلى المتحكم: use App\Post;دالة Blogتُستخدَم هذه الدالة لعرض صفحة المدونة وإظهار جميع منشوراته. إذا كان عدد المنشورات كبيرا فسيكون من العبث عرضُها دفعةَ واحدة، لذا سنستخدم التّصفيح Pagination (عرض عدد محدود من المنشورات في كل صفحة). يدعم Eloquent إعداد الصفحات: public function blog() { $posts = Post::where('id', '>', 0)->paginate(3); $posts->setPath('blog'); $data['posts'] = $posts; return view('blog', array('data' => $data, 'title' => 'Latest Blog Posts', 'description' => '', 'page' => 'blog', 'brands' => $this->brands, 'categories' => $this->categories, 'products' => $this->products)); }نبحث عن منشورات المدونة ثم نُعِدّ صفحاتها، نحدّد عدد المنشورات في كل صفحة بثلاثة منشورات: $posts = Post::where('id', '>', 0)->paginate(3);نحدّد الرابط الخاص بإعداد الصفحات، اخترنا الرابط http://larashop.dev/blog: $posts->setPath('blog');ثم نحتفظ بالنتائج في المصفوفة data التي سنمررها إلى العرض. دالة Blog_postتتلقى هذه الدالة رابطا ثم تعثر على منشور اعتمادا على الرابط الممرّر إليها: public function blog_post($url) { $post = Post::where('url', '=' , $url)->first(); $previous_url = Post::prevBlogPostUrl($post->id); $next_url = Post::nextBlogPostUrl($post->id); $data['tags'] = $post->tags; $data['title'] = $post->title; $data['description'] = $post->description; $data['content'] = $post->content; $data['blog'] = $post->blog; $data['created_at'] = $post->created_at; $data['image'] = $post->image; $data['previous_url'] = $previous_url; $data['next_url'] = $next_url; return view('blog_post', array('data' => $data, 'page' => 'blog'));لاحظ استخدام الدالة tags التي عرفناها في النموذج Post. عروض المدونةتوجد عروض المدونة في الملف المرفق. يتعلق الأمر بالعرضين blog.blade.php و blog_post.blade.php الذي عُدّل عليهما لعرض البيانات الممرّرة. لا توجد تعليمات جديدة علينا في العرضين سوى التعليمة {!! $data['posts']->render() !!}تعمل هذه التعليمة على ترقيم الصفحات لتسهيل تصفح منشورات المدونة. تذكر أننا في الدالة blog كتبنا التعليمة التالية $posts = Post::where('id', '>', 0)->paginate(3);لإعداد الصفحات بحيث تُنشَر كل ثلاثة منشورات دفعة واحدة مع إتاحة التنقل إلى بقية المنشورات. تنشئ الدالة render في قالب Blade روابط من شكل http://laravel.dev/blog?page=X حيث X رقم الصفحة. بما أننا بذرنا عشرة منشورات فسيكون لدينا أربع صفحات (3 منشورات في كل من الصفحات 1 إلى 3، ومنشور واحد في الصفحة الرابعة، الأخيرة). الملف المرفق: عروض المدونة. ترجمة -وبتصرّف- لمقال Laravel 5 Blog Tutorial لصاحبه Rodrick Kazembe.