PHPPamokos.lt


6. Models ir darbas su duomenų baze

Liko trečia svarbi MVC dedamoji, kurios dar neaptarėme. Kalbėjome apie Views, tada apie Controllers, atėjo laikas aptarti Models.

Kaip jau minėjau, Models yra failai, saugomi app kataloge. Jie dažniausiai atsako už duomenų apdorojimą ir logiką. Pavyzdžiai realių funkcijų, kurios gali būti aprašytos per Models:
  • function getArticles()
  • function saveUserData()
  • function prepareArticleForPublishing()
Pakartosiu bendrą MVC logikos grandinę:
  • Routes failas nukreipia į Controllerį
  • Controlleris iškviečia pasirinktų Modelių norimas funkcijas, kurios dažniausiai grąžina rezultatą - duomenis
  • Iš Controllerio kviečiamas View, perduodant jam iš Modelio gautus duomenis
Kadangi Modeliai dirba su duomenimis, tai dažniausiai reiškia kad dirba su duomenų baze. Jūs nustebsite, kaip tai paprasta Laravel sistemoje.
 

Prisijungimas prie duomenų bazės

Pradžioje sukurkime duomenų bazę ir sukonfigūruokime prisijungimo prie MySQL parametrus. Šiame kurse dirbsime būtent su MySQL duomenų bazių sistema, bet Laravel leidžia prisijungti ir prie kitų - SQLite, PostgreSQL, MS SQL Server.

Duomenų bazę galite lokaliai sukurti su phpMyAdmin ar bet kokiu kitu jums patogiu įrankiu - tai nėra šio kurso dalis. Sakykime, kad turime sukūrę duomenų bazę pavadinimu "laravel", o prisijungimo duomenys prie DB serverio yra tokie:
  • Adresas: "localhost"
  • Vartotojas: "root"
  • Slaptažodis: "laravel1234"
Į Laravel šiuos parametrus įrašome faile .env:
DB_HOST=localhost
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=laravel1234
Pastaba: realiuose projektuose nerekomenduoju palikti DB prisijungimo su vartotoju "root" - saugumo sumetimais sukurkite atskirą vartotoją ir sudėtingesnį slaptažodį.

Taigi, prisijungimo duomenis nurodėme, dabar perkrauname mūsų projekto pagrindinį puslapį naršyklėje: Nieko nepasikeitė? Gerai - būtent taip ir turėjo būti! Priešingu atveju, jei prie duomenų bazės prisijungti nepavyko - matysite Laravel klaidą. Bet tikiuosi, kad viskas gerai ir Laravel prisijungė prie MySQL.
 

Models: modelio sukūrimas ir Eloquent

Pats laikas sukurti pirmą modelį - sakykime, app/Book.php:
namespace App;

use Illuminate\Database\Eloquent\Model;

class Book extends Model {
}
Viskas, daugiau kodo nereikia. Taip, tuščia klasė, be jokių funkcijų, bet šiuo modeliu jau galima naudotis - dėl to, kad jis paveldi visus Eloquent metodus. Bendrai paėmus, darbas su Eloquent yra vienas didžiausių Laravel privalumų - šis įrankis sutaupo programuotojams labai daug brangaus laiko.

Taigi, pabandykime šį modelį panaudoti iš Controllerio:
use App\Book;

class BooksController extends Controller {

  public function showIndex()
  {
    $books = Book::all();
    return view('books', ['books_list' => $books]);
  }

}
Eilutė Book::all() grąžins visas eilutes iš DB lentelės "books". Matote kaip paprasta? Bet šioje "magijoje" yra keli svarbūs dalykai:
  • Kiekvienas toks Eloquent modelis yra susietas su konkrečia duomenų bazės lentele. Kaip matėme pavyzdyje, jos nurodyti net nebūtina - Laravel automatiškai priskirs lentelę pagal daugiskaitą nuo modelio pavadinimo! Jei modelio pavadinimas Book, tai susieta lentelė bus books.
  • Visa filosofija, kuri slepiasi po Eloquent, yra kad jums nereikia rašyti SQL užklausų - tiesiog kviečiate PHP funkcijas.
Dar keletas paprastų pavyzdžių, kaip gauti duomenis iš tokio Eloquent modelio:
Book::find(1); // atrinkimas pagal "id"
Book::where('author_id', 1)->get(); // atrinkimas pagal lauką
Book::where('author_id', 1)->count() // įrašų skaičius
Dabar - visiškai konkretus pilnas pavyzdys to, kaip galima išvesti į puslapį knygų sąrašą. resource/views/books.blade.php:
<table>
  <tr>
    <th>Book</th>
    <th>Author</th>
    <th>Release Date</th>
  </tr>
  @foreach ($books as $book)
  <tr>
    <td>{{ $book->title }}</td>
    <td>{{ $book->author }}</td>
    <td>{{ $book->release_date }}</td>
  </tr>
  @endforeach
  @if (count($books) == 0)
  <tr>
    <td colspan="3">No books found</td>
  </tr>
  @endif
</table>
app/Book.php:
class Book extends Model {
}
app/Http/Controllers/BooksController.php:
class BooksController extends Controller {

  public function showIndex() {
    $data = array('books' => Book::all());
    return View::make('books', $data);
  }

}
Štai taip viskas paprasta su Laravel ir duomenų baze. Bet, be abejo, yra ir daugiau galimybių dirbti su duomenimis. Susipažinkime su sąvoka Query Builder ir pažiūrėkime į pavyzdį:
User::where('id', '>', 100)->orderBy('username')->get();

Kaip matote, galima suformuoti visą užklausą iš atskirų dalių, įvairių Eloquent funkcijų pagalba.

Pažiūrėkime į kiek sudėtingesnę užklausą:

User::select('users.id', 'users.username', 'roles.title')
->join('roles', 'users.role_id', '=', 'roles.id')
->where('users.id', '>', 100)
->orWhere('users.role_id', '=', 1)
->orderBy('username')
->get();
Taip pat galima ir nenaudoti Eloquent modelių, o tiesiog kreiptis į konkrečią lentelę su DB::table()
DB::table('users')->select('username')->whereNull('deleted')->get();

Dar naudinga žinoti apie tokius būdus greitai paimti duomenis.

DB::table('users')->first(); // pirma eilutė
DB::table('users')->where('id', '=', 1)->pluck('username'); // masyvas iš vieno lauko reikšmių
Yra ir daugiau visokių SELECT pagalbininkų: groupBy, whereBetween, whereIn(), DB::raw(), max/min funkcijų ir kt. Apie jas plačiau galite pasiskaityti oficialioje dokumentacijoje.
 

Models: operacija INSERT

Iki šiol kalbėjome tik apie duomenų ištraukimą, pats laikas įterpti naujų duomenų. Tai irgi labai paprasta. Kad įterptumėte naują eilutę, tiesiog sukurkite naują Eloquent modelio objektą, užpildykite jo laukus ir iškvieskite funkciją save():
$user = new User;
$user->name = 'Administratorius';
$user->save();

Ir jei norėsite po to gauti naujos eilutės ID, kaip tai buvo daroma per PHP su funkcija mysql_insert_id(), su Laravel jokios funkcijos kviesti nereikia - identifikatorius automatiškai įrašomas į $user->id kintamąjį.

Taip pat Eloquent turi funkciją create(), kuriai galima paduoti tiesiog masyvą duomenų. Bet tam, kad galima būtų naudoti create(), turime modelyje apsirašyti, kokie laukai gali būti taip užpildomi, tam skirtas masyvas $fillable:

class User extends Model {

  protected $fillable = array('name', 'role_id');

}
$user = User::create(array('name' => 'Povilas', 'role_id' => 1));

Dar daugiau - yra funkcija firstOrCreate(), kuri patikrina, ar su tokiais duomenimis eilutė jau yra. Jei taip - ją grąžina, jei ne - sukuria ir grąžina.

$user = User::firstOrCreate(array('name' => 'Povilas', 'role_id' => 1));
 

Models: operacija UPDATE

Informacijos atnaujinimas atliekamas panašiai kaip ir įterpimas, tik pradžioje reikia surasti eilutę, kurią norime atnaujinti, o tada jau vėl užpildome norimus laukus ir kviečiame save():
$user = User::find(1);
$user->name = 'Administratorius';
$user->save();
 

Models: operacija DELETE

