إنشاء مدونة باستخدام Laravel 5


محمد أحمد العيل

تحتاج أغلب الشركات العاملة على الشبكة للانطلاق في التسويق بالمحتوى. يُعرَّف التسويق بالمحتوى بأنه "مقاربة تسويق ترتكز على إنتاج محتوى جيد، مناسب وذي قيمة ثم توزيعه من أجل جذب فئة محدّدة بدقة والحفاظ عليها، وفي آخر المطاف الحصول على إجراء ذي مردود ربحي من الزبون".

هذا الدرس جزء من سلسلة تعلم Laravel والتي تنتهج مبدأ "أفضل وسيلة للتعلم هي الممارسة"، حيث ستكون ممارستنا عبارة عن إنشاء تطبيق ويب للتسوق مع ميزة سلة المشتريات. يتكون فهرس السلسلة من التالي:

laravel5-blog.thumb.png.3f1af7f8e2791040

نفرض مثلا أنك طورت مشروع 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.

01_eloquent_belongstomany.thumb.png.ec63

استخدمنا دالة 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، ومنشور واحد في الصفحة الرابعة، الأخيرة).

02_laravel_blog_pagination.thumb.png.1de

الملف المرفق:  عروض المدونة.

ترجمة -وبتصرّف- لمقال Laravel 5 Blog Tutorial لصاحبه Rodrick Kazembe.



1 شخص أعجب بهذا


تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن