Kategorie – relacja wiele do wielu
Zaczniemy od stworznia modelu:
php artisan make:model Category
Czyli model będzie reprezentował pojedynczą kategorię, a teraz przjdźmy do przygotowania sobie migracji:
php artisan make:migration create_categories_table --create=categories
Przejdźmy zatem to utworzonej migracji i wypełnijmy ją:
Dodajmy pole name do migracji i to w zasadzie tyle. Chcemy nazwać kategorie.
$tables->string('name');
Po uzupełnieniu migracji trzeba ją wykonać, a do tego służy polecenie
php artisan migrate
Zostanie wykonana tylko jedna migracja, oczywiście ta nowo utworzona.
Podsumowując do tej pory co już zrobiliśmy, to mamy już utworzony mode który reprezentuje pojedynczą kategorię oraz tabelę do której będziemy te kategorie zapisywać.
Definiujemy pivot table
Pamiętajmy, że mamy tu relację wiele do wielu.
– jeden film może być przypisany do wielu kategorii
– i jedna kategoria może posiadać wiele filmów
Utworzymy sobie zatem osobną tabelę, gdyż interesują nas dwie rzeczy identyfikator kategorii oraz identyfikator powiązanego z nią filmu. W tej osobnej tabeli będziemy mieli dwa klucze obce. Pierwszy z nich będzie się odwoływał do pola id kategorii a drugi do pola id filmu. Zatem przejdźmy do dzieła:
Utwórzmy tabelę:
php artisan make:migration create_category_video_table --create=category_video
Teraz przejdźmy do nowo utworzonej migracji:
– Na wstępie możemy usunąć z niej pole id, gdyż nie będzie potrzebne
– utwórzmy dwa pola integer z category_id oraz video_id
– i utwórzmy klucze obce.
Całość wygląda tak:
Schema::create('category_video', function (Blueprint $table) { $table->integer('category_id'); $table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade'); $table->integer('video_id'); $table->foreign('video_id')->references('id')->on('videos')->onDelete('cascade'); $table->timestamps(); });
Teraz już możemy uruchomić migrację:
php artisan migrate
Metody wiążące modele
- Przejdźmy do modelu Category i tworzymy w niej metod która pozwoli Nam z poziomu kategorii odwołać się do zapisanych w niej filmów:
// * Kategoria jest przypisana do wielu filmów public function videos() { return $this->belongsToMany('App\Video')->withTimestamps(); }
Następnie przechodzimy do modelu Video i tam również utworzymy metodę, która pozwoli Nam z poziomu wideo odwołać się do wszystkich powiązanych z nim kategorii:
// * Wideo ma wiele kategorii public function categories() { return $this->belongsToMany('App\Category')->withTimestamps(); }
Eksperymenty z modelem i relacjami
Zacznijmy od utworzenia kategorii. DO tego przyda Nam się teraz:
php artisan tinker
$c = new App\Category;
Ta kategoria posiada właściwość, można to sprawdzić w migracji, a jest nią pole name. Do którego możemy się odwołać i ją uzupełnić:
$c->name = ‘Hello Roman!’
$c->save()
Utwórzmy w ten sposób kilka kategorii. Nastętnie utwórzmy zmienną $v i poprzez model przypiszmy do niej film o id 1:
$v->categories()->attach(1)
Wytłumaczmy sobie jeszcze raz co stało się powyżęj. w zmiennej $v mamy złapane video z tabeli videos o id=1. Wykonujemy na modelu metodę categories(), którą utworzyliśmy w modelu Video. Ma ona na celu pobranie wszystkich dostępnych kategorii, a następnie korzystamy z wbudowanej metody attach(id) gdzie wpisujemy id kategorii do której film przypisujemy. Aby wizualizować sobie co się stało możemy użyć polecenia:
DB::select('select * from category_video')
Powinniśmy otrzymać:
[ {#3060 +"category_id": "1", +"video_id": "1", +"created_at": "2019-12-27 15:06:59", +"updated_at": "2019-12-27 15:06:59", }, ]
Ok, a jak sprawdzić do jakiej kategorii został przypisany Nasz film?
$v->categories
Ok, ale przetestujmy odwrotnie sprawdźmy do konkretnej tkategorii jakie są przypisane filmy, w tym celu:
$c = new App\Category::find(1); $c->videos;
Dodajemy pole Select
Do formularza dodawania filmów dodamy pole Select. Przechodzimy zatem do resources/views/videos/form.blade.php i nad przyciskiem wpisujemy:
<div class="form-group"> <div class="col-md-4 control-label"> {!! Form::label('CategoryList', 'Wybierz kategorię:') !!} </div> <div class="col-md-6"> {!! Form::select('CategoryList', array('1' => 'Kategoria 1') , null, ['class'=>'form-control', 'multiple']) !!} </div> </div>
Wypełniamy pole listą kategorii
Jak wyświetlić listę nazw z tabeli categories:
App\Category::pluck(‘name’)
A więc przechodzimy do metody create() i na wstępie importujemy model category i modyfikujemy metodę create:
use App\Category; public function create() { $categories = Category::pluck('name', 'id'); return view('videos.create')->with('categories', $categories); }
Teraz przechodzimy do pliku widoku: form.blade.php i poczyniamy tam takie zmiany:
<div class="form-group"> <div class="col-md-4 control-label"> {!! Form::label('CategoryList', 'Wybierz kategorię:') !!} </div> <div class="col-md-6"> {!! Form::select('CategoryList', $categories , null, ['class'=>'form-control', 'multiple']) !!} </div> </div>
Zapisywanie kategorii
Zacznijmy do tego, ze należny nazwy kategorii dodać do dozwolonych nazw w modelu category.
protected $fillable = [ 'name' ];
Bardzo ważną zmianą jest dodanie tablicy do formularza (form.blade.php)
<div class="form-group"> <div class="col-md-4 control-label"> {!! Form::label('CategoryList', 'Wybierz kategorię:') !!} </div> <div class="col-md-6"> {!! Form::select('CategoryList[]', $categories , null, ['class'=>'form-control', 'multiple']) !!} </div> </div>
Zwróć uwagę, że w linii nr 6 w pierwszym parametrze pola select występuje ‘CategoryList[]‘
Teraz odpowiednio edytujemy metodę store()
public function store(CreateVideoRequest $request) { $video = new Video($request->all()); Auth::user()->videos()->save($video); $categoryIds = $request->input('CategoryList'); $video->categories()->attach($categoryIds); Session::flash('video_created', 'Twój film został dodany do bazy danych.'); return redirect('videos'); }
Wyświetlanie kategorii na stronie show.blade.php
W tym celu przechodzimy do pliku show.blade.php i w miejscu w którym chcemy wyświetlić do jakich kategorii należy aktualny film musimy:
@foreach($videos->categories as $category) <a href="#">{{ $category->name }} </a> @endforeach
Oczywiście można użyć znacznika <ul> lub <ol>
Kategorie w formularzu edycji
Mogłeś zauważyć, że gdy przejdziesz do edycji filmu – formularz, który współdzielimy z plikiem create.blade.php przekazuje dwa parametry: video oraz categories. Dlatego też z metody create() w pliku VideosController.php skopiujmy linię:
$categories = Category::pluck('name', 'id');
i wklejmy ją na początek metody edit(). Następnie w metodzie edit musimy przekazać jakoś obie zmienne (video, categories) do pliku widoku. musimy zatem podmienić tą linię:
return view('videos.edit')->with('video', $video);
na tą:
return view('videos.edit', compact('video', 'categories'));
Powyższy zabieg pozwoli Nam wyświetlić poprawnie formularz widoku, ale jak wcześniej znów nie jest idealnie. Problem polega na tym, że mieliśmy zaznaczone wcześniej kategorie, a teraz po wejściu w edycję ich nie wyświetla…
Oczywiście z tym też możemy sobie łatwo poradzić. W tym celu musimy przejść do pliku form.blade.php i w miejscu gdzie mamy fragment kodu odpowiedzialny za wyświetlanie pola select mamy zdefiniowany null:
{!! Form::select('CategoryList[]', $categories , null, ['class'=>'form-control', 'multiple']) !!}
To właśnie to pole odpowiada za zaznaczenie wybranych wcześniej kategorii. Możemy sobie z tym problemem poradzić przy pomocy modelu. Wystarczy przejść do Video.php i w nim dopisać metodę, która będzie zbierała listę kategorii powiązaną z tym jednym filmem, który w formularzu edytujemy.
public function getCategoryListAttribute(){ return $this->categories->pluck('id')->all(); }
Nazwa metody nie jest przypadkowa: getCategoryListAttribute. Zwróć uwagę na człon: getCategoryListAttribute. Jest identyczny jak w pliku form.blade.php pierwszy parametr select:
{!! Form::select('CategoryList[]', $categories , null, ['class'=>'form-control', 'multiple']) !!}
A pozostała nazwa metody? getCategoryListAttribute, chyba też mówi sama za siebie.
Dzięki takiej konwencji nazewnictwa Laravel będzie potrafił je ze sobą automatycznie powiązać.
Synchronizacja kategorii z wideo
Problem polega na tym, iż dodając video wpisujemy tytuł, url, description i wybieramy kategorię do jakich będzie należeć ten film. Po jakimś jednak czasie chcielibyśmy, zmienić jego przyunależność do kategorii i edytujmy ten film wybnierając inne kategorie. I tu jest problem. zmiany przebiegły pomyślnie ale i tak film należy do kategorii które mu oryginalnie przypisaliśmy. Jak sobie z tym problemem poradzić?
Musimy przejść do pliku VideosController i w nim odszukujemy metodę update(). Musimu zaktualizować ją o:
public function update($id, CreateVideoRequest $request) { $video = new Video; $video->create($request->all()); $video->categories()->sync($request->input('CategoryList')); return redirect('videos'); }