Trynimas yra beveik analogiškas atnaujinimui: surandame norimą eilutę ir kviečiame delete().
$user = User::find(1);
$user->delete();
Tiesa, su trynimu Laravel turi dar vieną patogumą - taip vadinamą "soft delete" metodą, kai eilutė nėra šalinama iš lentelės, o tiesiog užpildomas jos trynimo laikas lauke deleted_at. Kad pasinaudotumėte tokia galimybe, reikia modelyje panaudoti SoftDeletingTrait:
use Illuminate\Database\Eloquent\SoftDeletingTrait;
class Book extends Model {
  use SoftDeletingTrait;
  protected $dates = ['deleted_at'];
}

Tada, kai kviesite delete() funkciją, eilutė tiesiog pasipildys reikšme deleted_at. Ir jei po to bandysite gauti eilutes su get(), rezultatai negrąžins būtent tų ištrintųjų.

Jei kažkuriuo metu jums visgi prireiks tų ištrintų eilučių, rezultatų ištraukime galite panaudoti funkciją withTrashed():

DB::table('users')->withTrashed()->get();

O jei norėsite "prikelti" eilutę ir ją vėl aktyvuoti, tam yra funkcija restore:

$user->restore();

Iš kitos pusės, jei norėsite tokią užsilikusią eilutę visiškai pašalinti, kad jums netrukdytų - kvieskite forceDelete:

$user->forceDelete();
Ar naudoti tokį trynimo būdą - rinktis jums, tai yra tikrai patogu tais atvejais, kai yra didelė klaidų tikimybė, ir kai nereikalinga informacija visgi kažkada taps reikalinga ir prireiks greitai ją atstatyti.
 

Relationships: sąryšiai tarp skirtingų modelių/lentelių

Laravel siūlo ir galimybę nurodyti ir sąryšį tarp lentelių - tai ką įprastai darome su join ir foreign key struktūromis, Eloquent modelyje gali būti aprašoma kaip atskira funkcija. Konkretus pavyzdys - sakykime, kad lentelėje users yra laukas role_id, surištas su lentelės roles lauku id. Tada sukuriame du modelius: Role ir User:
class Role extends Model {
}
class User extends Model {

  public function role()
  {
    return $this->hasOne('App\Role');
  }

}

Ir tada, darydami užklausas, mes galime nedaryti jokių JOIN sakinių, tiesiog rašome taip:

$role = User::find(1)->role()->title;

Eloquent automatiškai priskirs JOIN'ui lauką, kuris turi būti klasės pavadinimas mažomis raidelėmis su pabaiga _id - pvz user_id, role_id ir pan.

Yra ir kitokių relationship tipų: belongsTo(), hasMany(), belongsToMany(). Principas yra panašus - sakykime, jei norime, nurodyti, kad viena lentelė priklauso kitai lentelei, naudojame belongsTo() - tą patį mūsų pavyzdį užrašome kitaip, kad Roles priklauso lentelei Users:

class User extends Model {
}
class Role extends Model {

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

}
$username = Role::find(1)->user()->username;

Ir tai dar ne viskas - tą kodą galima DAR sutrumpinti (jau sakiau kad Laravel yra nuostabus?). Kraunant Relationship funkciją, nebūtina po jos rašyti skliaustų, nes Laravel automatiškai užkraus tą funkciją ir iš jos paims pirmą eilutę.

Taigi, vietoje
$username = Role::find(1)->user()->username;
galime rašyti
$username = Role::find(1)->user->username;
 

Relationships: eager loading

Yra dar vienas būdas patogiai žaisti su sąryšiais - krauti juos iš karto Query Builder metu su funkcija with(). Tai vadinama eager loading. Pažiūrėkime į pavyzdį - jei norime išvesti visas krepšinio aikšteles ir jų miestus:
foreach (Court::with('city')->get() as $court)
{
  echo $court->title . ' - ' . $court->city->title;
}

Šiuo atveju kraunamas modelis City, kur modelyje Court yra nurodyta priklausomybė belongsTo().

Maža to, taip krauti galima ir daugiau nei vieną lentelių lygį - bandykime išvesti ir mikrorajonus:

foreach (Court::with('city.district')->get() as $court)
{
  echo $court->title . ' - ' . $court->city->district->title . ' (' . $court->city->title . ')';
}
 

Modelių generavimas su Artisan

Prisiminkime mūsų pagalbininką Artisan - būtent su juo galime nesunkiai sugeneruoti naują Eloquent modelį:
php artisan make:model City
Rezultatas - failas app/City.php:
namespace App;

