اكتشف إطار PHP Laravel


الدرس: العلاقة n: n


الصفحة السابقة
في الفصل السابق ، رأينا علاقة النوع : n1 الأبسط والأكثر انتشارًا. سنقوم الآن بدراسة علاقة النوع n: n ، والتي يصعب فهمها وتنفيذها. سنرى أن Eloquent يبسط إدارة هذا النوع من العلاقات.
سأستكمل مثال المدونة الشخصية التي بدأتها في الفصل السابق بإضافة إمكانية إضافة كلمات رئيسية ( tags) إلى المقالات. هذا الفصل طويل بعض الشيء لكنني فضلت جمع كل شيء هنا.

المعطيات


h5> العلاقة n: n
تخيل وجود علاقة بين جدولين A و B مما يجعل من الممكن القول: 
  • يمكنني الحصول على صف من الجدول "A" فيما يتعلق بعدة صفوف من الجدول "" B ،
  • يمكنني الحصول على صف من الجدول B فيما يتعلق بعدة صفوف من الجدول A .
لم يتم حل هذه العلاقة كما رأينا في الفصل السابق باستخدام مفتاح خارجي بسيط في أحد الجداول. في الواقع ، نحن بحاجة إلى مفاتيح في الجدولين وعدة مفاتيح ، وهو أمر لا يمكن تحقيقه. يكمن الحل في إنشاء جدول وسيط (يُسمى جدول محوري) يُستخدم لتخزين المفاتيح الخارجية. فيما يلي مخطط لما سنحققه:
framework Laravel MVC
الجدول المحوري
 يحتوي الجدول المحوري post_tag  على مفاتيح الجدولين: 
  • post_id  لحفظ مفتاح جدول المنشورات ،
  • tag_id  لتخزين المفتاح إلى جدول العلامات.
وبهذه الطريقة ، يمكن أن يكون لدينا عدة سجلات مرتبطة بين الجدولين ، ويكفي كل مرة لتسجيل المفتاحين في الجدول المحوري. من الواضح أنه على مستوى الكود ، يتطلب الأمر بعض الإشراف لأنه يوجد جدول إضافي للإدارة.
حسب الاصطلاح ، يتكون اسم الجدول المحوري من اسمين للجداول في صيغة المفرد بالترتيب الأبجدي.
الترحيل
وسوف نستمر في استخدام الجداول   users و   posts رأيناهما في الفصول السابقة. سنقوم بإنشاء جدول جديد   tags يهدف إلى حفظ الكلمات الأساسية. ابدأ بحذف جميع الجداول من قاعدة البيانات الخاصة بك ، وإلا فقد تتعارض مع السجلات التي سنقوم بإنشائها.
احذف أيضًا جدول الترحيل migrations .
عادة يجب أن يكون لديك بالفعل عمليات ترحيل للجداول    users،   password_resets  و    posts . سنضيف الجدولين :   tags و    post_tag .
إنشاء ترحيل جديد للجدول   tags  :

php artisan make:migration create_tags_table
وأدخل هذا الكود:

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTagsTable extends Migration {

    public function up()
    {
        Schema::create('tags', function(Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
            $table->string('tag', 50)->unique();
            $table->string('tag_url', 60)->unique();
        });
    }

    public function down()
    {
        Schema::drop('tags');
    }
}
الحقول:
  • Id  : مفتاح فريد زيادة ،
  • created_at  و updated_at   التي أنشأتها  timestamps ،
  • tag  : الكلمة الرئيسية الفريدة تقتصر على 50 حرفًا ،
  • tag_url  : إصدار العلامة المراد تضمينها في عنوان url (بحد أقصى 60 كحد أقصى لتغطية الحالات غير المواتية) .
نحتاج إلى حقلين للعلامة ، في الواقع سيتعين علينا تمريره في عنوان url للبحث حسب العلامة ، أو يخاطر المستخدم بإدخال لهجات على سبيل المثال (أو ما هو أسوأ "/") ، لنقم بتحويل هذه الأحرف الخاصة إلى أحرف مناسبة لعناوين url .
إنشاء ترحيل جديد للجدول   post_tag  :

php artisan make:migration create_post_tag_table
وأدخل هذا الكود:

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePostTagTable extends Migration {

    public function up()
    {
        Schema::create('post_tag', function(Blueprint $table) {
            $table->increments('id');
            $table->integer('post_id')->unsigned();
            $table->integer('tag_id')->unsigned();
            $table->foreign('post_id')->references('id')->on('posts')
                        ->onDelete('restrict')
                        ->onUpdate('restrict');

            $table->foreign('tag_id')->references('id')->on('tags')
                        ->onDelete('restrict')
                        ->onUpdate('restrict');
        });
    }

    public function down()
    {
        Schema::table('post_tag', function(Blueprint $table) {
            $table->dropForeign('post_tag_post_id_foreign');
            $table->dropForeign('post_tag_tag_id_foreign');
        });

        Schema::drop('post_tag');
    }
}
الحقول :
  • post_id  : المفتاح الخارجي لجدول المنشورات ،
  • tag_id  : المفتاح الخارجي لجدول العلامات.
لقد قدمت أيضًا خيار "تقييد( restrict ) " للأعمال المثيرة لتأمين العمليات على القاعدة.
عادة يجب أن يكون لديك هذه الترحيلات:
framework Laravel MVC
الترحيل
ابدأ عمليات الترحيل:

php artisan migrate
يجب أن ينتهي بك الأمر إلى هذه الجداول الستة في قاعدة البيانات الخاصة بك:
framework Laravel MVC
الجداول
لتجنب تلقي الأخطاء ، يجب إجراء عمليات الترحيل بالترتيب الصحيح! يتم إعطاء ترتيب عمليات الترحيل حسب تاريخ تكوينها ، لذلك عليك فقط تغيير التاريخ باسم عمليات الترحيل لتغيير الترتيب.
population
لديك بالفعل ملفات للجداول   users و   posts . سننشئ واحدة للعلامات ( TagTableSeeder.php) :

<?php

use Illuminate\Database\Seeder;
use Carbon\Carbon;

class TagTableSeeder extends Seeder {

    private function randDate()
    {
        return Carbon::createFromDate(null, rand(1, 12), rand(1, 28));
    }

    public function run()
    {
        DB::table('tags')->delete();
        
        for($i = 0; $i < 20; ++$i)
        {
            $date = $this->randDate();
            DB::table('tags')->insert(array(
                    'tag' => 'tag' . $i,
                    'tag_url' => 'tag' . $i,
                    'created_at' => $date,
                    'updated_at' => $date
                ));
        }
    }
}
سيكون لدينا بالتالي 20 علامات.
نقوم أيضًا بإنشاء ملف للجدول المحوري ( PostTagTableSeeder.php) :

<?php

use Illuminate\Database\Seeder;

class PostTagTableSeeder extends Seeder {

    public function run()
    {
        for($i = 1; $i <= 100; ++$i)
        {
            $numbers = range(1, 20);
            shuffle($numbers);
            $n = rand(3, 6);
            for($j = 1; $j < $n; ++$j)
            {
                DB::table('post_tag')->insert(array(
                    'post_id' => $i,
                    'tag_id' => $numbers[$j]
                ));
            }
        }
    }
}
عشوائيا نقوم بإنشاء عدة مهام للعلامات إلى المقالات.
يبقى فقط تحديث ملف DatabaseSeeder.php :

<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return  void
     */
    public function run()
    {
        $this->call(UserTableSeeder::class);
        $this->call(PostTableSeeder::class);
        $this->call(TagTableSeeder::class);
        $this->call(PostTagTableSeeder::class);
    }
}
يجب أن يكون لديك هذه الملفات:
framework Laravel MVC
ملفات population
يبقى فقط لإطلاق population:

php artisan db:seed
إذا تلقيت رسالة تخبرك أن أحد الفصول غير موجودة ، فابدأ بتنفيذ الامر composer dumpautoload . إذا بدأت ملء الجداول ، فابدأ من نقطة الصفر وحذفها جميعًا.
من الممكن إجراء عملية ترحيل وpopulation في كتلة مع بناء الجملة:

php artisan migrate --seed
المُجسمات
سنحتاج إلى إعلان العلاقة n: n في النموذج   Post  :

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{

    protected $fillable = ['titre','contenu','user_id'];

    public function user() 
    {
        return $this->belongsTo('App\User');
    }

    public function tags()
    {
        return $this->belongsToMany('App\Tag');
    } 

}
ستقوم الطريقة  Tags باسترداد العلامات المرتبطة بالمقال. نستخدم طريقة    belongsToMany  للقيام بذلك.
سنحتاج أيضًا إلى قالب للعلامات:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Tag extends Model
{

    protected $fillable = ['tag','tag_url'];

    public function posts()
    {
        return $this->belongsToMany('App\Post');
    } 

}
لدينا الطريقة المتبادلة للطريقة السابقة: سوف    تسمح لنا posts  باسترداد المقالات المتعلقة بالعلامة.
فيما يلي رسم تخطيطي لهذه العلاقة مع طريقتين متماثلتين:
framework Laravel MVC
العلاقة n: n
لقد انتهى الأمر بهذه المُجسمات الثلاثة:
framework Laravel MVC
المُجسمات 3
يمكننا إنشاء ملف لتخزينها ، ولكن بما أن هناك عددًا قليلًا ، فسنحتفظ بها على هذا النحو. إذا فعلنا ذلك ، فسنضطر إلى تكييف مساحات الأسماء وفقًا لذلك. قبل كل شيء ، يجب عليك ملء مساحة الاسم في ملف   config/auth.php للمُجسم   User .

التحقق من صحة


سيكون لدينا حالة تحقق خاصة إلى حد ما. في الواقع ، كما قلت أعلاه ، سيتم إدخال العلامات في عنصر تحكم نص مفصول بفواصل. لقد خططنا في الجدول   tags ألا يتجاوز عددهم 50 حرفًا. ليس لدينا في ترسانة قواعد التحقق من صحة Laravel مثل هذا الاحتمال ، لذلك سيتعين علينا إنشاؤه.
لقد أنشأنا فصلًا بالفعل   PostRequest في الفصل السابق ، يجب أن نضيف قاعدة العلامات:

<?php

namespace App\Http\Requests;

use App\Http\Requests\Request;

class PostRequest extends Request
{

    /**
     * Determine if the user is authorized to make this request.
     *
     * @return  bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return  array
     */
    public function rules()
    {
        return [
            'titre' => 'required|max:80',
            'contenu' => 'required',
            'tags' => ['Regex:/^[A-Za-z0-9-éèàù]{1,50}?(,[A-Za-z0-9-éèàù]{1,50})*$/']
        ];
    }

}
كما هو الحال خاصة ، وأنا استخدم تعبير منتظم. يبقى فقط لمعالجة الرسالة.
إذا نظرت إلى الملف ،   resources/lang/en/validation.php  ستجد هذا الكود:

<?php
'custom' => [
    'attribute-name' => [
        'rule-name' => 'custom-message',
    ],
],
هذا هو المكان الذي يمكننا فيه إضافة رسائل محددة. لذلك سنكتب:

<?php
'custom' => [
    'tags' => [
        'regex' => "tags, separated by commas (no spaces), should have a maximum of 50 characters.",
    ],
],
سنفعل نفس الشيء في الملف الفرنسي:

<?php
'custom' => [
    'tags' => [
        'regex' => "Les mots-clefs, séparés par des virgules (sans espaces), doivent avoir au maximum 50 caractères alphanumériques.",
    ],
],

الإدارة


وحدة التحكم والطرق
الآن بعد أن أصبح كل شيء مطبقًا من حيث البيانات والتحقق من الصحة ، دعونا نلقي نظرة على كيفية إدارتها. لدينا بالفعل وحدة تحكم   PostController ولكن يجب أن نكملها للعمل مع العلامات:

<?php

namespace App\Http\Controllers;

use App\Repositories\PostRepository;
use App\Repositories\TagRepository;
use App\Http\Requests\PostRequest;

class PostController extends Controller
{

    protected $postRepository;

    protected $nbrPerPage = 4;

    public function __construct(PostRepository $postRepository)
    {
        $this->middleware('auth', ['except' => ['index', 'indexTag']]);
        $this->middleware('admin', ['only' => 'destroy']);

        $this->postRepository = $postRepository;
    }

    public function index()
    {
        $posts = $this->postRepository->getWithUserAndTagsPaginate($this->nbrPerPage);
        $links = $posts->render();

        return view('posts.liste', compact('posts', 'links'));
    }

    public function create()
    {
        return view('posts.add');
    }

    public function store(PostRequest $request, TagRepository $tagRepository)
    {
        $inputs = array_merge($request->all(), ['user_id' => $request->user()->id]);

        $post = $this->postRepository->store($inputs);

        if(isset($inputs['tags'])) 
        {
            $tagRepository->store($post, $inputs['tags']);
        }

        return redirect(route('post.index'));
    }

    public function destroy($id)
    {
        $this->postRepository->destroy($id);

        return redirect()->back();
    }

    public function indexTag($tag)
    {
        $posts = $this->postRepository->getWithUserAndTagsForTagPaginate($tag, $this->nbrPerPage);
        $links = $posts->render();

        return view('posts.liste', compact('posts', 'links'))
        ->with('info', 'Résultats pour la recherche du mot-clé : ' . $tag);
    }

}
أضفت الطريقة   indexTag التي يجب أن تبدأ البحث عن المقالات التي تحتوي على هذه العلامة وإرسال المعلومات في طريقة العرض    liste . لقد قمت بإعادة صياغة الكود قليلا. 
من الضروري إضافة المسار للوصول إلى هذه الطريقة الجديدة:

<?php
Route::resource('post', 'PostController', ['except' => ['show', 'edit', 'update']]);
Route::get('post/tag/{tag}', 'PostController@indexTag');
h5> المستودعات
في ما يلي مستودع المواد ( app/Repositories/PostRepository.php) الذي تم تعديله لمراعاة العلامات:

<?php
namespace App\Repositories;

use App\Post;

class PostRepository {

    protected $post;

    public function __construct(Post $post)
    {
        $this->post = $post;
    }

    private function queryWithUserAndTags()
    {
        return $this->post->with('user', 'tags')
        ->orderBy('posts.created_at', 'desc');      
    }

    public function getWithUserAndTagsPaginate($n)
    {
        return $this->queryWithUserAndTags()->paginate($n);
    }

    public function getWithUserAndTagsForTagPaginate($tag, $n)
    {
        return $this->queryWithUserAndTags()
        ->whereHas('tags', function($q) use ($tag)
        {
          $q->where('tags.tag_url', $tag);
        })->paginate($n);
    }

    public function store($inputs)
    {
        return $this->post->create($inputs);
    }

    public function destroy($id)
    {
        $post = $this->post->findOrFail($id);
        $post->tags()->detach();
        $post->delete();
    }

}
قد تفاجأ بطول بعض أسماء الوظائف. إنه خيار نحوي. أفضل الأسماء الصريحة ، حتى لو كان ذلك يعني تمديدها. 
وهنا مستودع للعلامات ( app/Repositories/TagRepository.php‌) :

<?php
namespace App\Repositories;

use App\Tag;
use Illuminate\Support\Str;

class TagRepository
{

    protected $tag;

    public function __construct(Tag $tag)
    {
        $this->tag = $tag;
    }

    public function store($post, $tags)
    {
        $tags = explode(',', $tags);

        foreach ($tags as $tag) {

            $tag = trim($tag);

            $tag_url = Str::slug($tag);

            $tag_ref = $this->tag->where('tag_url', $tag_url)->first();

            if(is_null($tag_ref)) 
            {
                $tag_ref = new $this->tag([
                    'tag' => $tag,
                    'tag_url' => $tag_url
                ]); 

                $post->tags()->save($tag_ref);

            } else {
            
                $post->tags()->attach($tag_ref->id);

            }

        }

    }

}

كيفية العمل


سنقوم الآن بتحليل هذا الكود.
قائمة المقالات
يتم تعديل أسلوب مستودع المقالات وإعادة تسميته لإضافة الجدول   tags  :

<?php
public function getWithUserAndTagsPaginate($n)
{
    return $this->queryWithUserAndTags()->paginate($n);
}
لتوضيح الكود قُمت بإنشاء وظيفة خاصة والتي سنستخدمها عدة مرات:

<?php
private function queryWithUserAndTags()
{
    return $this->post->with('user', 'tags')
    ->orderBy('posts.created_at', 'desc');      
}
لاحظت أننا أضفنا الجدول   tags كمُدخل إلى الطريقة   with بالإضافة إلى    users . سنحتاج بالفعل إلى معلومات العلامة للعرض في العرض.
من المثير رؤية الاستعلامات التي أنشأها Eloquent ، على سبيل المثال للصفحة الأولى:

select count(*) as aggregate from `posts`

select * from `posts` order by `posts`.`created_at` desc limit 4 offset 0

select * from `users` where `users`.`id` in ('7', '2', '6')

select `tags`.*, `post_tag`.`post_id` as `pivot_post_id`, `post_tag`.`tag_id` as `pivot_tag_id` from `tags` inner join `post_tag` on `tags`.`id` = `post_tag`.`tag_id` where `post_tag`.`post_id` in ('25', '1', '18', '14')
نحن نرى ذلك:
  1. نطلب العدد الإجمالي للعناصر ترقيم الصفحات ،
  2. نطلب أول 4 سطور من المقالات مع ترتيب التاريخ ،
  3. نطلب من المستخدمين الذين يتوافقون مع المواد المختارة ،
  4. نطلب العلامات المعنية بالمقالات.
نحن ندرك جيدا العمل الذي أنجزه Eloquent لنا !
مقال جديد
من الواضح أن تسجيل مقال جديد سيكون أكثر صعوبة قليلاً بسبب وجود العلامات. في مستودع المنشورات ، سنوفر فقط المقالة:

<?php
public function store($inputs)
{
    return $this->post->create($inputs);
}
سيتم تنفيذ الجزء الأكبر من العمل في مستودع العلامات:

<?php
public function store($post, $tags)
{
    $tags = explode(',', $tags);

    foreach ($tags as $tag) {

        $tag = trim($tag);

        $tag_url = Str::slug($tag);

        $tag_ref = $this->tag->where('tag_url', $tag_url)->first();

        if(is_null($tag_ref)) 
        {
            $tag_ref = new $this->tag([
                'tag' => $tag,
                'tag_url' => $tag_url
            ]); 

            $post->tags()->save($tag_ref);

        } else {
        
            $post->tags()->attach($tag_ref->id);

        }

    }

}
هذا الكود يستحق بعض التعليقات. يتم إرسال العلامات بواسطة النموذج (الذي سنراه لاحقًا) في شكل نص مع فاصلة كفاصل بينهم  مثلا :

tag1,tag2,tag3
أول شيء في وحدة التحكم هو التحقق من وجود علامات تم إدخالها:

<?php
if(isset($inputs['tags'])) 
{
    $tagRepository->store($post, $inputs['tags']);
}
إذا كانت هذه هي الحالة ، يتم استدعاء طريقة المستودع store عن طريق إرسال العلامات وإشارة النموذج الذي تم إنشاؤه. في المستودع ، نقوم بإنشاء مصفوفة باستخدام الفاصل (فاصلة):

<?php
$tags = explode(',', $tags);
ثم نذهب من خلال الجدول:

<?php
foreach ($tags as $tag)
كإجراء وقائي ، نحذف أي مسافات:

<?php
$tag = trim($tag);
نقوم بإنشاء إصدار عنوان url للعلامة (باستخدام طريقة slug للفئة Str ) :

<?php
$tag_url = Str::slug($tag);
نتحقق مما إذا كانت هذه العلامة موجودة بالفعل:

<?php
$tag_ref = $this->tag->where('tag_url', $tag_url)->first();
إذا لم يكن الأمر كذلك ، فإننا ننشئها:

<?php
$tag_ref = new $this->tag([
    'tag' => $tag,
    'tag_url' => $tag_url
]); 
$post->tags()->save($tag_ref);
لاحظ كيف  تتيح لك الطريقة    save   هنا إنشاء العلامة والإشارة إلى الجدول المحوري.
إذا كانت العلامة موجودة بالفعل ، فنحن فقط نعلم الجدول المحوري بالطريقة   attach  :

<?php
$post->tags()->attach($tag_ref->id);
حذف مقال
عندما سنقوم بحذف مقال ، سيتعين علينا أيضًا حذف الروابط التي تحتوي على العلامات:

<?php
public function destroy($id)
{
    $post = $this->post->findOrFail($id);
    $post->tags()->detach();
    $post->delete();
}
تحذف الطريقة    Detach   الصفوف في الجدول المحوري.
البحث حسب العلامة
أخيرًا ، يجب أن نرى البحث عن طريق اختيار علامة(tag):

<?php
public function getWithUserAndTagsForTagPaginate($tag, $n)
{
    return $this->queryWithUserAndTags()
    ->whereHas('tags', function($q) use ($tag)
    {
      $q->where('tags.tag_url', $tag);
    })->paginate($n);
}
لاحظت أنه بالمقارنة مع كود الطريقة getWithUserAndTagsPaginate أضفنا هذه الطريقة whereHas . يضيف هذا الأسلوب شرطًا إلى جدول محمّل. من المثير للاهتمام أيضًا رؤية الطلبات التي تم إنشاؤها بواسطة Eloquent :

select count(*) as aggregate from `posts` where (select count(*) from `tags` inner join `post_tag` on `tags`.`id` = `post_tag`.`tag_id` where `post_tag`.`post_id` = `posts`.`id` and `tags`.`tag_url` = 'tag-14') >= '1'

select * from `posts` where (select count(*) from `tags` inner join `post_tag` on `tags`.`id` = `post_tag`.`tag_id` where `post_tag`.`post_id` = `posts`.`id` and `tags`.`tag_url` = 'tag-14') >= '1' order by `posts`.`created_at` desc limit 4 offset 0

select * from `users` where `users`.`id` in ('3', '1', '6', '4')

select `tags`.*, `post_tag`.`post_id` as `pivot_post_id`, `post_tag`.`tag_id` as `pivot_tag_id` from `tags` inner join `post_tag` on `tags`.`id` = `post_tag`.`tag_id` where `post_tag`.`post_id` in ('13', '76', '87', '37')
هناك 5 محمّلة إلى حد ما:
  1. نحسب سجلات ترقيم الصفحات (مع صلة) ،
  2. نحصل على 4 سطور من المقالات (مع صلة) ،
  3. نستعيد المستخدمين الذين كتبوا المقالات ،
  4. نسترجع العلامات المعنية بالمقالات (مع صلة).
هناك شيء واحد لم أتمكن من إدارته في كل هذا الكود ، وهو حالة العلامات اليتيمة في حالة حذف مقال. هذه الإدارة ليست إلزامية لأنها ليست مزعجة حقا أن يكون لها علامات اليتيمة. يمكن أن نخطط لصيانة عرضية للقاعدة أو إجراء من جانب المسؤول.

العرض


القالب
نحتفظ بنفس القالب ( resources/views/template.blade.php) :

<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Mon joli site</title>
        {!! Html::style('https://netdna.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css') !!}
        {!! Html::style('https://netdna.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css') !!}
        <!--[if lt IE 9]>
            { { Html::style('https://oss.maxcdn.com/libs/html5shiv/3.7.2/html5shiv.js') }}
            { { Html::style('https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js') }}
        <![endif]-->
        <style> textarea { resize: none; } </style>
    </head>
  <body>
    <header class="jumbotron">
      <div class="container">
        <h1 class="page-header">{!! link_to_route('post.index', 'Mon joli blog') !!}</h1>
        @yield('header')
      </div>
    </header>
    <div class="container">
      @yield('contenu')
    </div>
  </body>
</html>
القائمة
فيما يلي عرض لقائمة المقالات ( resources/views/posts/liste.blade.php) :

@extends('template')

@section('header')
    @if(Auth::check())
        <div class="btn-group pull-right">
            {!! link_to_route('post.create', 'Créer un article', [], ['class' => 'btn btn-info']) !!}
            {!! link_to('logout', 'Deconnexion', ['class' => 'btn btn-warning']) !!}
        </div>
    @else
        {!! link_to('login', 'Se connecter', ['class' => 'btn btn-info pull-right']) !!}
    @endif
@endsection

