W dzisiejszym wpisie chciałbym opisać jak w prosty i szybki sposób stworzyć od podstaw aplikację typu CRUD w Laravel 5 i MySQL. Aplikacja będzie przechowywała listę książek, w tym tytuł, cenę, wydawcę i ilość stron. Przejdźmy do konkretów.
1. Tworzymy bazę:
1 |
CREATE DATABASE IF NOT EXISTS `library`; |
po czym tworzymy pierwszą i ostatnią tabelę:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
-- Zrzut struktury tabela library.book CREATE TABLE IF NOT EXISTS `books` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(1024) COLLATE utf8_polish_ci NOT NULL, `bookPages` int(11) DEFAULT NULL, `price` int(11) NOT NULL, `publisher` varchar(20) COLLATE utf8_polish_ci NOT NULL, `updated_at` DATETIME NULL, `created_at` DATETIME NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COLLATE=utf8_polish_ci; /*!40000 ALTER TABLE `books` DISABLE KEYS */; INSERT INTO `books` (`id`, `title`, `bookPages`, `price`, `publisher`) VALUES (1, 'Hobbit', 245, 35, 'Fantasy Publishing'); /*!40000 ALTER TABLE `books` ENABLE KEYS */; /*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; /*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; |
Jak widzimy jest to prosta tabela reprezentująca książkę, a składająca się z pięciu pól: identyfikatora, tytułu, ilości stron, ceny, a także wydawcy. W celach testowych dodany został również jeden wpis książki o tytule ‘Hobbit’.
2. Za pomocą Composera (getcomposer.org) dodajemy nowy projekt:
1 |
composer create-project laravel/laravel library --prefer-dist |
W tym miejscu należy pamiętać o stworzeniu wirtualnych hostów dla naszej testowej aplikacji.
3. Modyfikujemy plik library/config/database.php:
1 2 3 4 5 6 7 8 9 10 11 |
'mysql' => [ 'driver' => 'mysql', 'host' => env('DB_HOST', 'localhost'), 'database' => env('DB_DATABASE', 'library'), 'username' => env('DB_USERNAME', 'root'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', 'strict' => false, ], |
Modyfikacje dokonujemy również poprzez edycję plików z dostępami:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
APP_ENV=local APP_DEBUG=true APP_KEY=8GiB3X5AQgan7bY8csqSEcwLDi69zpR1 DB_HOST=localhost DB_DATABASE=library DB_USERNAME=root DB_PASSWORD= CACHE_DRIVER=file SESSION_DRIVER=file QUEUE_DRIVER=sync MAIL_DRIVER=smtp MAIL_HOST=mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=null MAIL_PASSWORD=null |
- Tworzymy model książki:
1 |
php artisan make:model Book |
Dzięki tej instrukcji powstanie:
app/ Book.php:
1 2 3 4 5 6 7 |
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Book extends Model { // } |
- Tworzymy kontroler książki używając Artisana:
1 |
php artisan make:controller BookController |
W ten sposób w katalogu app/Http/Controllers powstał plik BookController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
<?php namespace App\Http\Controllers; use Request; use App\Http\Controllers\Controller; use Illuminate\Http\Request; class BookController extends Controller { /** * Display a listing of the resource. * * @return Response */ public function index() { // } /** * Show the form for creating a new resource. * * @return Response */ public function create() { // } /** * Store a newly created resource in storage. * * @return Response */ public function store() { // } /** * Display the specified resource. * * @param int $id * @return Response */ public function show($id) { // } /** * Show the form for editing the specified resource. * * @param int $id * @return Response */ public function edit($id) { // } /** * Update the specified resource in storage. * * @param int $id * @return Response */ public function update($id) { // } /** * Remove the specified resource from storage. * * @param int $id * @return Response */ public function destroy($id) { // } } |
Kontroler ten będzie miał za zadanie zarządzanie naszym systemem, przy czym widzimy, iż Artisan stworzył szablon kontrolera, który następnie będziemy sukcesywnie uzupełniać o kolejne linijki kodu.
- Dodajemy routing dla naszej aplikacji CRUD.
Laravel przechowuje wszystkie dane na temat routingu w pliku app/http/routes.php. Jeśli podejrzymy plik to zobaczymy:
1 2 3 4 5 6 7 8 |
Route::get('/', 'WelcomeController@index'); Route::get('home', 'HomeController@index'); Route::controllers([ 'auth' => 'Auth\AuthController', 'password' => 'Auth\PasswordController', ]); |
W Laravelu ruting zorganizowany został w sposób jasny i klarowny. Odpowiedni wpis deklarujemy za pomocą funkcji odpowiadającej typowi żądania:
Route::typ_żądania(…)
np.
– Route::get(…) – żądania typu GET
– Route::post(..) – żądania typu POST
– Route::any(…) – żądanie dowolnego typu
– Route::get(‘user/{id}’, function($id)}); – żądanie z parametrem ID
– Route::get(‘user/{id?}’, function($id)}); – żądanie z opcjonalnym parametrem ID
W żądaniach można również swobodnie korzystać z wyrażeń regularnych.
W pierwszej kolejności chcielibyśmy w naszym systemie wyświetlić listę książek, także użyjemy metody GET, dodatkowo zdefiniujmy metody do dodawania, edytowania, aktualizowania i usuwania książek, tak będzie prezentował się cały plik routes.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?php Route::get('/', 'WelcomeController@index'); Route::get('home', 'HomeController@index'); Route::controllers([ 'auth' => 'Auth\AuthController', 'password' => 'Auth\PasswordController', ]); Route::get('books', 'BookController@index'); Route::get('books/create', 'BookController@create'); Route::get('books/edit/{id}', 'BookController@edit'); Route::get('books/ destroy /{id}', 'BookController@destroy'); Route::post('books/store', 'BookController@store'); Route::post('books/update', 'BookController@update'); |
Nasz pierwszy wpis
Route::get(‘books’, ‘BookController@index’);
oznacza, iż ‘books’ zostanie zmapowane do kontrolera ‘BookController’ i metody ‘index’. Do pozostałych wpisów wrócimy w dalszej części kursu.
- Przechodzimy więc do naszego kontrolera app/http/BookController.php na samej górze dodajemy wpis modelu książki:
1 |
use App\Book; |
i odpowiednio modyfikujemy metodę index:
1 2 3 4 5 |
public function index() { $booksList=Book::all(); return view('books.list', ['books' => $booksList]); } |
W powyższej metodzie za pomocą metody all() obiektu Eloquent pobraliśmy wszystkie książki i przypisaliśmy je do zmiennej ‘booksList’. Następnie zwróciliśmy obiekt widoku o parametrze pierwszym równym adresowi widoku (w naszym przypadku będzie to resources/views/books/list.blade.php) a także przekazujemy do tego obiektu listę książek w postaci obiektu booksList.
8. W tym kroku musimy stworzyć widok wyświetlający listę książek. Najpierw stwórzmy jednak szablon naszej strony, nasz główny template:
app/resources/views/template.blade.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<!doctype html> <html> <head> <meta charset="utf-8"> </head> <body> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css"> <link rel="stylesheet" href="https://cdn.datatables.net/1.10.6/css/jquery.dataTables.min.css"/> <script src="https://code.jquery.com/jquery-1.11.1.min.js"></script> <script src="https://cdn.datatables.net/1.10.6/js/jquery.dataTables.min.js"></script> <div class="container"> @if (Session::has('message')) <div class="flash alert"> <p>{{ Session::get('message') }}</p> </div> @endif @yield('content') </div> </body> </html> |
Nasz szablon korzysta z systemu dołączonego do Laravela o nazwie “Blade”. Wszystkie pliki korzystające z instrukcji Blade powinny kończyć się na *.blade.php. Jak widzimy na przykładowym kodzie Blade jest ciekawym silnikiem umożliwiającym szybkie i czyste zarządzanie kodem widoków. Powyższy kod za pomocą instrukcji IF sprawdza, czy w sesji nie ma zapisanej żadnej wiadomości dla użytkownika. Jeśli tak to oczywiście wyświetla ją. Instrukcja definiująca miejsce umieszczenia właściwej części strony dynamicznie się zmieniającej to oczywiście @yeild(‘content’). Jak się łatwo domyślić, nazwa ‘content’ to etykieta identyfikująca dany obszar przeznaczony na główną treść widoku.
- Kolejny krok to stworzenie pojedynczego widoku listy książek. W tym celu tworzymy plik resources/views/books/list.blade.php o następującej treści:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
@extends('../template') @section('content') <script> $(document).ready(function(){ $('.dataTable').DataTable();}); </script> <div class="page-header" > <h1>Library</h1> </p></div> <div class="panel panel-default"> <div class="panel-body"> <a href="{{ action('BookController@create') }}" class="btn btn-info">Add New Book</a> </div> </div> @if ($books->isEmpty()) There are no books! @else <table class="dataTable"> <thead> <tr> <th>Title</th> <th>Number of pages</th> <th>Price</th> <th>Publisher</th> <th>Options</th> </tr> </thead> <tbody> @foreach($books as $book) <tr> <td>{{ $book->title }}</td> <td>{{ $book->bookPages }}</td> <td>{{ $book->price }}</td> <td>{{ $book->publisher }}</td> <td> <a href="{{ action('BookController@edit', $book->id) }}" class="btn btn-default">Edit</a> <a href="{{ action('BookController@destroy ', $book->id) }}" class="btn btn-danger">Delete</a> </td> </tr> @endforeach </tbody> </table> @endif @stop |
Pierwsza linia to oczywiste odwołanie do katalogu wyżej i pliku z naszym szablonem. W drugiej linii definiujemy start sekcji ‘content’. Sekcja kończy się w ostatniej linijce pliku instrukcją ‘@stop’. W ciele sekcji definiujemy znacznik odnośnika z adresem do kontrolera BookController i metody create – będzie to odnośnik służący do dodania nowej pozycji (jak widzimy w sekcji adresu podaliśmy tą samą wartość zdefiniowaną w naszym pliku routes.php).
W kolejnych linijkach sprawdzamy czy podany do widoku obiekt nie jest pusty (czy nie zawiera żadnych wpisów). W takim wypadku wyświetlany jest stosowny komunikat, w przeciwnym wyświetlana zostanie lista książek utworzona za pomocą pętli foreach iterującej po każdym elemencie listy. Obok każdej książki tworzymy sparametryzowany odnośnik do opcji usunięcia lub edycji książki:
1 2 |
<a href="{{ action('BookController@edit', $book->id) }}" class="btn btn-default">Edit</a> <a href="{{ action('BookController@destroy ', $book->id) }}" class="btn btn-danger">Delete</a> |
Jeśli teraz uruchomimy przeglądarkę internetową i spróbujemy uruchomić efekty naszej pracy to pod adresem:
[nazwaserwera]/books
powinniśmy ujrzeć:
- Kolejny krok to stworzenie widoku i funkcjonalności dodawania nowej książki.
Jako, że odpowiedni routing mamy już ustawiony (wpis:
1 |
Route::get('books/create', 'BookController@create'); |
) wystarczy teraz uzupełnić metodę ‘create()’ klasy BookController:
1 2 3 4 5 |
public function create() { //add new book form return view('books.create'); } |
(metoda ta ma za zadanie jedynie zwrócenie widoku o nazwie ‘books.create’), a następnie dodać widok create.blade.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
@extends('../template') @section('content') <div class="page-header" > <h1>Add new book </h1> </div> @if ( $errors->count() > 0 ) <div class="alert alert-danger"> <ul> @foreach( $errors->all() as $message )</p> <li>{{ $message }}</li> @endforeach </ul> </div> @endif <form action="{{ action('BookController@store') }}" method="post" role="form" > <input type="hidden" name="_token" value="{{{ csrf_token() }}}" /> <div class="form-group"> <label for="title">Title</label> <input type="text" class="form-control" name="title" /> </div> <div class="form-group"> <label for="bookPages">Number of pages</label><br /> <input type="text" class="form-control" name="bookPages" /> </div> <div class="form-group"> <label for="email">Price</label><br /> <input type="text" class="form-control" name="price" /> </div> <div class="form-group"> <label for="email">Publisher</label><br /> <input type="text" class="form-control" name="publisher" /> </div> <input type="submit" value="Add" class="btn btn-primary" /> <a href="{{ action('BookController@index') }}" class="btn btn-link">Return</a> </form> @stop |
resources/views/books/create.blade.php
Podobnie jak w przypadku pierwszego widoku tak i tu musimy zadeklarować szablon, do którego przypiszemy widok, a także samą treść pojedynczej strony. Jako miejsce docelowe formularza dodajemy adres:
‘BookController@store’ wcześniej zdefiniowany w pliku routes.php. Warto również zwrócić uwagę na linijkę:
<input type=”hidden” name=”_token” value=”{{{ csrf_token() }}}” />
Jako, że używamy natywnych kontrolek HTMLowych Laravel wymaga by w formularzu przesyłać również token CSRF – co też za pomocą ukrytego pola czynimy.
Jeśli uruchomimy teraz adres serwer/books/create powinniśmy ujrzeć poniższy formularz:
- W kolejnym kroku musimy dane pobrane z formularza zapisać w bazie. W tym celu po raz kolejny edytujemy kontoler BookController i metodę ‘store’:
1 2 3 4 5 6 7 8 9 10 |
public function store() { $book = new Book; $book->title = Request::input('title'); $book->bookPages = Request::input('bookPages'); $book->price = Request::input('price'); $book->publisher = Request::input('publisher'); $book->save(); return redirect()->action('BookController@index'); } |
Cóż ta metoda robi? Przede wszystkim tworzy nowy obiekt książki, następnie przypisuje do jej pól wartości pobrane z formularza uzyskane poprzez odwołanie do obiektu żądania i metody ‘input’. W ostatnim kroku obiekt książki zapisuje zmiany a aplikacja przenosi użytkownika do karty listy książek.
12. W kolejnym kroku zajmiemy się edytowaniem książek. W tym celu tworzymy no widok edycji:
/resources/views/books/edit.blade.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
@extends('../template') @section('content') <div class="page-header" > <h1>Edit</h1> </div> @if ( $errors->count() > 0 ) <div class="alert alert-danger"> <ul> @foreach( $errors->all() as $message )</p> <li>{{ $message }}</li> @endforeach </ul> </div> @endif <form action="{{ action('BookController@update') }}" method="post" role="form" > <input type="hidden" name="id" value="{{ $book->id }}"> <div class="form-group"> <label for="title">Title</label> <input type="text" class="form-control" name="title" value="{{ $book->title }}"/> <input type="hidden" name="_token" value="{{{ csrf_token() }}}" /> </div> <div class="form-group"> <label for="bookPages">Number of pages</label><br /> <input type="text" class="form-control" name="bookPages" value="{{ $book->bookPages }}"/> </div> <div class="form-group"> <label for="email">Price</label><br /> <input type="text" class="form-control" name="price" value="{{ $book->price }}"/> </div> <div class="form-group"> <label for="email">Publisher</label><br /> <input type="text" class="form-control" name="publisher" value="{{ $book->publisher }}"/> </div> <input type="submit" value="Add" class="btn btn-primary" /> <a href="{{ action('BookController@index') }}" class="btn btn-link">Return</a> </form> @stop |
Widzimy, że widok ten niewiele różni się od widoku dodawania nowej książki. Jedyne istotne zmiany to:
– zmiana skryptu docelowego formularza, tutaj użyliśmy metody ‘update’ kontrolera ‘BookController’. Używamy przy tym routingu zdefiniowanego wcześniej w routes.php
1 |
Route::post('books/update, 'BookController@update); |
Sama metoda update prezentuje się następująco (w pierwszej linii jej działanie sprowadza się do odnalezienia książki na podstawie id pobranego z formularza, następnie do pól książki przypisywane są wartości pobrane z pól formularza, obiekt książki jest zapisywany – a na samym końcu wykonywane jest przekierowanie do spisu książek):
/app/Http/Controllers/BookController.php:metoda update()
1 2 3 4 5 6 7 8 9 10 |
public function update() { $book = Book::find(Request::input('id')); $book->title = Request::input('title'); $book->bookPages = Request::input('bookPages'); $book->price = Request::input('price'); $book->publisher = Request::input('publisher'); $book->save(); return redirect()->action('BookController@index'); } |
– do każdej formatki HTMLowej dodana została wartość dla danego pola edytowanego przedmiotu
No dobrze, mamy już zrobioną obsługę formularza edycji, a także sam wygląd formularza. Stwórzmy teraz kod obsługujący jego wyświetlenie. Posłuży do tego metoda edit($id) kontrolera Book:
/app/Http/Controllers/BookController.php:metoda edit()
1 2 3 4 5 |
public function edit( $id) { $book = Book::find($id); return view('books.edit', ['book' => $book]); } |
Metoda ta jest trywialnie prosta, za argument przyjmuje identyfikator książki, po czym w pierwszej linii pobiera z bazy książkę na podstawie ów ID, po czym przekazuję ją do widoku edycji. Warto dodać, iż również ten kontroler ma zdefiniowany swój własny routing w pliku routes.php.
Tak stworzona funkcjonalność powinna działać poprawnie – czyli wyświetlać formularz, a następnie go pozytywnie zapisać.
- Ostatnim elementem naszej aplikacji książek będzie opcja usunięcia książki. W tym celu stworzony został sparametryzowany przycisk “Delete”. Zgodnie z naszą tabelą routingu (plik routes.php), za usuwanie odpowiadać będzie metodą destroy ($id) kontrolera Book:
1 2 3 4 5 6 |
public function destroy($id) { $book = Book::find($id); $book->delete(); return redirect()->action('BookController@index'); } |
Jak widać, metoda ta pobiera id książki, odnajduje ją w bazie, po czym przy pomocy instrukcji delete() usuwa ją z dostępnych rekordów.
Tak oto stworzyliśmy w pełni działającą aplikację CRUD w Laravelu 5. Jest to niezwykle ciekawy framework, zdobywający przy tym co raz większą popularność.
Oczywiście powyższy przykład to minimalny start do frameworka. Wiele aspektów można poprawić (oddzielny katalog dla modeli, użycie Dependency Injection), ale na początek myślę, ze to wystarczy.
Zachęcam do zgłębienia tematu i obserwowania kolejnych wpisów, gdyż takie o Laravelu na pewno się jeszcze pokażą.
Kod źródłowy aplikacji: library
Baza: library
Bardzo przydatny wpis! Dzięki wielkie 🙂
Szaleństwo 🙂 Nie sądziłem, że tak łatwo i szybko można coś stworzyć. Bardzo pomocny opis. Pozdrawiam. 🙂
Krok 5: Jakim cudem powstal plik BookController.php skoro zostala wywolana komenda: “php artisan make:controller BooksController”? Ma byc Book? czy Books?
Krok 8: Jest napisane, ze template ma byc stworzony w “app/resources/template.blade.php”… moze chodzilo o “app/resources/views/template.blade.php”?
Kurs bardzo fajny, ale duzo drobnych bledow ktore uniemozliwiaja uruchomienie aplikacji :/
Wyglada jakby autor pisal kod z glowy, a sam nie przetestowal czy to dziala.
Wykonujac kroki 1-9 nie jestem w stanie uruchomic widoku “server/books”
Tak, ma Pan rację. Dziękuję za wskazanie nieścisłości, poprawki już zostały naniesione. Żeby rozwiać resztę wątpliwości zamieszczam kod źródłowy na końcu wpisu. Dziękuję za konstruktywny komentarz!
Witam.
Przeglądając bloga oraz szukając pewnych informacji w internecie. Próbuje znaleźć powiązania między Laravel a Angular. Czy te dwa frameworki da się się wspólnie połączyć, oczywiście nie na siłę, ale jako harmonijna współpraca między nimi? Jest sens pracować przy projekcie na tych dwóch frameworkach?
Witam,
oczywiście, te dwa frameworki nie dość, że da się połączyć, to nawet należy to robić.
Laravel świetnie nadaje się jako część serwerowa, backend, który udostępnia pewne API, czyli pewien zakres funkcjonalności, gdzie formatem danych będzie np. JSON.
Takie API może być wykorzystane właśnie przez Angulara do stworzenia logiki frontendu strony www. Proszę zauważyć ile to przyniesie korzyści – w momencie gdy będzie potrzeba napisania do naszego systemu aplikacji mobilnej (oprócz istniejącej strony www w Angularze) – wystarczy połączyć się z nią z pomocą istniejącego API.