use Illuminate\Database\Eloquent\Model;

class City extends Model
{
//
}
 

Praktika: krepšinio aikštelių duomenų bazės valdymas

Taigi šioje pamokoje pateiktos informacijos turėtų pilnai užtekti, kad dirbtumėte su duomenų baze ir jau galėtumėte sukurti savo CRUD (Create, Read, Update, Delete) aplikaciją. Hm, o gal tai dabar ir padarykime?

Prisiminus mūsų krepšinio aikštelių projektą, turime padaryti administratoriui galimybę valdyti aikšteles, jų tipus ir miestus. Kol kas praleidžiame prisijungimo formos funkciją - ją padarysime kitame skyrelyje. Taigi, kaip turbūt jau galite nuspėti, turėsime tris Modelius: Court, CourtType ir City.

Pradėkime nuo paprasčiausio dalyko - miestų. Sukuriame per phpMyAdmin paprastą DB lentelę cities su laukais id, title, o taip pat created_at ir updated_at (timestamps). Tada kuriame modelį app/models/City.php:
class City extends Model {
}
Primenu, kad Routes faile turime tokią eilutę:
Route::resource('admin/miestai', 'AdminCitiesController');

Tiesa, mūsų Routes faile nurodytas administravimo filtras auth, tai kol kas jį pašaliname, nes autorizacijos mechanizmo dar neturime.