@section('contenu')
    @if(isset($info))
        <div class="row alert alert-info">{ { $info }}</div>
    @endif
    {!! $links !!}
    @foreach($posts as $post)
        <article class="row bg-primary">
            <div class="col-md-12">
                <header>
                    <h1>{ { $post->titre }}
                        <div class="pull-right">
                            @foreach($post->tags as $tag)
                                {!! link_to('post/tag/' . $tag->tag_url, $tag->tag, ['class' => 'btn btn-xs btn-info']) !!}
                            @endforeach
                        </div>
                    </h1>
                </header>
                <hr>
                <section>
                    <p>{ { $post->contenu }}</p>
                    @if(Auth::check() and Auth::user()->admin)
                        {!! Form::open(['method' => 'DELETE', 'route' => ['post.destroy', $post->id]]) !!}
                            {!! Form::submit('Supprimer cet article', ['class' => 'btn btn-danger btn-xs ', 'onclick' => 'return confirm(\'Vraiment supprimer cet article ?\')']) !!}
                        {!! Form::close() !!}
                    @endif
                    <em class="pull-right">
                        <span class="glyphicon glyphicon-pencil"></span> { { $post->user->name }} le {!! $post->created_at->format('d-m-Y') !!}
                    </em>
                </section>
            </div>
        </article>
        <br>
    @endforeach
    {!! $links !!}
@endsection
مع هذا الجانب:
framework Laravel MVC
قائمة المقالات
تظهر العلامات في شكل أزرار صغيرة. يؤدي النقر فوق أحد هذه الأزرار إلى بدء البحث من هذه العلامة ويعرض المقالات المقابلة بالإضافة إلى شريط المعلومات:
framework Laravel MVC
البحث عن طريق العلامة
المستخدم المتصل لديه أيضًا الزر لإنشاء مقال. المسؤول لديه أيضا زر الحذف:
framework Laravel MVC
للمستخدمين المسجلين
عرض إنشاء المقالة
تم إثراء نموذج إنشاء المقالة ( resources/views/posts/add.blade.php) بعنصر تحكم نصي لإدخال العلامات:

@extends('template')

@section('contenu')
    <br>
    <div class="col-sm-offset-3 col-sm-6">
        <div class="panel panel-info">
            <div class="panel-heading">Ajout d'un article</div>
            <div class="panel-body"> 
                {!! Form::open(['route' => 'post.store']) !!}
                    <div class="form-group {!! $errors->has('titre') ? 'has-error' : '' !!}">
                        {!! Form::text('titre', null, ['class' => 'form-control', 'placeholder' => 'Titre']) !!}
                        {!! $errors->first('titre', '<small class="help-block">:message</small>') !!}
                    </div>
                    <div class="form-group {!! $errors->has('contenu') ? 'has-error' : '' !!}">
                        {!! Form::textarea ('contenu', null, ['class' => 'form-control', 'placeholder' => 'Contenu']) !!}
                        {!! $errors->first('contenu', '<small class="help-block">:message</small>') !!}
                    </div>
                    <div class="form-group { { $errors->has('tags') ? 'has-error' : '' }}">
                        {!! Form::text('tags', null, array('class' => 'form-control', 'placeholder' => 'Entrez les tags séparés par des virgules')) !!}
                        {!! $errors->first('tags', '<small class="help-block">:message</small>') !!}
                    </div>
                    {!! Form::submit('Envoyer !', ['class' => 'btn btn-info pull-right']) !!}
                {!! Form::close() !!}
            </div>
        </div>
        <a href="javascript:history.back()" class="btn btn-primary">
            <span class="glyphicon glyphicon-circle-arrow-left"></span> Retour
        </a>
    </div>
@endsection
تتم إدارة رسالة الخطأ للتحقق من صحة العلامة أيضًا:
framework Laravel MVC
نموذج إنشاء المقالة
لا أفصل كود كل واجهات العرض هذه ، فهي ليست معقدة للغاية وتغطي المواقف التي واجهتها بالفعل.

في الخلاصة


  • تتطلب علاقة نوع n: n إنشاء جدول محوري.
  • Eloquent يدير بأناقة الجداول المحورية بطرق مناسبة.
  • يمكنك إنشاء قواعد ورسائل التحقق من الصحة المخصصة.