--- title: Laravel [01] (Una app CRUD) tags: daw, Laravel, rutes, M7 --- <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licencia de Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a><br />Este obra está bajo una <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">licencia de Creative Commons Reconocimiento-NoComercial-CompartirIgual 4.0 Internacional</a>. [Enllaç a Hackmk.io](https://hackmd.io/@JdaXaviQ/S1-ILmdxn) ## Laravel [01] (Una app CRUD, quick but not very dirty). ### Creació i configuració del projecte: ```bash= $ cd /home/isard/src $ composer create-project laravel/laravel una_app_crud ``` A continuació configurem el nostre Apache2 per a que serveixi la nostra app: Afegim les següents línies al nostre fitxer de configuració /etc/apache2/apache2.conf ``` <Directory /home/isard/src/una_app_crud/> Options Indexes FollowSymLinks AllowOverride All Require all granted </Directory> ``` I canviem el virtual host per defecte per a que apunti a aquesta nova ubicació: ``` DocumentRoot /home/isard/src/una_app_crud/public ``` Modifiquem un parell de permisos per a que l'usuari www-data pugui accedir còmodament a la nostra aplicació, reiniciem Apache i provem la nostra nova app. ```bash= $ chmod 751 /home/isard $ chmod -R 777 /home/isard/src/una_app_crud/storage $ sudo systemctl restart apache2 $ firefox localhost/ ``` ![](https://i.imgur.com/HewVTNr.png) ### Generant contingut Primer de tot anem a preparar la nostra pàgina principal. Heu de reproduir la estructura de carpetes i fitxers següents: ![](https://i.imgur.com/rWD3t3L.png) ```bash= $ mkdir -p /home/isard/src/una_app_crud/resources/views/layouts/partials $ cd /home/isard/src/una_app_crud/resources/views/layouts/partials $ touch footer.blade.php header.blade.php meta.blade.php ../master.blade.php ``` Contigut de meta.blade.php ```php= <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>@yield('pageTitle') | MyCrudApp</title> ``` Contingut de header.blade.php: ```php= <nav class="navbar"> <a class="navbar-brand" href="{{asset('/')}}">MyCrudApp</a> <div class="nav_container" id="navContainer"> <ul class="nav_ul"> <li class="nav-item"> <a class="nav-link" href="{{asset('/')}}">Home</a> </li> <li class="nav-item"> <a class="nav-link" href="#" id="navMenuLink"> Menu </a> </li> </ul> </div> </nav> ``` Contingut de footer.blade.php ```php= <h3>Peu de pàgina</h3> ``` Contingut de master.blade.php ```php= <!DOCTYPE html> <html lang="ca"> <head> @include('layouts.partials.meta') @yield('scripts') </head> <body> @include('layouts.partials.header') <div class="container"> @yield('content') </div> @include('layouts.partials.footer') </body> </html> ``` Contingut de welcome.blade.php: ```php= @extends('layouts.master') @section('pageTitle', 'Home') @section('content') <div class="contingut"> <h1>Hola, tècnics superiors en desenvolupament!</h1> <p>Una altra app CRUD, però aquesta feta amb Laravel.</p> </div> @endsection ``` ### La persistència de dades. Com que som a una classe, intentaré ser original i les nostres dades aniran sobre estudiants; de moment tindrem una única taula anomenada estudiants, amb els següents camps: * id * nom * cognom * edat * email Passes a seguir: 1.- Crear una nova base de dades. 2.- Connectar el nostre projecte Laravel a la nostra base de dades mySQL. 3.- Generar la nostra migració d'estudiants. 4.- Executar la migració. 5.- Crear un model per a la taula estudiants. #### Creació de la base de dades: Es possible crear la base de dades directament des de laravel si s'escau, però no ho he considerat adient per la nostra activitat. ```bash= $ sudo mysql MariaDB [(none)]> create database laraveldb; Query OK, 1 row affected (0,002 sec) MariaDB [(none)]> ALTER DATABASE laraveldb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; Query OK, 1 row affected (0,001 sec) MariaDB [(none)]> create user 'laravel'@'localhost' identified by '1234'; Query OK, 0 rows affected (0,008 sec) MariaDB [(none)]> grant all privileges on laraveldb.* to 'laravel'@'localhost'; Query OK, 0 rows affected (0,003 sec) MariaDB [(none)]> flush privileges; Query OK, 0 rows affected (0,002 sec) ``` Ara configurem el fitxer .env per a subministrar-li les credencials de connexió a la base de dades. ```bash= DB_CONNECTION=mysql DB_HOST=localhost DB_PORT=3306 DB_DATABASE=laraveldb DB_USERNAME=laravel DB_PASSWORD=1234 ``` #### La nostra primera migració. ```bash= $ cd /home/isard/src/una_app_crud/ $ php artisan make:migration create_estudiants_table INFO Migration [database/migrations/2023_03_26_102803_create_estudiants_table.php] created successfully. ``` Aquesta darrera comanda ens hauria d'haver creat un nou fitxer a la carpeta /database/migrations/ amb un nom semblant a: __2023_02_26_102803_create_estudiants_table.php__ Si editem aquest fitxer trobarem dues funcions: up() i down(), aquestes funcions són les encarregades de crear i destruir la taula estudiants quan ho necessitem i les podem editar al nostre gust, per exemple per afegir-hi més camps a la nostra futura nova taula. En el nostre cas, modificarem la funció up() i la deixarem així: ```php= public function up(): void { Schema::create('estudiants', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('nom'); $table->string('cognom'); $table->integer('edat'); $table->string('email'); $table->timestamps(); }); } ``` Abans de còrrer la nostra primera migració ens hem d'assegurar que tenim instal·lats els connectors adients per a la nostra base de dades: ```bash= $ sudo apt install php-mdb2-driver-mysql $ php artisan migrate INFO Preparing database. Creating migration table ......................................... 25ms DONE INFO Running migrations. 2014_10_12_000000_create_users_table ............................ 485ms DONE 2014_10_12_100000_create_password_reset_tokens_table ............. 39ms DONE 2019_08_19_000000_create_failed_jobs_table ....................... 35ms DONE 2019_12_14_000001_create_personal_access_tokens_table ........... 125ms DONE 2023_03_26_103552_create_estudiants_table ........................ 17ms DONE ``` Com poder apreciar a continuació se n'han creat diverses taules relacionades amb el nostre projecte a la base de dades, entre elles la que contindrà la informació dels nostres estudiants: ![](https://i.imgur.com/hsW1Nfq.png) ![](https://i.imgur.com/A2QkkTf.png) En aquest punt tenim una taula que al haver-se creat sota el control de Laravel, també pot èsser actualitzada des de Laravel mitjançant migracions. #### El model Estudiant. Ara ens toca crear el seu model i això o com a mínim la part inicial també li podem delegar a Artisan: ```bash= $ php artisan make:model Estudiant ``` i ens crearà el fitxer: 'una_app_crud/app/Models/Estudiant.php' que per dins tindrà aquesta pinta: ```php= <?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Estudiant extends Model { use HasFactory; } ``` Modificarem el codi per afegir una propietat que combinada amb Eloquent i Laravel omplir de forma massiva les propietats dels _nostres estudiants_. ```php= <?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Estudiant extends Model { use HasFactory; protected $fillable = [ 'nom', 'cognom', 'edat', 'email' ]; } ``` No us preocupeu gaire si no veieu com utilitzar aquesta propietat ara mateix, quedarà força més clar més endavant amb un exemple més endavant. ### El controlador EstudiantController. Deixarem aquí el desenvolupament de la persistència dels nostres estudiants i passarem a implementar el controlador associat. Encarreguen a Artisan que generi el controlador EstudiantController per nosaltres i li passem el paràmetre --resource per a indicar-li que volem implementar el CRUD complert. ```bash= ~/src/una_app_crud$ php artisan make:controller EstudiantController --resource INFO Controller [app/Http/Controllers/EstudiantController.php] created successfully. ~/src/una_app_crud$ cat ./app/Http/Controllers/EstudiantController.php <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\Estudiant; class EstudiantController extends Controller { /** * Display a listing of the resource. */ public function index() { // } /** * Show the form for creating a new resource. */ public function create() { // } /** * Store a newly created resource in storage. */ public function store(Request $request) { // } /** * Display the specified resource. */ public function show(string $id) { // } /** * Show the form for editing the specified resource. */ public function edit(string $id) { // } /** * Update the specified resource in storage. */ public function update(Request $request, string $id) { // } /** * Remove the specified resource from storage. */ public function destroy(string $id) { // } } ``` :bomb: Boooom! Ens ha generat un controlador amb l'esquelet de les funcions necessàries per realitzar un CRUD complert. Per a que puguem enllaçar-lo amb el seu model introduirem la següent línia al codi generat del fitxer EstudiantController: ```php= use App\Models\Estudiant; ``` #### funció index() És la funció encarregada de retornar el llistat complert de tots els estudiants. ```php= public function index() { $estudiants = Estudiant::all(); return view('estudiants.index', compact('estudiants','estudiants')); } ``` #### funció create() Ens durà a una vista on podrem crear un nou estudiant des de zero. ```php= public function create() { return view('estudiants.create'); } ``` #### funció store() Desa un nou usuari, per exemple un acabat de crear desprès de cridar a la funció create(). ```php= public function store(Request $request) { $this->validate($request, [ 'nom' => 'required', 'cognom' => 'required', 'edat' => 'required|numeric', 'email' => 'required|email', ]); $input = $request->all(); Estudiant::create($input); return redirect()->route('estudiants.index'); } ``` #### funció show() Obté un estudiant a partir de la seva id. ```php= public function show($id) { $estudiant = Estudiant::findOrFail($id); return view('estudiants.show', compact('estudiant','estudiant')); } ``` #### funció edit() De la mateixa forma que show(), obté un estudiant a partir de la seva id, però en aquest cas ens el presenta en un formulari per a poder-lo modificar. ```php= public function edit($id) { $estudiant = Estudiant::find($id); return view('estudiants.edit', compact('estudiant','estudiant')); } ``` #### funció update() Recupera un estudiant de la base de dades, el modifica amb les dades passades com a parámetre i desa les modificacions a la base de dades. ```php= public function update(Request $request, $id) { $estudiant = Estudiant::findOrFail($id); $this->validate($request, [ 'nom' => 'required', 'cognom' => 'required', 'edat' => 'required|numeric', 'email' => 'required|email', ]); $input = $request->all(); $estudiant->fill($input)->save(); return redirect()->route('estudiants.index'); } ``` #### funció destroy() Esborra un estudiant de la base de dades. ```php= public function destroy($id) { $estudiant = Estudiant::findOrFail($id); $estudiant->delete(); return redirect()->route('estudiants.index'); } ``` #### Rutes: Totes aquestes funcions no poden ser executades si no les enllaçem amb el client a través de rutes, per exemple: > Route::get('/estudiants/index', [EstudiantController::class, 'index']); Però no serà aquesta la manera en que generarem les nostres rutes per a realitzar el CRUD dels nostres usuaris. Aprofitarem que varem crear el nostre controlador amb el modificador --resource i que el CRUD el fem amb les funcions generades automàticament per Laravel per a utilitzar una ruta especial de Laravel que aten totes les crides CRUD sense haver d'especificar-les una a una. Afegirem la següent línia al fitxer: routes/web.php > Route::resource('estudiants', '\App\Http\Controllers\EstudiantController'); Un cop arrivats a aquest punt, hariem poder veure les següents rutes si li preguntem a artisan quines rutes té el nostre projecte: ```php= ~/src/una_app_crud$ php artisan route:list GET|HEAD / ................................................................................................................................. POST _ignition/execute-solution .......................... ignition.executeSolution › Spatie\LaravelIgnition › ExecuteSolutionController GET|HEAD _ignition/health-check ...................................... ignition.healthCheck › Spatie\LaravelIgnition › HealthCheckController POST _ignition/update-config ................................... ignition.updateConfig › Spatie\LaravelIgnition › UpdateConfigController GET|HEAD api/user .......................................................................................................................... GET|HEAD estudiants ........................................................................... estudiants.index › EstudiantController@index POST estudiants ........................................................................... estudiants.store › EstudiantController@store GET|HEAD estudiants/create .................................................................. estudiants.create › EstudiantController@create GET|HEAD estudiants/{estudiant} ................................................................. estudiants.show › EstudiantController@show PUT|PATCH estudiants/{estudiant} ............................................................. estudiants.update › EstudiantController@update DELETE estudiants/{estudiant} ........................................................... estudiants.destroy › EstudiantController@destroy GET|HEAD estudiants/{estudiant}/edit ............................................................ estudiants.edit › EstudiantController@edit GET|HEAD sanctum/csrf-cookie ............................................. sanctum.csrf-cookie › Laravel\Sanctum › CsrfCookieController@show Showing [13] routes ``` ### Vistes. #### Vista index. Aquesta vista serà el cor de la nostra aplicació, des d'aquí crearem els enllaços a la resta de funcionalitats. * Creació del fitxer de la vista ```bash= $ cd /home/isard/src/una_app_crud/ $ mkdir -p ./resources/views/estudiants $ touch ./resources/views/estudiants/index.blade.php ``` Omplir el fitxer index.blade.php que acavem de crear amb el següent contingut: ```php= @extends('layouts.master') @section('pageTitle', 'Index Estudiants') @section('content') <h1>Index d'estudiants</h1> <a href="{{route('estudiants.create')}}">Create New</a> <hr/> <table class="table"> <thead> <th>Nom</th> <th>Cognom</th> <th>Edat</th> <th>Email</th> <th colspan="3">Accions</th> </thead> <?php $even = FALSE; ?> @foreach($estudiants as $estudiant) <tr class= <?php echo $even ? '"even_row"' : '"odd_row"'; $even = !$even ?>> <td>{{$estudiant->nom}}</td> <td>{{$estudiant->cognom}}</td> <td>{{$estudiant->edat}}</td> <td>{{$estudiant->email}}</td> <td> <div> <a href="{{route('estudiants.show', $estudiant->id)}}" >Details</a> <a href="{{route('estudiants.edit', $estudiant->id)}}" >Edit</a> <form class="inline_form" action="{{ route('estudiants.destroy', $estudiant->id) }}" method="POST"> <input type="hidden" name="_method" value="DELETE"> <input type="hidden" name="_token" value="{{ csrf_token() }}"> <button>Delete User</button> </form> </div> </td> </tr> @endforeach </table> @endsection ``` #### Vista create. En aquesta vista utilitzarem un paquet de Laravel anomenat [Laravel Collective](https://laravelcollective.com/docs/6.x/html) que ens ajudarà a manegar el contigut dels formularis HTML. Per a istal·lar el paquet anirem al directori arrel del nostre projecte i executarem la següent comanda: ```bash= $ cd /home/isard/src/una_app_crud $ composer require laravelcollective/html Info from https://repo.packagist.org: #StandWithUkraine ./composer.json has been updated Running composer update laravelcollective/html Loading composer repositories with package information Updating dependencies Lock file operations: 1 install, 0 updates, 0 removals - Locking laravelcollective/html (v6.4.0) Writing lock file Installing dependencies from lock file (including require-dev) Package operations: 1 install, 0 updates, 0 removals - Downloading laravelcollective/html (v6.4.0) - Installing laravelcollective/html (v6.4.0): Extracting archive Generating optimized autoload files > Illuminate\Foundation\ComposerScripts::postAutoloadDump > @php artisan package:discover --ansi INFO Discovering packages. laravel/sail ......................................................................................................................... DONE laravel/sanctum ...................................................................................................................... DONE laravel/tinker ....................................................................................................................... DONE laravelcollective/html ............................................................................................................... DONE nesbot/carbon ........................................................................................................................ DONE nunomaduro/collision ................................................................................................................. DONE nunomaduro/termwind .................................................................................................................. DONE spatie/laravel-ignition .............................................................................................................. DONE 80 packages you are using are looking for funding. Use the `composer fund` command to find out more! > @php artisan vendor:publish --tag=laravel-assets --ansi --force INFO No publishable resources for tag [laravel-assets]. No security vulnerability advisories found Using version ^6.4 for laravelcollective/html $ touch ./resources/views/estudiants/create.blade.php $ php artisan cache:clear $ sudo chmod -R 777 storage/ $ composer dump-autoload ``` Contingut de create.blade.php: ```php= @extends('layouts.master') @section('pageTitle', 'Create Estudiant') @section('content') <h1>Crea un nou estudiant</h1> <hr/> <!-- if validation in the controller fails, show the errors --> @if ($errors->any()) <div> <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif <!-- Open the form with the store function route. --> {{ Form::open(['action' => 'App\Http\Controllers\EstudiantController@store'])}} <!-- Include the CRSF token --> {{Form::token()}} <!-- build our form inputs --> <div class="form-group"> {{Form::label('nom', 'Nom')}} {{Form::text('nom', '', ['class' => 'form-control'])}} </div> <div class="form-group"> {{Form::label('cognom', 'Cognom')}} {{Form::text('cognom', '', ['class' => 'form-control'])}} </div> <div class="form-group"> {{Form::label('edat', 'Edat')}} {{Form::number('edat', '', ['class' => 'form-control'])}} </div> <div class="form-group"> {{Form::label('email', 'Adreça email')}} {{Form::text('email', '', ['class' => 'form-control'])}} </div> <!-- build the submission button --> {{Form::submit('Crear!', ['class' => 'btn btn-primary'])}} {{ Form::close() }} @endsection ``` #### Vista show. show.blade.php ```php= @extends('layouts.master') @section('pageTitle', 'Detalls Estudiant') @section('content') <h1>Detalls d'estudiant</h1> <hr/> <dl> <dt>Nom</dt> <dd>{{$estudiant->nom}}</dd> <dt>Cognom</dt> <dd>{{$estudiant->cognom}}</dd> <dt>Edat</dt> <dd>{{$estudiant->edat}}</dd> <dt>Email</dt> <dd>{{$estudiant->email}}</dd> </dl> <div> <a href="{{route('estudiants.edit', $estudiant->id)}}">Edit</a> <form action="{{ route('estudiants.destroy', $estudiant->id) }}" method="POST"> <input type="hidden" name="_method" value="DELETE"> <input type="hidden" name="_token" value="{{ csrf_token() }}"> <button>Delete User</button> </form> </div> @endsection ``` #### Vista edit. edit.blade.php ```php= @extends('layouts.master') @section('pageTitle', 'Edit Students Details') @section('content') <h1>Edit Student</h1> <hr/> <!-- if validation in the controller fails, show the errors --> @if ($errors->any()) <div> <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif <!-- Open the form with the store function route. --> {{ Form::open(['action' => ['\App\Http\Controllers\EstudiantController@update', $estudiant->id], 'method' => 'PUT']) }} <!-- Include the CRSF token --> {{Form::token()}} <!-- build our form inputs --> <div class="form-group"> {{Form::label('nom', 'Nom')}} {{Form::text('nom', $estudiant->nom, ['class' => 'form-control'])}} </div> <div class="form-group"> {{Form::label('cognom', 'Cognom')}} {{Form::text('cognom', $estudiant->cognom, ['class' => 'form-control'])}} </div> <div class="form-group"> {{Form::label('edat', 'Edat')}} {{Form::number('edat', $estudiant->edat, ['class' => 'form-control'])}} </div> <div class="form-group"> {{Form::label('email', 'Adreça email')}} {{Form::text('email', $estudiant->email, ['class' => 'form-control'])}} </div> {{Form::submit('Actualitza!')}} {{ Form::close() }} @endsection ``` ### Retocant el menú de navegació. Ara que ja tenim enllestides les rutes i les vistes associades, podem retocar el menú de navegació per a incloure les noves funcionalitats. #### Nou contingut de la plantilla parcial header.blade.php ```php= <nav class="navbar"> <a class="navbar-brand" href="{{asset('/')}}">MyCrudApp</a> <div class="nav_container" id="navContainer"> <ul class="nav_ul"> <li class="nav-item"> <a class="nav-link" href="{{asset('/')}}">Home</a> </li> <li class="nav-item"> <a href="{{route('estudiants.index')}}">Index</a> </li> <li class="nav-item"> <a href="{{route('estudiants.create')}}">Create</a> </li> </ul> </div> </nav> ``` ### Aplicant estils. /home/isard/src/una_app_crud/public/css/style.css ```css= .navbar{ margin: 1em; } .navbar-brand { margin: 1em; display: block; } .nav_container * { display: inline; } .nav_container li { background-color: lightsteelblue; padding: 0.3em; margin: 3px; border-radius: 0.3em; } .inline_form { display: inline; } .even_row { background-color: cornsilk; } .odd_row { background-color: azure; } ``` I així hauria d'haber quedat la pàgina desprès d'aplicar els estils ;) ![](https://i.imgur.com/FiGPbCh.png) ### I si fem també una API? Com és evident, Laravel no ens permet només crear pàgines web, també podem generar APIs i seguir aprofitant els recursos que ens proporciona el framework. En un primer pas, oferirem el llistat d'estudiants a la mateixa ruta que la pàgina web, però emprant el prefix /api devant de la ruta original. ![](https://i.imgur.com/awtYTYg.png) Per aconsseguir que funcioni aquesta ruta, primer hem d'afegir-la al fitxer ./routes/api.php ```php= Route::get('/estudiants', '\App\Http\Controllers\EstudiantController@index_api'); ``` La ruta utilitzarà el controlador pre-existent EstudiantController, però l'haurem d'editar per a afegir-li el mètode index_api: ```php= /** * Display a listing of the resource. */ public function index() { $estudiants = Estudiant::all(); return view('estudiants.index', compact('estudiants','estudiants')); } public function index_api() { // $estudiants = Estudiant::all(); // return response()->json($estudiants); return Estudiant::all(); } ``` El codi comentat és més evident, però el codi actiu a banda de ser més compacte il·lustra com Laravel converteix directament a JSON els resultats de consultes Eloquent(ORM) quan les envia com a resposta.