Vietoje
Route::group(array('before' => 'auth'), function() { ...
laikinai padarome
Route::group(array(), function() { ...
Taigi, dabar turime užpildyti AdminCitiesController funkcijas. Pirmiausia - sąrašas, kuris aprašomas funkcijoje index(). Jos tekstas paprastas:
public function index()
{
  return view('admin.cities.list', array('cities' => City::all()));
}
Dar reikia nepamiršti failo viršuje nurodyti, kad naudojame City:

use app\City;

Kuriame View failą ir perduodame jam visus miestus iš duomenų bazės. Paprasta, ar ne?

Primenu, ką reiškia užrašas admin.cities.list. Taškais atskiriami katalogai, tad šiuo atveju View failas yra resources/views/admin/cities/list.blade.php, jis atrodo taip:

@extends('base')

@section('content')
<h2>Miestai</h2>

<table>
<tr>
  <th>Miestas</th>
  <th>Veiksmai</th>
</tr>
@if ($cities->count() > 0)
  @foreach ($cities as $city)
  <tr>
    <td>{{ $city->title }}</td>
    <td>
      <a href="{{ url('admin/miestai/' . $city->id . '/edit') }}">Redaguoti</a>
    </td>
  </tr>
  @endforeach
@else
<tr>
  <td colspan="2">Duomenų nėra.</td>
</tr>
@endif
</table>
<br />
<a href="{{ url('admin/miestai/create') }}">Naujas miestas</a>
@stop

Greičiausiai pamatysime, kad duomenų nėra. Einame toliau ir bandome sukurti naują miestą. Controlleryje už įvedimo formą atsako funkcija create():

public function create()
{
  return view('admin.cities.create');
}
Tada sukuriame formą iš vieno lauko - resources/views/admin/cities/create.blade.php:
@extends('base')

@section('content')
<h2>Naujas miestas</h2>

<form action="{{ url('admin/miestai') }}" method="post">
{{ csrf_field() }}
Pavadinimas:
<br />
<input type="text" name="title" />
<br />
<input type="submit" value=" Saugoti " />
</form>
@stop
Atkreipkite dėmesį į funkciją csrf_field() - ją reikia iškviesti visose formose, kad būtų įvykdyta apsauga nuo CSRF - apie ją ir apie formas kalbėsime kitame skyrelyje. Ir galiausiai apdorokime išsaugojimą - už tai atsako funkcija store(). Saugojimą galime daryti dviem variantais - su new City ir jo užpildymu, arba su fillable laukais ir create() funkcija. Renkamės pirmąjį variantą:
public function store(Request $request)
{
  $city = new City;
  $city->title = $request->input('title');
  $city->save();
  return redirect('admin/miestai');
}

Čia panaudojau naują struktūrą $request->input(), kuri skirta duomenų paėmimui iš formos - apie ją plačiau kalbėsime kitame skyrelyje. Bet norint panaudoti Request klasę, reikia nepamiršti failo pradžioje įrašyti:

use Illuminate\Http\Request;

Štai taip paprasta - tiesiog išsaugome ir grįžtame į sąrašą. Šiuo atveju gan stipriai viską supaprastiname - forma turės turėti validacijos mechanizmą, bet apie tai kalbėsime kituose skyreliuose. Kol kas tikslas - sukurti duomenų valdymo mechanizmą.

Taigi, toliau - informacijos redagavimas. Controlleryje už tai atsako funkcija edit:

public function edit($id)
{
  return view('admin.cities.edit', array('city' => City::find($id)));
}

Ir sukuriame edit.blade.php, kuris beveik analogiškas create.blade.php failui, tik formos action yra kitoks bei perduodama reikšmė į formos laukelį.

Svarbus dalykas - kaip perduoti metodą PUT, juk HTML formos palaiko tik GET ir POST. Triukas - pridėti hidden lauką, pagal kurį Laravel atpažins metodą. Vėliau, kai kalbėsime apie formų apdorojimą, parodysiu kaip tai padaryti lengviau su Laravel.

@extends('base')

@section('content')
<h2>Miesto redagavimas</h2>

<form action="{{ url('admin/miestai/' . $city->id) }}" method="post">
<input name="_method" type="hidden" value="PUT">
{{ csrf_field() }}
Pavadinimas:
<br />
<input type="text" name="title" value="{{ $city->title }}" />
<br />
<input type="submit" value=" Saugoti " />
</form>
@stop

Ir dabar atnaujiname duomenis - su metodu PUT perduodami formos parametrai ateina į Controllerio metodą update(). Išsaugome viską keliomis eilutėmis:

public function update(Request $request, $id)
{
  $city = City::find($id);
  $city->title = $request->input('title');
  $city->save();
  return redirect('admin/miestai');
}

Kaip matote, čia taip pat naudojame Request klasę, ir taip pat perduodame $id kaip parametrą, kurį Laravel "paims" automatiškai.

Na ir liko paskutinis veiksmas - delete(). Kaip ir su PUT metodu, DELETE metodas neperduodamas tiesiogiai per formą ar per nuorodą, tad teks padaryti formą su hidden lauku _method. Sąrašo view failą redaguojame ir konkretaus miesto eilutės kodą praplečiame iki tokio:
<a href="{{ url('admin/miestai/' . $city->id . '/edit') }}">Redaguoti</a>
<form style="display:inline" action="{{ url('admin/miestai/' . $city->id) }}"
  method="post" onsubmit="return confirm('Ar tikrai?')">
  <input type="hidden" name="_method" value="DELETE" />
  {{ csrf_field() }}
  <input type="submit" value="Trinti" />
</form>

Na o pats trynimas tai viena Laravel Controllerio eilutė:

public function destroy($id)
{
  City::find($id)->delete();
  return redirect('admin/miestai');
}
Štai ir viskas - sukūrėme miestų valdymo funkciją. Pabaigoje - dar kažkiek pastabų apie tai, ką ką tik padarėme:
  • Čia naudojome taip vadinamą RESTful Resource Controllerių tipą, bet galima tą patį padaryti ir su paprastais Controlleriais, patiems aprašant funkcijas, pvz getDelete() ar postUpdate(), tada nereikės naudoti metodų PUT arba DELETE - viską galima pasidaryti per GET ir POST. Tai tą antrąjį variantą galite pasitreniruoti patys.
  • Kaip jau minėjau, realiame projekte viskas būtų kur kas sudėtingiau - formos validacija, klaidų apdorojimas ir t.t. Čia viską labai supaprastinau iki demo-varianto.
  • Pats formos kūrimas ir apdorojimas gavosi gana sudėtingas, bet Laravel turi įrankių kaip visa tai palengvinti - apie tai kalbėsime kitoje pamokoje.
 

Namų darbai

Nagi, atspėkite iš trijų kartų, ką užduosiu namų darbams :) Taip, teisingai, padaryti kitų dviejų skilčių valdymą - aikštelių tipų ir pačių aikštelių. Nes be praktikos nebus realių rezultatų ir progreso. Bet jei jau labai užstrigsite - galite pasitikrinti su mano variantu, kurį parsisiųskite iš čia.


(c) 2015-2018. Visais klausimais kreipkitės povilas@laraveldaily.com