Diferencia entre revisiones de «PHP Servizos Web»

De MediaWiki
Ir a la navegación Ir a la búsqueda
 
(No se muestran 61 ediciones intermedias de 2 usuarios)
Línea 2: Línea 2:
 
== Introdución ==
 
== Introdución ==
  
* [http://blog.architexa.com/2010/11/rest-architecture-simplified/ Arquitectura REST].
+
* Para entender esta sección, os alumnos deben ter realizados os exercicios e visto os vídeos nos que se explica o funcionamento de Laravel.
* [http://www.restapitutorial.com/ Arquitectura REST e regras que debería cumprirse].
+
: Partimos dun proyecto de Laravel de nome beleza, creado coa orde: '''composer create-project laravel/laravel beleza "10.*"'''
* [http://www.i2factory.com/es/integracion/qu%C3%A9-es-un-servicio-restful Que é un servizo REST (en español) e regras que debe cumprir].
 
  
* [https://laravel.com/docs/5.1/controllers#restful-resource-controllers Controlador en Laravel para servizos Rest].
 
* [https://styde.net/api-rest-con-laravel-5-1-primeros-pasos/ Manual para crear un servizo REST en LARAVEL].
 
:* Neste manual non aplica ben as regras para que sexa Rest (os código de erro de resposta HTTP).
 
  
* [https://ajgallego.gitbooks.io/laravel-5/content/capitulo_5_rest.html Manual para crear un servizo REST en LARAVEL].
+
* Para facilitarnos o traballo <u>ides a instalar no Visual Studio Code a extensión '''Laravel Extension Pack'''</u> que nos vai axudar a programar en Laravel.
  
* [https://manuais.iessanclemente.net/index.php/LARAVEL_Framework_-_Tutorial_01_-_Creaci%C3%B3n_de_API_RESTful#Creaci.C3.B3n_de_Controladores_y_Rutas_API_RESTful_en_Laravel Exemplo de creación dun servizo REST].
+
 
 +
 
 +
<br />
 +
* [https://gausswebapp.com/arquitectura-rest.html Arquitectura REST].
 +
* [https://juanda.gitbooks.io/webapps/content/api/arquitectura-api-rest.html Arquitectura REST e regras que debería cumprirse].
 +
 
 +
* [https://laravel.com/docs/10.x/controllers#api-resource-routes Controlador en Laravel para servizos Rest].
  
  
 
* [http://www.apañados.es/tenemos-que-apanar/internet-tutoriales-y-trucos/146-40-apis-utiles-disenadores-desarrolladores-web.html Exemplos de APIs en Internet]
 
* [http://www.apañados.es/tenemos-que-apanar/internet-tutoriales-y-trucos/146-40-apis-utiles-disenadores-desarrolladores-web.html Exemplos de APIs en Internet]
 +
 +
* [https://manuais.iessanclemente.net/index.php/LARAVEL_Framework_-_Tutorial_01_-_Creaci%C3%B3n_de_API_RESTful#Creaci.C3.B3n_de_Controladores_y_Rutas_API_RESTful_en_Laravel Servizo Rest]: Esta parte do manual tomou como base o exemplo de creación dun servizo REST de [https://manuais.iessanclemente.net/index.php/Usuario:Veiga Rafael Veiga].
 +
 +
 +
 +
<br />
 +
 +
== Manual de exemplo ==
 +
* [https://cvallejo.medium.com/c%C3%B3mo-crear-una-api-en-laravel-10-367fea542f88 Manual para crear un servizo REST en LARAVEL].
 +
: Aclaracións ao manual anterior:
 +
:* Previamente tedes que ter creada unha base de datos en MySql (eu vou poñer de nome api-new) cun usuario / password de acceso para poder facer operacións de create, alter, drop, insert, update, delete e select sobre a base de datos.
 +
:: Unha vez feito, modificade o arquivo .env nas entradas DB_YYYYYYYYY que se atopen.
 +
 +
:* Paso 1: Para crear o proxecto: composer create-project laravel/laravel api-new
 +
:* Paso 2.1:
 +
::* O arquivo para modificar o método run se atopa en: /database/seeders/UserSeeder.php
 +
::* Hai que empregar estos uses para que acceda ás clases User e Hash:
 +
:::: use App\Models\User;
 +
:::: use Illuminate\Support\Facades\Hash;
 +
:* Paso 2.1: O comando é con dous guións: php artisan migrate --seed
 +
:* Paso 3.1: O comando é con dous guións: php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
 +
 +
 +
<br />
  
 
== Servizo REST: Características ==
 
== Servizo REST: Características ==
: Información obtida [http://www.i2factory.com/es/integracion/qu%C3%A9-es-un-servicio-restful deste enlace].
 
  
 
* REST é o acrónimo de <b>Re</b>presentational <b>S</b>tate <b>T</b>ransfer.
 
* REST é o acrónimo de <b>Re</b>presentational <b>S</b>tate <b>T</b>ransfer.
Línea 45: Línea 70:
 
:* 201 → Created : Petición completada. Creouse un novo recurso
 
:* 201 → Created : Petición completada. Creouse un novo recurso
 
:* 204 → No Content: Petición procesada correctamente, pero a resposta non ten ningún contido
 
:* 204 → No Content: Petición procesada correctamente, pero a resposta non ten ningún contido
 +
:* 400 → Bad Request: Indica que faltan datos para solicitar la información o están mal o incompletos.Por exemplo, para dar de alta un alumno, necesitas o seu nome. Se non se envía como dato enviarímos este código de volta.
 
:* 401 → Unauthorized: A información de autenticación non é válida
 
:* 401 → Unauthorized: A información de autenticación non é válida
 
:* 404 → Not found: O recurso non se atopou.
 
:* 404 → Not found: O recurso non se atopou.
Línea 103: Línea 129:
 
=== Base de datos ===
 
=== Base de datos ===
  
* Partimos da seguinte base de datos de nome BELLEZA composta polas seguintes táboas:
+
* Partimos da seguinte base de datos de nome '''belleza'''.
 +
 
 +
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 +
CREATE SCHEMA `belleza` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_spanish2_ci ;
 +
</syntaxhighlight>
 +
 
 +
 
 +
* A base de datos está composta polas seguintes táboas:
  
 
'''Táboa MARCAS:'''
 
'''Táboa MARCAS:'''
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
::<syntaxhighlight lang="java" enclose="div" highlight="" >
CREATE TABLE `BELLEZA`.`MARCAS` (
+
CREATE TABLE `belleza`.`marcas` (
   `id_marca` INT NOT NULL AUTO_INCREMENT,
+
   `id` INT NOT NULL AUTO_INCREMENT,
 
   `descripcion` VARCHAR(100) NOT NULL,
 
   `descripcion` VARCHAR(100) NOT NULL,
   PRIMARY KEY (`id_marca`));
+
   PRIMARY KEY (`id`));
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
'''Táboa PERFUMES:'''
+
'''Táboa perfumes:'''
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
::<syntaxhighlight lang="java" enclose="div" highlight="" >
CREATE TABLE `PERFUMES` (
+
CREATE TABLE `belleza`.`perfumes` (
   `id_perfume` int(11) NOT NULL AUTO_INCREMENT,
+
   `id` int NOT NULL AUTO_INCREMENT,
   `descripcion` varchar(45) COLLATE utf8_spanish2_ci NOT NULL,
+
   `descripcion` varchar(45) NOT NULL,
 
   `prezo` decimal(8,2) NOT NULL,
 
   `prezo` decimal(8,2) NOT NULL,
 
   `data_compra` date DEFAULT NULL,
 
   `data_compra` date DEFAULT NULL,
   `marca_id` int(11) NOT NULL,
+
   `marca_id` int NOT NULL,
   PRIMARY KEY (`id_perfume`),
+
   PRIMARY KEY (`id`),
   KEY `fk_PERFUMES_1_idx` (`id_perfume`),
+
   KEY `fk_PERFUMES_1_idx` (`id`),
 
   KEY `fk_PERFUMES_MARCA_idx` (`marca_id`),
 
   KEY `fk_PERFUMES_MARCA_idx` (`marca_id`),
   CONSTRAINT `fk_PERFUMES_MARCA` FOREIGN KEY (`marca_id`) REFERENCES `MARCAS` (`id_marca`) ON DELETE NO ACTION ON UPDATE CASCADE
+
   CONSTRAINT `fk_PERFUMES_MARCA` FOREIGN KEY (`marca_id`) REFERENCES `marcas` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COLLATE=utf8_spanish2_ci;
+
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_spanish2_ci;
  
 
</syntaxhighlight>  
 
</syntaxhighlight>  
Línea 131: Línea 164:
  
 
* Algúns datos de exemplo:
 
* Algúns datos de exemplo:
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
::<syntaxhighlight lang="java" enclose="div" highlight="" >
INSERT INTO `MARCAS` VALUES (1,'CHANNEL'),(2,'ADIDAS'),(3,'ARAMIS');
+
INSERT INTO `marcas` VALUES (1,'CHANNEL'),(2,'ADIDAS'),(3,'ARAMIS');
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
::<syntaxhighlight lang="java" enclose="div" highlight="" >
INSERT INTO `PERFUMES` VALUES (1,'Channel 1',46.22,'2017-02-01',1),(2,'Adidas',1.33,NULL,2),(18,'Best Aramis',54.32,'0000-00-00',3);
+
INSERT INTO `perfumes` VALUES (1,'Channel 1',46.22,'2017-02-01',1),(2,'Adidas',1.33,NULL,2),(18,'Best Aramis',54.32,'2024-01-18',3);
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
Línea 143: Línea 176:
  
 
* Agora en Laravel creamos os modelos correspondentes a cada táboa:
 
* Agora en Laravel creamos os modelos correspondentes a cada táboa:
: '''Arquivo /app/Marca.php'''
+
: Podemos crealos dende consola, coa orde: '''php artisan make:model NomeDoModelo'''
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
: '''Arquivo /app/Models/Marca.php'''
 +
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 
<?php
 
<?php
  
Línea 153: Línea 187:
 
class Marca extends Model
 
class Marca extends Model
 
{
 
{
     protected $table ='MARCAS';
+
     protected $table ='marcas';   // Non faria falta poñelo
     protected $primaryKey='id_marca';
+
     protected $primaryKey='id';   // Non faria falta poñelo
 
      
 
      
 
     public $timestamps = false;
 
     public $timestamps = false;
Línea 166: Línea 200:
 
      
 
      
 
     public function perfumes(){
 
     public function perfumes(){
         return $this->hasMany('App\Perfume','marca_id','id_marca');
+
         return $this->hasMany('App\Models\Perfume','marca_id','id');
 
     }
 
     }
 
}
 
}
Línea 173: Línea 207:
  
 
: '''Arquivo /app/Perfume.php'''
 
: '''Arquivo /app/Perfume.php'''
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 
<?php
 
<?php
  
Línea 182: Línea 216:
 
class Perfume extends Model
 
class Perfume extends Model
 
{
 
{
     protected $table ='PERFUMES';
+
     protected $table ='perfumes';
     protected $primaryKey='id_perfume';
+
     protected $primaryKey='id';
 
      
 
      
 
     public $timestamps = false;
 
     public $timestamps = false;
Línea 200: Línea 234:
  
 
     public function marca(){
 
     public function marca(){
         return $this->belongsTo('App\Marca','marca_id','id_marca');
+
         return $this->belongsTo('App\Models\Marca','marca_id','id');
 
     }
 
     }
 
}  
 
}  
Línea 206: Línea 240:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
* Liña 15: Se devolvemos datos en json podemos facer uso do atributo $dateFormat para que os campos de tipo Date teñan un formato específico. O problema desta solución é que despois da un erro cando engadimos datos....???
+
* Liña 15: Se devolvemos datos en json podemos facer uso do atributo $dateFormat para que os campos de tipo Date teñan un formato específico. O problema desta solución é que despois da un erro cando engadimos datos....
  
 
=== Rutas ===
 
=== Rutas ===
Línea 218: Línea 252:
  
 
:* '''POST''' http://meusitio.es/marcas → Para crear unha marca
 
:* '''POST''' http://meusitio.es/marcas → Para crear unha marca
:* '''GET''' http://meusitio.es/marcas/{marcas} → Para obter a información dunha marca concreta
+
:* '''GET''' http://meusitio.es/marcas/{marca} → Para obter a información dunha marca concreta
:* '''PUT''' http://meusitio.es/marcas/{marcas} → Para modificar os datos dunha marca concreta
+
:* '''PUT''' http://meusitio.es/marcas/{marca} → Para modificar os datos dunha marca concreta
:* '''DELETE''' http://meusitio.es/marcas/{marcas} → Para eliminar unha marca concreta
+
:* '''DELETE''' http://meusitio.es/marcas/{marca} → Para eliminar unha marca concreta
 
:* '''GET''' http://meusitio.es/marcas → Para obter o listado de marcas.
 
:* '''GET''' http://meusitio.es/marcas → Para obter o listado de marcas.
  
  
:* '''POST''' http://meusitio.es/marcas/{marcas}/perfumes → Para crear un perfume
+
:* '''POST''' http://meusitio.es/marcas/{marca}/perfumes → Para crear un perfume
:* '''PUT''' http://meusitio.es/marca/{marcas}/perfumes/{perfumes} → Para modificar os datos dun perfume concreto
+
:* '''PUT''' http://meusitio.es/marca/{marca}/perfumes/{perfumes} → Para modificar os datos dun perfume concreto
:* '''GET''' http://meusitio.es/marcas/{marcas}/perfumes → Para obter o listado de perfumes dunha marca concreta.
+
:* '''GET''' http://meusitio.es/marcas/{marca}/perfumes → Para obter o listado de perfumes dunha marca concreta.
:* '''DELETE''' http://meusitio.es/marcas/{marcas}/perfumes/{id} → Para eliminar un perfume concreto
+
:* '''DELETE''' http://meusitio.es/marcas/{marca}/perfumes/{id} → Para eliminar un perfume concreto
  
 
:* '''GET''' http://meusitio.es/perfumes → Para obter o listado de perfumes.
 
:* '''GET''' http://meusitio.es/perfumes → Para obter o listado de perfumes.
Línea 233: Línea 267:
  
  
* Imos ter polo tanto tres controladores asociados a estas rutas:
+
* Imos ter polo tanto dous controladores asociados a estas rutas:
:* MarcasController
+
:* MarcaController
:* MarcasPerfumesController
+
:* PerfumeController
:* PerfumesController
+
 
 +
 
 +
* Ningún deles vai ter unha vista que vaia a devolver ao usuario, polo tanto nas rutas podemos non crear as opcións 'create' e 'edit' xa que estas son empregadas para amosar unha vista (formulario) para crear un modelo e modificar un modelo.
  
 
: Os comandos para crear ditos controladores serán:
 
: Os comandos para crear ditos controladores serán:
 
:* Versión 5.1 ou anteriores:
 
:* Versión 5.1 ou anteriores:
:::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
:::<syntaxhighlight lang="java" enclose="div" highlight="" >
 
php artisan make:controller MarcaController
 
php artisan make:controller MarcaController
 
php artisan make:controller PerfumeController
 
php artisan make:controller PerfumeController
php artisan make:controller MarcaPerfumeController
 
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
:* Versión 5.2 ou posteriores:
+
:* Versión 10 ou posteriores:
:::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
:::<syntaxhighlight lang="java" enclose="div" highlight="" >
php artisan make:controller MarcaController --resource
+
php artisan make:controller MarcaController --api
php artisan make:controller PerfumeController --resource
+
php artisan make:controller PerfumeController --api
php artisan make:controller MarcaPerfumeController --resource
 
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
  
* Editamos o arquivo /app/Http/routes.php
+
:: O que estamos a facer poñendo --api é que o controlador non teña os métodos '''create''' e '''edit'''.
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
 
Route::resource('marcas','MarcasController',
 
                ['only' => ['store', 'show', 'update', 'destroy','index']]);
 
  
Route::resource('marcas.perfumes','MarcasPerfumesController',
 
                ['only' => ['store', 'update', 'index', 'destroy','']]);
 
 
Route::resource('perfumes','PerfumesController',
 
                ['only' => ['index', 'show']]);
 
</syntaxhighlight>
 
  
 +
:* Nota: Na creación dos controladores podedes poñer a seguinte opción: php artisan make:controller MarcaController --api '''--model=Marca'''
 +
:: O que estamos a facer poñendo --model=Modelo é que os métodos do controlador van ter como entrada un obxecto da clase asociada. Nos imos enviar a ditos método o id da marca e laravel vaise encargar de buscar todos os datos na BD e inxectalos no obxecto.
 +
:: Por exemplo, no caso de Marca:
 +
:::<syntaxhighlight lang="java" enclose="div" highlight="1" >
 +
    public function destroy(Marca $marca)
 +
    {
 +
        //
 +
    }
 +
</syntaxhighlight>
 +
:: No caso de servizos web '''non imos empregar esta forma''' xa que en caso de non atopar a marca devolvería unha páxina web e nos imos devolver un código de erro en formato json.
  
:<u>Nota:</u> Información sobre as rutas [https://ajgallego.gitbooks.io/laravel-5/content/capitulo_5_rest.html neste enlace].
 
  
  
 +
* Agora necesitamos crear as rutas que van a relacionar unha url cun método do controlador.
 
:<u>'''Importante:'''</u> A partires de Laravel 5.3 as rutas do servizo REST deberían gardarse no arquivo '''routes/api.php''' e as rutas 'normais' no arquivo '''routes/web.php'''.
 
:<u>'''Importante:'''</u> A partires de Laravel 5.3 as rutas do servizo REST deberían gardarse no arquivo '''routes/api.php''' e as rutas 'normais' no arquivo '''routes/web.php'''.
 
: Ao facelo desta forma, se optimizan os recursos empregados por Laravel para atender as peticións (nas peticións normais se están aplicando filtros para inicializar a sesión, as cookies, os bindings e a protección CSRF).
 
: Ao facelo desta forma, se optimizan os recursos empregados por Laravel para atender as peticións (nas peticións normais se están aplicando filtros para inicializar a sesión, as cookies, os bindings e a protección CSRF).
 
: Se queremos acceder a unha ruta que se atopa definida en api.php, teremos que empregar a palabra '''api''' na ruta desta forma: http://meusitio.es/'''api'''/marcas/{marcas}.
 
: Se queremos acceder a unha ruta que se atopa definida en api.php, teremos que empregar a palabra '''api''' na ruta desta forma: http://meusitio.es/'''api'''/marcas/{marcas}.
  
: Se queremos implementalo coa nosa versión (5.1) podemos facelo desta forma:
 
::<syntaxhighlight lang="java" line enclose="div" highlight="1" >
 
Route::group(['prefix'=>'api/v1'], function()
 
{
 
    Route::resource('marcas','MarcasController',
 
                ['only' => ['store', 'show', 'update', 'destroy','index']]);
 
  
    Route::resource('marcas.perfumes','MarcasPerfumesController',
 
                ['only' => ['store', 'update', 'index', 'destroy','']]);
 
  
    Route::resource('perfumes','PerfumesController',
+
<br />
                ['only' => ['index', 'show']]);
+
* O podemos facer de forma automática ou ben manualmente.
 +
 
 +
:* '''Automaticamente:'''
 +
:: Editamos o arquivo '''/app/routes/api.php''' e escribimos esta liña:
 +
::<syntaxhighlight lang="java" enclose="div" highlight="1,3" >
 +
use App\Http\Controllers\MarcaController;
 +
....
 +
Route::apiResource('marcas', MarcaController::class);  // Ao ser apiResource xa non engade as rutas create e edit
 +
</syntaxhighlight>
 +
 
 +
:: O anterior crea as seguintes rutas de forma automática:
 +
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 +
  GET|HEAD        api/marcas ............................................................................ marcas.index › MarcaController@index
 +
  POST            api/marcas ............................................................................ marcas.store › MarcaController@store
 +
  GET|HEAD        api/marcas/{marca} ...................................................................... marcas.show › MarcaController@show
 +
  PUT|PATCH      api/marcas/{marca} .................................................................. marcas.update › MarcaController@update
 +
  DELETE          api/marcas/{marca} ................................................................ marcas.destroy › MarcaController@destroy
 +
</syntaxhighlight>
 +
 
 +
:* Manualmente, editando o arquivo '''/app/routes/api.php'''
 +
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 +
Route::get('/marcas', [MarcaController::class, 'index']);
 +
Route::post('/marcas', [MarcaController::class, 'store']);
 +
Route::get('/marcas/{marca}', [MarcaController::class, 'show']);
 +
Route::put('/marcas/{marca}', [MarcaController::class, 'update']);
 +
Route::delete('/marcas/{marca}', [MarcaController::class, 'destroy']);
 +
</syntaxhighlight>
 +
 
 +
 
 +
 
 +
 
 +
* É unha práctica recomendable antepoñer un prefixo ás rutas para indicar a versión da API que estamos a definir. De tal forma que se cambiamos as rutas ou parámetros nunha nova versión, poderíamos deixar funcionando a versión anterior da api. Para poñer un prefixo temos que facer no arquivo '''/app/routes/api.php''':
 +
::<syntaxhighlight lang="java" enclose="div" highlight="4" >
 +
use App\Http\Controllers\MarcaController;
 +
.....
 +
 
 +
Route::group(['prefix'=>'v1'], function()
 +
{
 +
  Route::apiResource('marcas', MarcaController::class);
 
});
 
});
 
</syntaxhighlight>  
 
</syntaxhighlight>  
Línea 294: Línea 359:
  
 
* Unha vez creadas as rutas podemos comprobalas có comando: '''php artisan route:list'''
 
* Unha vez creadas as rutas podemos comprobalas có comando: '''php artisan route:list'''
[[Imagen:Php_laravel_rest_1.jpg|500px|center]]
+
: Polo de agora có anterior cubrimos as rutas que teñen que ver coa marca.
 +
: Faltan as rutas dos perfumes.
 +
 
 +
* Primeiro debemos crear o controlador para os perfumes se non o temos feito anteriormente: php artisan make:controller PerfumeController --api --model=Perfume
 +
* Agora para as rutas, poderíamos facer o mesmo que con marca, pero desa forma a ruta creada para dar de alta un perfume sería: POST /api/v1/perfumes.
 +
: Sen embargo, se queremos empregar o nomeado de rutas nun Restful debería ser algo parecido a isto: POST /api/v1/marcas/XX/perfumes, indicando que se quere dar de alta un perfume da marca XX.
 +
 
  
=== Probando as rutas ===
+
: Se lembrades, iso é o que estaba posto ao principio de todo:
 +
:* '''POST''' http://meusitio.es/marcas/{marca}/perfumes → Para crear un perfume
 +
:* '''PUT''' http://meusitio.es/marca/{marca}/perfumes/{perfumes} → Para modificar os datos dun perfume concreto
 +
:* '''GET''' http://meusitio.es/marcas/{marca}/perfumes → Para obter o listado de perfumes dunha marca concreta.
 +
:* '''DELETE''' http://meusitio.es/marcas/{marca}/perfumes/{id} → Para eliminar un perfume concreto
 +
 
 +
 
 +
* Como podemos lograr isto ?
 +
: Pois indicando que perfume depende de marca no arquivo onde definimos as rutas.
 +
: ''' Arquivo /app/routes/api.php'''
 +
::<syntaxhighlight lang="java" enclose="div" highlight="2,7" >
 +
use App\Http\Controllers\MarcaController;
 +
use App\Http\Controllers\PerfumeController;
 +
.....
 +
Route::group(['prefix'=>'v1'], function()
 +
{
 +
  Route::apiResource('marcas', MarcaController::class);
 +
  Route::apiResource('marcas.perfumes', PerfumeController::class);
 +
 
 +
});
 +
</syntaxhighlight>
 +
 
 +
 
 +
: Fixarse como está posto 'marcas.perfumes'. Isto fai que se cre as seguintes rutas:
 +
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 +
  GET|HEAD        api/v1/marcas/{marca}/perfumes ............................................. marcas.perfumes.index › PerfumeController@index
 +
  POST            api/v1/marcas/{marca}/perfumes ............................................. marcas.perfumes.store › PerfumeController@store
 +
  GET|HEAD        api/v1/marcas/{marca}/perfumes/{perfume} ..................................... marcas.perfumes.show › PerfumeController@show
 +
  PUT|PATCH      api/v1/marcas/{marca}/perfumes/{perfume} ................................. marcas.perfumes.update › PerfumeController@update
 +
  DELETE          api/v1/marcas/{marca}/perfumes/{perfume} ............................... marcas.perfumes.destroy › PerfumeController@destroy
 +
</syntaxhighlight>
  
* Podemos facer que dende os controladores amosen unha cadea de texto para comprobar que as rutas funcionan correctamente.
 
: Para comprobalas podemos facer uso:
 
:* Da orde [https://curl.haxx.se/docs/manpage.html curl]
 
::* Máis información [https://ajgallego.gitbooks.io/laravel-5/content/capitulo_5_curl.html neste enlace].
 
:* Complemento de Firefox [https://addons.mozilla.org/es/firefox/addon/restclient/ restclient] ou [https://addons.mozilla.org/es/firefox/addon/httprequester/ httprequest].
 
  
 +
: Pero sobre isto podemos facer unha mellora. Se vos fixades, para borrar un perfume concreto estou poñendo como ruta: api/v1/marcas/{marca}/perfumes/{perfume} pero isto non ten moito sentido, xa que {perfume} vai ser o perfume concreto. Non necesitaría enviarlle a marca á que pertence dito perfume.
 +
: As rutas de show, update e delete non necesitan a marca. Para simplificar ditas rutas podemos empregar un método shallow() da forma:
 +
::<syntaxhighlight lang="java" enclose="div" highlight="7" >
 +
use App\Http\Controllers\MarcaController;
 +
use App\Http\Controllers\PerfumeController;
 +
.....
 +
Route::group(['prefix'=>'v1'], function()
 +
{
 +
  Route::apiResource('marcas', MarcaController::class);
 +
  Route::apiResource('marcas.perfumes', PerfumeController::class)->shallow();
  
* <u>'''Importante:'''</u> Por defecto Laravel espera recibir un token dos formularios para evitar ataques CSRF. No caso dos servizos, estes non empregan formularios polo que desactivaremos o envío do token no arquivo '''/app/Http/kernel.php'''
+
});
::<syntaxhighlight lang="java" line enclose="div" highlight="8" >
+
</syntaxhighlight>  
  ...........
+
 
    protected $middleware = [
+
: Desta forma se crean estas rutas:
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
+
::<syntaxhighlight lang="java" enclose="div" highlight="3-5" >
        \App\Http\Middleware\EncryptCookies::class,
+
  GET|HEAD        api/v1/marcas/{marca}/perfumes ............................................. marcas.perfumes.index › PerfumeController@index
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
+
  POST            api/v1/marcas/{marca}/perfumes ............................................. marcas.perfumes.store › PerfumeController@store
        \Illuminate\Session\Middleware\StartSession::class,
+
  GET|HEAD        api/v1/perfumes/{perfume} ........................................................... perfumes.show › PerfumeController@show
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
+
  PUT|PATCH      api/v1/perfumes/{perfume} ....................................................... perfumes.update › PerfumeController@update
      // \App\Http\Middleware\VerifyCsrfToken::class,
+
  DELETE          api/v1/perfumes/{perfume} ..................................................... perfumes.destroy › PerfumeController@destroy
    ];
+
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
  
 +
: Se vos fixades, tanto store (crea un perfume da marca indicada) como index (amosa todos os perfumes da marca indicada) van necesitar o id da marca ao que pertence o perfume.
 +
: Polo tanto imos necesitar modificar os métodos do controlador para que reciban dita información dende a url.
 +
: '''Arquivo /app/Http/Controllers/PerfumeController.php'''
 +
::<syntaxhighlight lang="java" enclose="div" highlight="5,13" >
 +
....
 +
    /**
 +
    * Display a listing of the resource.
 +
    */
 +
    public function index($id)
 +
    {
 +
        //
 +
    }
 +
 +
    /**
 +
    * Store a newly created resource in storage.
 +
    */
 +
    public function store(Request $request,$id)
 +
    {
 +
        //
 +
    }
  
 +
.....
 +
</syntaxhighlight>
  
 +
 +
* Agora soamente quedan por definir a seguinte ruta:
 +
 +
:* '''GET''' http://meusitio.es/perfumes → Para obter o listado de perfumes.
 +
 +
: Creamos unha función no controlador de Perfumes (/app/Http/Controllers/PerfumeController.php) para esta rutas:
 +
:* '''public function index_todos() {}''': Que amosará todos os perfumes.
 +
: Creamos a ruta no arquivo '''/app/routes/api.php''':
 +
::<syntaxhighlight lang="java" enclose="div" highlight="6" >
 +
....
 +
Route::group(['prefix'=>'v1'], function()
 +
{
 +
  Route::apiResource('marcas', MarcaController::class);
 +
  Route::apiResource('marcas.perfumes', PerfumeController::class)->shallow();
 +
  Route::get('perfumes',[PerfumeController::class,'index_todos']);
 +
 +
});
 +
 +
.....
 +
</syntaxhighlight>
 +
 +
: Có anterior temos todas estas rutas:
 +
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 +
  GET|HEAD        api/v1/marcas ......................................................................... marcas.index › MarcaController@index
 +
  POST            api/v1/marcas ......................................................................... marcas.store › MarcaController@store
 +
  GET|HEAD        api/v1/marcas/{marca} ................................................................... marcas.show › MarcaController@show
 +
  PUT|PATCH      api/v1/marcas/{marca} ............................................................... marcas.update › MarcaController@update
 +
  DELETE          api/v1/marcas/{marca} ............................................................. marcas.destroy › MarcaController@destroy
 +
  GET|HEAD        api/v1/marcas/{marca}/perfumes ............................................. marcas.perfumes.index › PerfumeController@index
 +
  POST            api/v1/marcas/{marca}/perfumes ............................................. marcas.perfumes.store › PerfumeController@store
 +
  GET|HEAD        api/v1/perfumes .............................................................................. PerfumeController@index_todos
 +
  GET|HEAD        api/v1/perfumes/{perfume} ........................................................... perfumes.show › PerfumeController@show
 +
  PUT|PATCH      api/v1/perfumes/{perfume} ....................................................... perfumes.update › PerfumeController@update
 +
  DELETE          api/v1/perfumes/{perfume} ..................................................... perfumes.destroy › PerfumeController@destroy
 +
</syntaxhighlight>
 +
 +
=== Probando as rutas ===
 +
 +
 +
* '''IMPORTANTE:'''
 +
:* Lembrar que debe estar iniciado o servidor web de Laravel, executando dende consola e dentro do cartafol do proxecto: php artisan serve
 +
:* Lembrar que a ruta, se a tedes 'agrupada' polo número de versión será: http://localhost:8000/api/v1/???????
 +
 +
 +
 +
* Antes de probar as rutas imos a modificar o Controller para que amose un texto en cada un dos métodos que vai responder a cada unha das rutas.
  
 
::'''Arquivo: /app/Http/Controller/MarcaController.php'''
 
::'''Arquivo: /app/Http/Controller/MarcaController.php'''
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 
<?php
 
<?php
  
 
namespace App\Http\Controllers;
 
namespace App\Http\Controllers;
  
 +
use App\Models\Marca;
 
use Illuminate\Http\Request;
 
use Illuminate\Http\Request;
  
use App\Http\Requests;
+
class MarcaController extends Controller
use App\Http\Controllers\Controller;
 
 
 
class MarcasController extends Controller
 
 
{
 
{
 
     /**
 
     /**
 
     * Display a listing of the resource.
 
     * Display a listing of the resource.
    *
 
    * @return \Illuminate\Http\Response
 
 
     */
 
     */
 
     public function index()
 
     public function index()
 
     {
 
     {
         return 'Amosamos as marcas';
+
         return "Amosa todas as marcas";
 
     }
 
     }
 
  
 
     /**
 
     /**
 
     * Store a newly created resource in storage.
 
     * Store a newly created resource in storage.
    *
 
    * @param  \Illuminate\Http\Request  $request
 
    * @return \Illuminate\Http\Response
 
 
     */
 
     */
 
     public function store(Request $request)
 
     public function store(Request $request)
 
     {
 
     {
         return "Creando unha nova marca";
+
         return "Crea una nova marca: $request->descripcion";
 
     }
 
     }
  
 
     /**
 
     /**
 
     * Display the specified resource.
 
     * Display the specified resource.
    *
 
    * @param  int  $id
 
    * @return \Illuminate\Http\Response
 
 
     */
 
     */
 
     public function show($id)
 
     public function show($id)
 
     {
 
     {
         return "Amosando datos da marca $id";  
+
        $marca = Marca::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
 +
         return "Amosa os datos da marca: $marca->descripcion";
 
     }
 
     }
  
 
     /**
 
     /**
 
     * Update the specified resource in storage.
 
     * Update the specified resource in storage.
    *
 
    * @param  \Illuminate\Http\Request  $request
 
    * @param  int  $id
 
    * @return \Illuminate\Http\Response
 
 
     */
 
     */
     public function update(Request $request, $id)
+
     public function update(Request $request,$id)
 
     {
 
     {
         return "Actualizando os datos da marca $id";
+
        $marca = Marca::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
 +
         return "Modifica os datos ($request->descripcion) da marca $marca->id";
 
     }
 
     }
  
 
     /**
 
     /**
 
     * Remove the specified resource from storage.
 
     * Remove the specified resource from storage.
    *
 
    * @param  int  $id
 
    * @return \Illuminate\Http\Response
 
 
     */
 
     */
 
     public function destroy($id)
 
     public function destroy($id)
 
     {
 
     {
         return "Eliminando marca $id";
+
        $marca = Marca::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
 +
         return "Borra a marca $marca->id";
 
     }
 
     }
 
}
 
}
 +
</syntaxhighlight>
  
</syntaxhighlight>
 
  
  
'''Arquivo: /app/Http/Controller/MarcaPerfumeController.php'''
+
'''Arquivo: /app/Http/Controller/PerfumeController.php'''
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 
<?php
 
<?php
  
 
namespace App\Http\Controllers;
 
namespace App\Http\Controllers;
  
 +
use App\Models\Perfume;
 
use Illuminate\Http\Request;
 
use Illuminate\Http\Request;
  
use App\Http\Requests;
+
class PerfumeController extends Controller
use App\Http\Controllers\Controller;
+
{
  
class MarcasPerfumesController extends Controller
+
    public function index_todos(){
{
+
        return 'Amosa todos os perfumes';
 +
    }
 
     /**
 
     /**
     * Display a listing of the resource.
+
     * Amosa os perfumes dunha marca dada
    *
 
    * @return \Illuminate\Http\Response
 
 
     */
 
     */
     public function index($idMarca)
+
     public function index($id_marca)
 
     {
 
     {
         return "Amosando os perfumes da marca $idMarca";
+
        $marca = Marca::find($id_marca);  // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
 +
         return "Amosa todos os perfumes da marca indicada $marca->id";
 
     }
 
     }
 
  
 
     /**
 
     /**
 
     * Store a newly created resource in storage.
 
     * Store a newly created resource in storage.
    *
 
    * @param  \Illuminate\Http\Request  $request
 
    * @return \Illuminate\Http\Response
 
 
     */
 
     */
     public function store(Request $request,$idMarca)
+
     public function store(Request $request,$id_marca)
 
     {
 
     {
         return "Se crea un novo perfume da marca $idMarca";
+
        $marca = Marca::find($id_marca);  // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
 +
         return "Garda un perfume novo ($request->descripcion) que pertence á marca indicada: $marca->id";
 
     }
 
     }
  
 
+
    /**
 +
    * Display the specified resource.
 +
    */
 +
    public function show($id)
 +
    {
 +
        $perfume = Perfume::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa o perfume
 +
        return "Amosa os datos do perfume indicado: $perfume->descripcion";
 +
    }
  
 
     /**
 
     /**
 
     * Update the specified resource in storage.
 
     * Update the specified resource in storage.
    *
 
    * @param  \Illuminate\Http\Request  $request
 
    * @param  int  $id
 
    * @return \Illuminate\Http\Response
 
 
     */
 
     */
     public function update(Request $request, $idMarca,$idPerfume)
+
     public function update(Request $request, $id)
 
     {
 
     {
         return "Se actualizan os datos do perfume $idPerfume da marca $idMarca";
+
        $perfume = Perfume::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa o perfume
 +
         return "Modifica os datos ($request->descripcion) do perfume indicado ($perfume->id)";
 
     }
 
     }
  
 
     /**
 
     /**
 
     * Remove the specified resource from storage.
 
     * Remove the specified resource from storage.
    *
 
    * @param  int  $id
 
    * @return \Illuminate\Http\Response
 
 
     */
 
     */
     public function destroy($idMarca,$idPerfume)
+
     public function destroy($id)
 
     {
 
     {
         return "Se elimina perfume $idPerfume da marca $idMarca";
+
        $perfume = Perfume::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa o perfume
 +
         return "Borra o perfume: $perfume->id";
 
     }
 
     }
 
}
 
}
 
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
  
'''Arquivo: /app/Http/Controller/PerfumeController.php'''
+
* Para comprobalas podemos facer uso:
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
:* Da orde [https://curl.haxx.se/docs/manpage.html curl]
<?php
+
:: Máis información [https://ajgallego.gitbooks.io/laravel-5/content/capitulo_5_curl.html neste enlace].
 +
:* Complemento para:
 +
::* '''Firefox''':
 +
:::* [https://addons.mozilla.org/es/firefox/addon/restclient/ restclient].
 +
:::* [https://addons.mozilla.org/en-US/firefox/addon/rester/?utm_source=addons.mozilla.org&utm_medium=referral Rester].
  
namespace App\Http\Controllers;
+
::* '''Chome:''':
 +
:::* [https://chrome.google.com/webstore/detail/talend-api-tester-free-ed/aejoelaoggembcahagimdiliamlcdmfm Talend Api Tester]
  
use Illuminate\Http\Request;
 
  
use App\Http\Requests;
 
use App\Http\Controllers\Controller;
 
  
class PerfumesController extends Controller
 
{
 
    /**
 
    * Display a listing of the resource.
 
    *
 
    * @return \Illuminate\Http\Response
 
    */
 
    public function index()
 
    {
 
        return "Amósanse todos os perfumes";
 
    }
 
  
  
    /**
 
    * Display the specified resource.
 
    *
 
    * @param  int  $id
 
    * @return \Illuminate\Http\Response
 
    */
 
    public function show($idPerfume)
 
    {
 
        return "Amosa os datos do perfume $idPerfume";
 
    }
 
  
}
+
* Exemplos para probar as rutas:
</syntaxhighlight>
 
  
 +
:* Empregando a extensión de Chrome.
 +
::<gallery caption="Probando rutas con Chrome" widths="600" heights="300px" perrow="2">
 +
Image:Php_laravel_1.jpg |Unha vez instalado, o executamos. Prememos no nome da extensión.
 +
Image:php_laravel_servizos_1.JPG | Exemplo de chamada empregando o verbo GET.
 +
Image:php_laravel_servizos_2.JPG | Ao ser unha chamada de tipo GET podemos empregar o navegador.
 +
Image:Php_laravel_servizos_3.JPG | Exemplo de chamada empregando o verbo POST. Ao ser unha chamada cunha API, está desabilitado a protección por token CSRF.
 +
</gallery>
  
  
* Exemplos para probar as rutas:
 
  
: Comando:
+
* Empregando o comando curl:
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
::<syntaxhighlight lang="java" enclose="div" highlight="" >
curl -i "http://localhost:8000/marcas"
+
curl -i "http://localhost:8000/api/v1/marcas"
 
</syntaxhighlight>  
 
</syntaxhighlight>  
 
: Resultado:
 
: Resultado:
::<syntaxhighlight lang="java" line enclose="div" highlight="10" >
+
::<syntaxhighlight lang="java" enclose="div" highlight="10" >
 
HTTP/1.1 200 OK
 
HTTP/1.1 200 OK
 
Host: localhost:8000
 
Host: localhost:8000
 +
Date: Thu, 22 Feb 2024 16:39:38 GMT
 
Connection: close
 
Connection: close
X-Powered-By: PHP/5.5.9-1ubuntu4.19
+
X-Powered-By: PHP/8.1.2-1ubuntu2.14
Cache-Control: no-cache
 
Date: Mon, 17 Apr 2017 09:25:34 GMT
 
 
Content-Type: text/html; charset=UTF-8
 
Content-Type: text/html; charset=UTF-8
Set-Cookie: laravel_session=eyJpdiI6IjIrbzRrS08yaU51bjVwdk1WY2VrcGc9PSIsInZhbHVlIjoiQmhUVU9SdVhKVzBVckNqUitJV0JlR2tSNkkydWFmY2hGSWdmWUV0Q053MlV1QTVyb21FcG1tMDFveGREaDZmTk55Nnk1VDNkclBCQng5OExXM1ZnQmc9PSIsIm1hYyI6Ijc2YWQ5YTY0MmZjOWRkZjRmNTU4ZTllNWNjZTA0NjRkZGUwMmEzNTIyNjhiMmJkMTBiZWMzYzczYTQ0Zjk2ZGEifQ%3D%3D; expires=Mon, 17-Apr-2017 11:25:34 GMT; Max-Age=7200; path=/; httponly
+
Cache-Control: no-cache, private
 +
Date: Thu, 22 Feb 2024 16:39:38 GMT
 +
X-RateLimit-Limit: 60
 +
X-RateLimit-Remaining: 59
 +
Access-Control-Allow-Origin: *
  
Amosamos as marcas
+
Amosa todas as marcas
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
  
 
: Comando:
 
: Comando:
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
::<syntaxhighlight lang="java" enclose="div" highlight="" >
curl -i -H "Accept: application/json" -X POST http://localhost:8000/marcas
+
curl -i -H "Accept: application/json" -X POST "http://localhost:8000/api/v1/marcas"
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
 
: Resultado:
 
: Resultado:
::<syntaxhighlight lang="java" line enclose="div" highlight="10" >
+
::<syntaxhighlight lang="java" enclose="div" highlight="10" >
 
HTTP/1.1 200 OK
 
HTTP/1.1 200 OK
 
Host: localhost:8000
 
Host: localhost:8000
 +
Date: Thu, 22 Feb 2024 16:40:34 GMT
 
Connection: close
 
Connection: close
X-Powered-By: PHP/5.5.9-1ubuntu4.19
+
X-Powered-By: PHP/8.1.2-1ubuntu2.14
Cache-Control: no-cache
 
Date: Mon, 17 Apr 2017 09:26:43 GMT
 
 
Content-Type: text/html; charset=UTF-8
 
Content-Type: text/html; charset=UTF-8
Set-Cookie: laravel_session=eyJpdiI6InYrZXU0Y1BsbGM0NGdWR212aUNlQXc9PSIsInZhbHVlIjoiNG1QM0lEaG02QmFkS2p1XC9UZzZ6R2dCT2FxUFwvbURSNWNJN21ibUpvZzFvWVE1b3JBaFB4a0F3ZjFIc0NVZzNPVWxOSmhsT2wwdnFIdTJhck5PeHBCdz09IiwibWFjIjoiODg0YTNjOTQ2NjAyOTNhNTI0OGI5MjZkYjIwZWU0YmJiMTM1ODFmMDkzNTU3MmZjMzg5OGY3NWE0NzNjZDlkZiJ9; expires=Mon, 17-Apr-2017 11:26:43 GMT; Max-Age=7200; path=/; httponly
+
Cache-Control: no-cache, private
 +
Date: Thu, 22 Feb 2024 16:40:34 GMT
 +
X-RateLimit-Limit: 60
 +
X-RateLimit-Remaining: 58
 +
Access-Control-Allow-Origin: *
  
Creando unha nova marca
+
Crea una nova marca
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Línea 544: Línea 694:
  
 
: Soamente temos que chamar á función toJson para converter os datos a dito modelo da forma:
 
: Soamente temos que chamar á función toJson para converter os datos a dito modelo da forma:
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 
$jsonPerfumes = Perfumes::all()->toJson();
 
$jsonPerfumes = Perfumes::all()->toJson();
 
</syntaxhighlight>  
 
</syntaxhighlight>  
Línea 557: Línea 707:
 
: Se ademais queremos devolver un código de resposta HTTP o poñeremos ao final: <b>response()->json(['variable'=>'valor','status'=>'ok'],200);</b>
 
: Se ademais queremos devolver un código de resposta HTTP o poñeremos ao final: <b>response()->json(['variable'=>'valor','status'=>'ok'],200);</b>
  
: Máis información [https://laravel.com/docs/5.1/responses#json-responses neste enlace].
+
: Máis información [https://laravel.com/docs/10.x/responses#json-responses neste enlace].
 
: Todas as respostas e formatos deste exercicio están seguindo as normas [http://jsonapi.org/format/ indicadas neste enlace].
 
: Todas as respostas e formatos deste exercicio están seguindo as normas [http://jsonapi.org/format/ indicadas neste enlace].
  
  
 
* '''Arquivo app/Http/Controller/MarcasController.php'''
 
* '''Arquivo app/Http/Controller/MarcasController.php'''
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 
<?php
 
<?php
  
 
namespace App\Http\Controllers;
 
namespace App\Http\Controllers;
  
use Illuminate\Http\Request;
+
use App\Models\Marca;
 +
use App\Models\Perfume;
  
use App\Http\Requests;
 
use App\Http\Controllers\Controller;
 
  
use App\Marca;
+
use Illuminate\Http\Request;
use Validator;
+
use Illuminate\Support\Facades\Validator;
  
class MarcasController extends Controller
+
class MarcaController extends Controller
 
{
 
{
    /**
+
    /**
 
     * Display a listing of the resource.
 
     * Display a listing of the resource.
    *
 
    * @return \Illuminate\Http\Response
 
 
     */
 
     */
 
     public function index()
 
     public function index()
 
     {
 
     {
       
 
 
         return response()->json(['status'=>'ok','data'=>Marca::all()],200);
 
         return response()->json(['status'=>'ok','data'=>Marca::all()],200);
 
     }
 
     }
 
  
 
     /**
 
     /**
 
     * Store a newly created resource in storage.
 
     * Store a newly created resource in storage.
    *
 
    * @param  \Illuminate\Http\Request  $request
 
    * @return \Illuminate\Http\Response
 
 
     */
 
     */
 
     public function store(Request $request)
 
     public function store(Request $request)
 
     {
 
     {
 
         if(!$request->input('descripcion')){
 
         if(!$request->input('descripcion')){
             return response()->json(['errors'=>['status'=>'422','title'=>'Datos incompletos','detail'=>'Falta a descripcion da marca']],400);          
+
             return response()->json(['errors'=>['status'=>'422','title'=>'Datos incompletos','detail'=>'Falta a descripcion da marca']],400);
 
         }
 
         }
 
         $novaMarca = new Marca;
 
         $novaMarca = new Marca;
Línea 604: Línea 746:
 
         $novaMarca->save();
 
         $novaMarca->save();
  
         return response()->json(['status'=>'ok','data'=>$novaMarca],201)->header('Location', 'http://www.dominio.es/marcas/'.$novaMarca->id_marca)->header('Content-Type', 'application/json');
+
         return response()->json(['status'=>'ok','data'=>$novaMarca],201)->header('Location', 'http://www.dominio.es/marcas/'.$novaMarca->id_marca);  // No header iría a URL onde se pode consultar o novo recurso creado. No voso caso sería: 'http://localhost:8000/api/v1/marcas/' .$novaMarca->id_marca
 
     }
 
     }
  
 
     /**
 
     /**
 
     * Display the specified resource.
 
     * Display the specified resource.
     *
+
     * Ao definir un obxecto da clase Marca, automaticamente vai buscala polo id enviado.
    * @param  int  $id
 
    * @return \Illuminate\Http\Response
 
 
     */
 
     */
     public function show($idMarca)
+
     public function show($id)
 
     {
 
     {
         $marca = Marca::find($idMarca);
+
         $marca = Marca::find($id); // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
 
         if (!$marca){
 
         if (!$marca){
 
             return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
 
             return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
 
         }
 
         }
       
+
 
 
         return response()->json(['status'=>'ok','data'=>$marca],200);
 
         return response()->json(['status'=>'ok','data'=>$marca],200);
 
     }
 
     }
Línea 625: Línea 765:
 
     /**
 
     /**
 
     * Update the specified resource in storage.
 
     * Update the specified resource in storage.
    *
 
    * @param  \Illuminate\Http\Request  $request
 
    * @param  int  $id
 
    * @return \Illuminate\Http\Response
 
 
     */
 
     */
     public function update(Request $request, $idMarca)
+
     public function update(Request $request,$id)
 
     {
 
     {
 
         // Validamos os datos que nos chegan:
 
         // Validamos os datos que nos chegan:
Línea 637: Línea 773:
 
         ]);
 
         ]);
 
         if ($validator->fails()){
 
         if ($validator->fails()){
           return response()->json(['errors'=>['status'=>'422','title'=>'Datos incompletos','detail'=>'Falta a descripcion da marca ou ten mais de 100 caracteres']],400);          
+
           return response()->json(['errors'=>['status'=>'422','title'=>'Datos incompletos','detail'=>'Falta a descripcion da marca ou ten mais de 100 caracteres']],400);
 
         }
 
         }
       
+
 
 
         // Pode vir polo método PUT ou polo método PATCH
 
         // Pode vir polo método PUT ou polo método PATCH
 
         // Non imos facer distinción.
 
         // Non imos facer distinción.
 
         $modificado=false;
 
         $modificado=false;
         $marcaModificar = Marca::find($idMarca);
+
         $marca = Marca::find($id); // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
         if (!$marcaModificar){
+
         if (!$marca){
 
             return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
 
             return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
 
         }
 
         }
 +
 
         if($request->has('descripcion')){
 
         if($request->has('descripcion')){
 
             $modificado=true;
 
             $modificado=true;
             $marcaModificar->descripcion=$request->input('descripcion');
+
             $marca->descripcion=$request->input('descripcion');
 
         }
 
         }
       
+
 
 
         // Non se modificou nada. Neste caso non pode darse o caso xa que obrigamos a enviar a descripcion no caso de modificación. Pero se tivésemos máis campos, teríamos que quitar o atribute required.
 
         // Non se modificou nada. Neste caso non pode darse o caso xa que obrigamos a enviar a descripcion no caso de modificación. Pero se tivésemos máis campos, teríamos que quitar o atribute required.
 
         if(!$modificado) {
 
         if(!$modificado) {
 
             return response()->json(['errors'=>['status'=>'304','title'=>'Marca non modificada']],304);
 
             return response()->json(['errors'=>['status'=>'304','title'=>'Marca non modificada']],304);
 
         }
 
         }
       
+
 
         $marcaModificar->save();
+
         $marca->save();
 
         return response()->json(['status'=>'ok'],200);
 
         return response()->json(['status'=>'ok'],200);
       
+
 
 
     }
 
     }
  
 
     /**
 
     /**
 
     * Remove the specified resource from storage.
 
     * Remove the specified resource from storage.
    *
 
    * @param  int  $id
 
    * @return \Illuminate\Http\Response
 
 
     */
 
     */
     public function destroy($idMarca)
+
     public function destroy($id)
 
     {
 
     {
         $marcaBorrar = Marca::find($idMarca);
+
         $marca = Marca::find($id); // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
         if (!$marcaBorrar){
+
 
 +
         if (!$marca){
 
             return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
 
             return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
 
         }
 
         }
       
+
 
 
         // Comprobamos se temos perfumes asociados á marca
 
         // Comprobamos se temos perfumes asociados á marca
         if($marcaBorrar->perfumes && $marcaBorrar->perfumes->count()>0){
+
         if($marca->perfumes->count()>0){
 
             return response()->json(['errors'=>['status'=>'409','title'=>'Existen perfumes asociados']],409);
 
             return response()->json(['errors'=>['status'=>'409','title'=>'Existen perfumes asociados']],409);
 
         }
 
         }
       
+
 
         $marcaBorrar->delete();
+
         $marca->delete();
         return response()->json('',204);
+
         return response()->json(null,204);  // Tamén se pode poñer: return response()->noContent();
       
+
 
       
 
 
     }
 
     }
 
}
 
}
 +
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
  
* '''Arquivo app/Http/Controller/PerfumesController.php'''
 
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
 
<?php
 
  
namespace App\Http\Controllers;
 
  
use Illuminate\Http\Request;
 
  
use App\Http\Requests;
 
use App\Http\Controllers\Controller;
 
use App\Perfume;
 
  
class PerfumesController extends Controller
+
<br />
{
+
* Imos probar todas estas rutas.
    /**
+
* '''Nota Importante:'''
    * Display a listing of the resource.
+
:* Se vos sae o erro: 500 Internal Server Error, comprobade que tedes configurado ben o acceso á base de datos no arquivo .env e cun usuario/password que teña permiso de acceso e modificación sobre a base de datos.
    *
+
:* Se aínda asi recibides o erro 500, podedes poñer o código que vos faia nunha ruta GET do controlador (por exemplo INDEX) e chamar dende un navegador a dita ruta e 'depurar' amosando coa orde var_dump() ou dd() o contido do que ides facendo.
    * @return \Illuminate\Http\Response
+
 
    */
+
    public function index()
+
::<gallery caption="Probando rutas de Marca con Chrome" widths="600" heights="300px" perrow="2">
    {
+
Image:php_laravel_servizos_4.JPG | Probando a ruta (método index do controller) que recupera todas as marcas.
        return response()->json(['status'=>'ok','data'=>Perfumes::all()],200);    }
+
Image:php_laravel_servizos_5.JPG | Probando a ruta (método store do controller) que engade unha nova marca. Neste caso enviamos os datos (descripcion) na URL xa que estamos a empregar QUERY PARAMETERS.
 +
Image:php_laravel_servizos_6.JPG | Probando a ruta (método store do controller) que engade unha nova marca. Neste caso enviamos os datos (descripcion) na corpo da páxina xa que estamos a empregar FORM PARAMETERS.
 +
Image:php_laravel_servizos_7.JPG | Probando a ruta (método store do controller) que engade unha nova marca. Neste caso enviamos os datos (descripcion) na corpo da páxina en formato JSON. '''ESTA VAI SER A FORMA QUE IMOS EMPREGAR DENDE LARAVEL PARA CONSUMIR UN SERVIZO WEB'''.
 +
Image:php_laravel_servizos_8.JPG | Probando a ruta (método show do controller) que amosa os datos dunha marca.
 +
Image:php_laravel_servizos_9.JPG | Probando a ruta (método update do controller) que actualiza unha marca. Aquí temos que enviar dous datos (neste Modelo), o id da marca a modificar e a nova descripción (non deixamos actualizar o id). O id da marca vai na URL (mirade as rutas có comando php artisan route:list) a os datos van en formato JSON como fixemos no paso anterior.
 +
Image:php_laravel_servizos_10.JPG | Probando a ruta (método destroy do controller) que borra unha marca. Neste caso non deixa xa que o id desta marca ten perfumes asociados.
 +
Image:php_laravel_servizos_11.JPG | Probando a ruta (método destroy do controller) que borra unha marca. Comprobade que tedes ese número de id na táboa de Marcas.
  
 +
</gallery>
  
    /**
 
    * Display the specified resource.
 
    *
 
    * @param  int  $id
 
    * @return \Illuminate\Http\Response
 
    */
 
    public function show($idPerfume)
 
    {
 
        $perfume=Perfume::find($idPerfume);
 
        if (!$perfume){
 
            return response()->json(['errors'=>['status'=>'404','title'=>'Perfume non atopado']],404);
 
        }
 
     
 
        return response()->json(['status'=>'ok','data'=>$perfume],200);
 
    }
 
  
}
 
</syntaxhighlight>
 
  
  
  
  
* '''Arquivo app/Http/Controller/MarcasPerfumesController.php'''
+
* '''Arquivo app/Http/Controller/PerfumesController.php'''
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 
<?php
 
<?php
  
 
namespace App\Http\Controllers;
 
namespace App\Http\Controllers;
  
 +
use App\Models\Marca;
 +
use App\Models\Perfume;
 +
 +
use Illuminate\Support\Facades\Validator;
 
use Illuminate\Http\Request;
 
use Illuminate\Http\Request;
  
use App\Http\Requests;
+
class PerfumeController extends Controller
use App\Http\Controllers\Controller;
 
 
 
use App\Marca;
 
use App\Perfume;
 
use Validator;
 
 
 
class MarcasPerfumesController extends Controller
 
 
{
 
{
 
     // Formato dd/mm/YYYY => devuelve YYYY-mm-dd
 
     // Formato dd/mm/YYYY => devuelve YYYY-mm-dd
 
     function cambiaf_a_mysql($fecha){
 
     function cambiaf_a_mysql($fecha){
       
+
 
 
         if (empty($fecha)) return null;
 
         if (empty($fecha)) return null;
       
+
 
         $numeroDia = substr($fecha,0,2);
+
         $partes = explode("-",$fecha);
        $mes = substr($fecha,3,2);
+
 
        $anio = substr($fecha,6,4);
+
         return $partes[2] . '-' . $partes[1] . '-' . $partes[0];
         return $anio . '-' . $mes . '-' . $numeroDia;
+
     }
     }  
+
 
      
+
     public function index_todos() {
      
+
        return response()->json(['status'=>'ok','data'=>Perfume::all()],200);
 +
     }
 
     /**
 
     /**
     * Display a listing of the resource.
+
     * Amosa os perfumes dunha marca dada
    *
 
    * @return \Illuminate\Http\Response
 
 
     */
 
     */
     public function index($idMarca)
+
     public function index($id_marca)
 
     {
 
     {
         $marca = Marca::find($idMarca);
+
         $marca = Marca::find($id_marca); // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
 
         if (!$marca){
 
         if (!$marca){
 
             return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
 
             return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
 
         }
 
         }
       
+
 
 
         $perfumes = $marca->perfumes()->get();
 
         $perfumes = $marca->perfumes()->get();
 
         return response()->json(['status'=>'ok','data'=>$perfumes],200);
 
         return response()->json(['status'=>'ok','data'=>$perfumes],200);
 
     }
 
     }
 
  
 
     /**
 
     /**
 
     * Store a newly created resource in storage.
 
     * Store a newly created resource in storage.
    *
 
    * @param  \Illuminate\Http\Request  $request
 
    * @return \Illuminate\Http\Response
 
 
     */
 
     */
     public function store(Request $request,$idMarca)
+
     public function store(Request $request,$id_marca)
 
     {
 
     {
 
         // Validamos os datos que nos chegan:
 
         // Validamos os datos que nos chegan:
Línea 793: Línea 902:
 
             'descripcion' => 'required|max:45',
 
             'descripcion' => 'required|max:45',
 
             'prezo' => 'required|regex:/[0-9]{1,3},[0-9]{2}/',
 
             'prezo' => 'required|regex:/[0-9]{1,3},[0-9]{2}/',
             'data_compra' => 'date_format:"d/m/Y"'
+
             'data_compra' => 'date_format:"d-m-Y"' // 4 díxitos para o ano
 
         ]);
 
         ]);
 
         if ($validator->fails()){
 
         if ($validator->fails()){
           return response()->json(['errors'=>['status'=>'422','title'=>'Datos incompletos','detail'=>'Faltan datos ou con formato incorrecto: descripcion, prezo, data_compra']],400);          
+
           return response()->json(['errors'=>['status'=>'422','title'=>'Datos incompletos','detail'=>'Faltan datos ou con formato incorrecto: descripcion, prezo, data_compra']],400);
 
         }
 
         }
  
 +
        $marca = Marca::find($id_marca);  // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
 
         // Comprobamos se a marca existe
 
         // Comprobamos se a marca existe
        $marca = Marca::find($idMarca);
 
 
         if (!$marca){
 
         if (!$marca){
 
             return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
 
             return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
 
         }
 
         }
  
         //SE NON TIVISEMOS QUE FORMATEAR OS DATOS PODERÍAMOS FACELO ASÍ:
+
         //SE NON TIVISEMOS QUE FORMATEAR OS DATOS PODERÍAMOS FACELO ASÍ,
 
         //  $marca->perfumes()->create($request->all());
 
         //  $marca->perfumes()->create($request->all());
         //  pero desta forma teríamos que enviar o id da marca tamén como dato e comprobar que sexa o mesmo que $idMarca
+
         //  PERO CAMBIAMOS O FORMATO DA DATA e teríamos que engador ao obxecto $request o id da marca
 +
 
 
         $novoPerfume=new Perfume;
 
         $novoPerfume=new Perfume;
 
         $novoPerfume->descripcion=$request->input('descripcion');
 
         $novoPerfume->descripcion=$request->input('descripcion');
 
         $novoPerfume->prezo=str_replace(',','.',$request->input('prezo'));
 
         $novoPerfume->prezo=str_replace(',','.',$request->input('prezo'));
 
         $novoPerfume->data_compra=$this->cambiaf_a_mysql($request->input('data_compra'));
 
         $novoPerfume->data_compra=$this->cambiaf_a_mysql($request->input('data_compra'));
         $novoPerfume->marca_id=$idMarca;
+
         $novoPerfume->marca_id=$marca->id;
 
         $novoPerfume->save();
 
         $novoPerfume->save();
       
 
        return response()->json(['status'=>'ok','data'=>$novoPerfume],201)->header('Location', 'http://www.dominio.es/perfumes/' . $novoPerfume->id_perfume)->header('Content-Type', 'application/json');
 
  
 +
        return response()->json(['status'=>'ok','data'=>$novoPerfume],201)->header('Location', 'http://www.dominio.es/perfumes/' . $novoPerfume->id_perfume);  // No header iría a URL onde se pode consultar o novo recurso creado. No voso caso sería: 'http://localhost:8000/api/v1/perfumes/' .$novoPerfume->id_perfume
 
     }
 
     }
  
 +
    /**
 +
    * Display the specified resource.
 +
    */
 +
    public function show($id)
 +
    {
 +
        $perfume = Perfume::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa o perfume
 +
        if (!$perfume){
 +
            return response()->json(['errors'=>['status'=>'404','title'=>'Perfume non atopado']],404);
 +
        }
 +
 +
        return response()->json(['status'=>'ok','data'=>$perfume],200);
  
 +
    }
  
 
     /**
 
     /**
 
     * Update the specified resource in storage.
 
     * Update the specified resource in storage.
    *
 
    * @param  \Illuminate\Http\Request  $request
 
    * @param  int  $id
 
    * @return \Illuminate\Http\Response
 
 
     */
 
     */
     public function update(Request $request, $idMarca,$idPerfume)
+
     public function update(Request $request, $id)
 
     {
 
     {
 
         // Validamos os datos que nos chegan:
 
         // Validamos os datos que nos chegan:
Línea 838: Línea 955:
 
         ]);
 
         ]);
 
         if ($validator->fails()){
 
         if ($validator->fails()){
           return response()->json(['errors'=>['status'=>'422','title'=>'Datos incompletos','detail'=>'Faltan datos ou con formato incorrecto: descripcion, prezo, data_compra']],400);          
+
           return response()->json(['errors'=>['status'=>'422','title'=>'Datos incompletos','detail'=>'Faltan datos ou con formato incorrecto: descripcion, prezo, data_compra']],400);
 
         }
 
         }
 
         if($request->has('marca_id')){
 
         if($request->has('marca_id')){
 
             // Comprobamos se a marca nova existe
 
             // Comprobamos se a marca nova existe
             $marcadestino = Marca::find($request->input('marca_id'));
+
             $marcadestino = Marca::find($request->marca_id);
 
             if (!$marcadestino){
 
             if (!$marcadestino){
                 return response()->json(['errors'=>['status'=>'422','title'=>'Datos incompletos','detail'=>'A marca non existe']],400);          
+
                 return response()->json(['errors'=>['status'=>'422','title'=>'Datos incompletos','detail'=>'A marca non existe']],400);
 
             }
 
             }
 
         }
 
         }
  
       
+
         $perfume = Perfume::find($id); // Poderíamos empregar findOrFail, pero así temos control se non atopa o perfume
        // Comprobamos se a marca existe
 
         $marca = Marca::find($idMarca);
 
        if (!$marca){
 
            return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
 
        }
 
        // Comprobamos se o perfume existe
 
        $perfume=$marca->perfumes()->find($idPerfume);
 
        if (!$perfume){
 
            return response()->json(['errors'=>['status'=>'404','title'=>'Perfume non atopado']],404);
 
        }
 
 
 
        $modificado=false;
 
 
         if($request->has('descripcion')){
 
         if($request->has('descripcion')){
             $perfume->descripcion=$request->input('descripcion');
+
             $perfume->descripcion=$request->descripcion;
            $modificado=true;
 
 
         }
 
         }
 
         if($request->has('prezo')){
 
         if($request->has('prezo')){
             $perfume->prezp=str_replace(',','.',$request->input('prezo'));
+
             $perfume->prezo=str_replace(',','.',$request->prezo);
            $modificado=true;
 
 
         }
 
         }
 
         if($request->has('data_compra')){
 
         if($request->has('data_compra')){
             $perfume->data_compra=$this->cambiaf_a_mysql($request->input('data_compra'));
+
             $perfume->data_compra=$this->cambiaf_a_mysql($request->data_compra);
            $modificado=true;
 
 
         }
 
         }
 
         if($request->has('marca_id')){
 
         if($request->has('marca_id')){
             $perfume->marca_id=$request->input('marca_id');
+
             $perfume->marca_id=$request->marca_id;
            $modificado=true;
 
 
         }
 
         }
  
        if(!$modificado) {
 
            return response()->json(['errors'=>['status'=>'304','title'=>'Marca non modificada']],304);
 
        }
 
       
 
 
         $perfume->save();
 
         $perfume->save();
         return response()->json(['status'=>'ok','data'=>$perfume],200);      
+
         return response()->json(['status'=>'ok','data'=>$perfume],200);
 
     }
 
     }
  
 
     /**
 
     /**
 
     * Remove the specified resource from storage.
 
     * Remove the specified resource from storage.
    *
 
    * @param  int  $id
 
    * @return \Illuminate\Http\Response
 
 
     */
 
     */
     public function destroy($idMarca,$idPerfume)
+
     public function destroy($id)
 
     {
 
     {
         $marca = Marca::find($idMarca);
+
         $perfume = Perfume::find($id); // Poderíamos empregar findOrFail, pero así temos control se non atopa o perfume
         if (!$marca){
+
         if (!$perfume){
             return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
+
             return response()->json(['errors'=>['status'=>'404','title'=>'Perfume non atopado']],404);
 
         }
 
         }
  
         // Comprobamos se o perfume existe
+
         $perfume->delete();
        $perfumeBorrar=$marca->perfumes()->find($idPerfume);
+
         return response()->json(null,204); // Tamén se pode poñer: return response()->noContent();
         if (!$perfumeBorrar){
+
 
            return response()->json(['errors'=>['status'=>'404','title'=>'Perfume non atopado']],404);
 
        }
 
       
 
       
 
        //$perfumeBorrar->delete();
 
        return response()->json('',204);
 
 
     }
 
     }
 
}
 
}
 +
  
 
</syntaxhighlight>
 
</syntaxhighlight>
  
  
=== Autenticar ===
+
 
 +
<br />
 +
* Imos probar todas estas rutas.
 +
* '''Nota Importante:'''
 +
:* Se vos sae o erro: 500 Internal Server Error, comprobade que tedes configurado ben o acceso á base de datos no arquivo .env e cun usuario/password que teña permiso de acceso e modificación sobre a base de datos.
 +
:* Se aínda asi recibides o erro 500, podedes poñer o código que vos faia nunha ruta GET do controlador (por exemplo INDEX) e chamar dende un navegador a dita ruta e 'depurar' amosando coa orde var_dump() ou dd() o contido do que ides facendo.
 +
 
 +
 
 +
::<gallery caption="Probando rutas de Marca con Chrome" widths="600" heights="300px" perrow="2">
 +
Image:php_laravel_servizos_12.JPG | Probando a ruta (método index_todos do controller) que recupera todos perfumes.
 +
Image:php_laravel_servizos_13.JPG | Probando a ruta (método index do controller) que recupera todos perfumes dunha marca dada. No exemplo da marca có id=1.
 +
Image:php_laravel_servizos_14.JPG | Probando a ruta (método store do controller) que engade un novo perfume. Neste caso enviamos os datos (descripcion, prezo e data_compra)  no corpo da páxina en formato JSON. Observar o formato da data e do prezo. Dito formato está definido no controlador, no método Validate. O prezo leva coma e a data guión.
 +
Image:php_laravel_servizos_14B.JPG | No exemplo anterior podemos ver como o servizo devolve os datos do perfume creado, incluído o seu id.
 +
Image:php_laravel_servizos_15.JPG | Probando a ruta (método show do controller) que amosa os datos dun perfume. No exemplo busca os datos do perfume có id=1
 +
Image:php_laravel_servizos_16.JPG | Probando a ruta (método update do controller) que actualiza un perfume. Aquí temos que enviar dous datos (neste Modelo), o id do perfume a modificar (no exemplo está na URL, valor 1) e os datos que queremos actualizar (no exemplo actualiza a descripcion soamente). Os datos van en formato JSON como fixemos no paso anterior.
 +
Image:php_laravel_servizos_17.JPG | Probando a ruta (método destroy do controller) que borra un perfume.
 +
</gallery>
 +
 
 +
== Seguridade ==
 +
 
 +
 
 +
'''<u>IMPORTANTE:</u>''' Facade unha copia do proxecto anterior xa que imos probar diferentes formas de autenticación. Podedes copiar e pegar o proxecto e dádalle outro nome ao cartafol raíz do mesmo (por exemplo, beleza_basica, beleza_token,...)
 +
 
 +
* Vimos anteriormente como podemos ter rutas nun servizo que leven consigo modificar a base de datos. O lóxico é que estas rutas estean protexidas por algún tipo de seguridade baseado en autentificación.
 +
 
 +
 
 +
 
 +
 
 +
<br />
 +
* Tedes [http://restcookbook.com/Basics/loggingin/ neste enlace] unha explicación das diferentes formas de seguridade no envío de contrasinais para acceder a recursos.
 +
* [http://www.thegameofcode.com/2012/07/conceptos-basicos-de-oauth2.html Neste enlace] explica en que consiste a seguridade baseada en OAuth2.
 +
 
 +
 
 +
 
 +
 
 +
<br />
 +
* '''<u>NOTA IMPORTANTE:</u>''': Todas as comunicacións có servidor debería empregar http'''S''' para que vaian cifradas.
 +
 
 +
 
 +
<br />
 +
===Seguridade básica===
 +
 
 +
 
 +
* Imos ver a forma menos segura de integrar unha seguridade básica nas rutas do servizo que queiramos protexer.
 +
: Indicar que en todos os casos a comunicación empregando http'''S''' é necesaria para que a información vaia cifrada.
 +
 
 +
 
 +
 
 +
<br />
 +
==== Autenticar ====
  
  
Línea 927: Línea 1065:
  
  
==== Autenticación manual ====
+
<br />
 +
===== Autenticación manual =====
 +
 
 +
 
 +
* '''Creade un proxecto baleiro que sexa copia do proxecto feito ata o de agora. Podedes chamarlle beleza_manual, por exemplo''' e traballade sobre esta copia.
 +
 
  
 
* Neste caso teremos que ter creada unha táboa 'Usuarios' cos campos que nos interesen que se vaian a utilizar para validar.
 
* Neste caso teremos que ter creada unha táboa 'Usuarios' cos campos que nos interesen que se vaian a utilizar para validar.
: Normalmente teremos un campo password 'encriptado' coa orde [https://laravel.com/docs/5.1/hashing bcrypt de Laravel].
+
: Normalmente teremos un campo password 'encriptado' coa orde [https://laravel.com/docs/10.x/hashing bcrypt de Laravel].
  
 
* Os pasos son so seguintes:
 
* Os pasos son so seguintes:
Línea 937: Línea 1080:
  
 
: Creamos a táboa en MYSQL:
 
: Creamos a táboa en MYSQL:
::<syntaxhighlight lang="java" line enclose="div" highlight="6" >
+
::<syntaxhighlight lang="java" enclose="div" highlight="6" >
CREATE TABLE `USERS_LARAVEL` (
+
CREATE TABLE `users_laravel` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
+
   `id` int NOT NULL AUTO_INCREMENT,
 
   `password` varchar(100) NOT NULL,
 
   `password` varchar(100) NOT NULL,
 
   PRIMARY KEY (`id`)
 
   PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;
+
) ENGINE=InnoDB AUTO_INCREMENT=1;
  
 
</syntaxhighlight>
 
</syntaxhighlight>
Línea 949: Línea 1092:
 
: Modificamos o arquivo '''app\User.php''' e quitamos a opción de que o usuario poida resetear o password e indicamos que campos van poder ser 'escritos' (fillable) e que campos non van poder enviarse en formato json (hidden).
 
: Modificamos o arquivo '''app\User.php''' e quitamos a opción de que o usuario poida resetear o password e indicamos que campos van poder ser 'escritos' (fillable) e que campos non van poder enviarse en formato json (hidden).
  
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 
<?php
 
<?php
  
namespace App;
+
namespace App\Models;
  
 
use Illuminate\Auth\Authenticatable;
 
use Illuminate\Auth\Authenticatable;
Línea 974: Línea 1117:
 
     * @var string
 
     * @var string
 
     */
 
     */
     protected $table = 'USERS_LARAVEL';
+
     protected $table = 'users_laravel';
 
     public $timestamps = false;                             
 
     public $timestamps = false;                             
 
                                      
 
                                      
Línea 998: Línea 1141:
 
* Creamos un usuario. Para iso o podemos facer dende Laravel facendo un 'create' do modelo User (User::create(['password' => bcrypt('password')]);) ou ben podemos ir á consola e poñer o comando: '''php artisan tinker'''
 
* Creamos un usuario. Para iso o podemos facer dende Laravel facendo un 'create' do modelo User (User::create(['password' => bcrypt('password')]);) ou ben podemos ir á consola e poñer o comando: '''php artisan tinker'''
 
: A continuación teclearemos: bcrypt('password')
 
: A continuación teclearemos: bcrypt('password')
 +
 +
: Por exemplo, se poñemos 'password' eu obteño: $2y$12$1PYR/k5MbfGRs3DYGD6yRO7da8EGFxDsMaXhGcM4jZY/xjxJzr1Ea
 +
 
: O resultado o copiaremos en Mysql nun rexistro novo dun usuario novo.
 
: O resultado o copiaremos en Mysql nun rexistro novo dun usuario novo.
: Premeremos '''exit''' para saír.  
+
: Premeremos '''exit''' para saír da consola.  
  
  
* Agora na función dentro do controlador, que queiramos protexer teremos que escribir:
+
* Agora na función dentro do controlador, que queiramos protexer teremos que escribir o seguinte tendo en conta que en todas eles debemos de enviar o password:
::<syntaxhighlight lang="java" line enclose="div" highlight="3" >
+
::<syntaxhighlight lang="java" enclose="div" highlight="1,3-6" >
     public function store(Request $request)
+
     public function destroy(Request $request, $id)
 
     {
 
     {
         if (Auth::attempt(['password' => $request->password])){
+
         $user = User::where('password',$request->password)->first();
                // Retornamos a información             
+
        if (!$user){
 +
            return response('Non tes permiso de acceso.', 401);
 
         }
 
         }
         else{
+
         $marca = Marca::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
             return response('Unauthorized.', 401);
+
        if (!$marca){
 +
             return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
 
         }
 
         }
 +
 +
        if (!$marca){
 +
            return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
 +
        }
 +
 +
        // Comprobamos se temos perfumes asociados á marca
 +
        if($marca->perfumes && $marca->perfumes->count()>0){
 +
            return response()->json(['errors'=>['status'=>'409','title'=>'Existen perfumes asociados']],409);
 +
        }
 +
 +
        $marca->delete();
 +
        return response()->json(null,204);
 +
 
     }
 
     }
 +
</syntaxhighlight>
 +
 +
[[Imagen:Php_laravel_servizos_seg_5.JPG|500px]]
 +
 +
  
</syntaxhighlight>
 
  
:* Como vemos o usuario terá que enviar como parámetro o password para poder facer operación.
+
:* Como vemos o usuario terá que enviar como parámetro o password para poder facer a operación.
 
:: Poderíamos poñer máis campos dentro do array da forma: Auth::attempt(['user' => $request->user, 'password' => $request->password])
 
:: Poderíamos poñer máis campos dentro do array da forma: Auth::attempt(['user' => $request->user, 'password' => $request->password])
  
  
  
==== Autenticación proporcionada por Laravel ====
+
: <u>Esta forma non é segura e deberá empregarse o emprego de tokens.</u>
 +
 
 +
===Seguridade baseada en OAuth2 empregando tokens de acceso===
 +
* Laravel 10 ten [https://laravel.com/docs/10.x/passport#passport-or-sanctum dúas formas de implementar a seguridade nun servizo web]:
 +
:* Passport => Elixe Passport se: a túa aplicación necesita cumprir co estándar OAuth2, especialmente se vas interactuar con servizos de terceiros ou proporcionar a túa API a aplicacións de terceiros.
 +
:* Sanctum => Elixe Sanctum se: estás desenvolvendo unha SPA ou unha aplicación móbil que consome unha API no mesmo dominio, e buscas unha solución máis sinxela sen as complexidades de OAuth2.
 +
 
 +
: '''Aclaración:''' Nunha SPA, a maior parte do código necesario (HTML, JavaScript e CSS) cárgase unha soa vez, ou os recursos necesarios cárganse dinamicamente e engádense á páxina segundo sexa necesario, xeralmente en resposta a accións do usuario ou eventos. Isto significa que, despois da carga inicial da aplicación, a SPA pode actualizar o contido que se mostra ao usuario sen necesidade de recargar a páxina completa, o que se logra a través de chamadas asíncronas ao servidor (como peticións AJAX) para obter datos e actualizar a vista presentada ao usuario.
 +
 
 +
 
 +
* Tedes os pasos para implementar Passport en: https://laravel.com/docs/10.x/passport#installation
 +
 
 +
 
 +
'''PASO 1:'''
 +
: Dende a consola e no cartafol do proxecto laravel, instalamos Passport:
 +
 
 +
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 +
composer require laravel/passport
 +
</syntaxhighlight>
 +
 
 +
 
 +
'''PASO 2:'''
 +
: Debemos modificar o arquivo App/config/app.php e engadir o provider: Laravel\Passport\PassportServiceProvider::class,
 +
[[Image:Php_laravel_servizos_seg_2.JPG|500px]]
 +
 
 +
: e premer gardar o arquivo.
  
* Os pasos son so seguintes:
 
  
 +
'''PASO 3:'''
 +
: Dende a consola e no cartafol do proxecto laravel, creamos as táboas necesarias para manexar os tokens:
 +
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 +
php artisan migrate
 +
</syntaxhighlight>
  
:* Escribimos o seguinte comando dende consola: '''php artisan migrate'''
 
::* Isto crea tres táboas: migrate, users, password_resets (máis información sobre as migracións [https://laravel.com/docs/5.1/migrations neste enlace].
 
:: A nos soamente importa a táboa users (as outras poderíamos borralas se non imos facer uso de formularios para o reseteo de password e rexistro de usuarios e a de migrate se non imos traballar con migraciones).
 
  
 +
'''PASO 3:'''
 +
: Dende a consola e no cartafol do proxecto laravel, este comando creará as claves de cifrado necesarias para xerar tokens de acceso seguros. Ademais, o comando creará clientes de "acceso persoal" e "concesión por contrasinal" que se utilizarán para xerar tokens de acceso:
 +
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 +
php artisan passport:install
 +
</syntaxhighlight>
  
  
:* Creamos un middleware có comando: '''php artisan make:middleware AutenticacionBasica'''
 
  
:* Escribimos o seguinte código:
+
'''PASO 4:'''
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
: Modificamos o arquivo App/Models/User.php
 +
::<syntaxhighlight lang="java" enclose="div" highlight="7,8" >
 
<?php
 
<?php
namespace App\Http\Middleware;
+
namespace App\Models;
  
use Illuminate\Support\Facades\Auth;
+
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Closure;
+
use Illuminate\Foundation\Auth\User as Authenticatable;
 +
use Illuminate\Notifications\Notifiable;
 +
//use Laravel\Sanctum\HasApiTokens;
 +
use Laravel\Passport\HasApiTokens;
  
class AutenticacionBasica
+
class User extends Authenticatable
 
{
 
{
 +
    use HasApiTokens, HasFactory, Notifiable;
 +
 
     /**
 
     /**
     * Handle an incoming request.
+
     * The attributes that are mass assignable.
 
     *
 
     *
     * @param  \Illuminate\Http\Request  $request
+
     * @var array<int, string>
    * @param  \Closure  $next
 
    * @return mixed
 
 
     */
 
     */
     public function handle($request, Closure $next)
+
     protected $fillable = [
    {
+
        'name',
         return Auth::onceBasic() ?: $next($request);
+
        'email',
     }
+
         'password',
}
+
     ];
</syntaxhighlight>
 
  
* Usamos un tipo de autenticación básica no que se pide un login-password para ademais non utilizamos cookies para gardar a autenticación no caso dos servizos (stateless). Máis información [https://laravel.com/docs/5.1/authentication#http-basic-authentication neste enlace].
+
  .................
  
 +
</syntaxhighlight>
  
:* Modificamos o arquivo '''/app/Http/Kernel.php''' e rexistramos o middleware:
+
: GARDAR OS CAMBIOS
::<syntaxhighlight lang="java" line enclose="div" highlight="6" >
 
    ..............
 
    protected $routeMiddleware = [
 
        'auth' => \App\Http\Middleware\Authenticate::class,
 
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
 
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
 
        'auth.basic.once' => \App\Http\Middleware\AutenticacionBasica::class,
 
    ];
 
</syntaxhighlight>
 
  
  
* Agora teremos que enviar como login o email e o password (os campos que utiliza por defecto para autenticarse).
 
:: Podemos modificar isto cambiando o middleware: return Auth::onceBasic(<b>'password'</b>) ?: $next($request);
 
:: Agora soamente verificará que o password sexa correcto.
 
:: Tamén podemos poñer outro campo para o 'login', por exemplo: return Auth::onceBasic(<b>'emial','password'</b>) ?: $next($request);
 
:: Esto é igual ao comportamento por defecto...
 
  
* Debemos rexistrar o middleware no controlador. Existen varias formas de facelo, unha delas é no constructor:
+
'''PASO 5:'''
::<syntaxhighlight lang="java" line enclose="div" highlight="3" >
+
: Modificamos o arquivo App/config/auth.php
     ..................
+
::<syntaxhighlight lang="java" enclose="div" highlight="6-9" >
    public function __construct(){
+
     'guards' => [
         $this->middleware('auth.basic.once');
+
        'web' => [
     }
+
            'driver' => 'session',
 +
            'provider' => 'users',
 +
        ],
 +
         'api' => [
 +
            'driver' => 'passport',
 +
            'provider' => 'users',
 +
        ],
 +
     ],
 
</syntaxhighlight>
 
</syntaxhighlight>
  
: Se queremos que o middleware afecta soamente a algún/s método/s: $this->middleware('auth.basic.once',['only' => ['store']]);
+
: GARDAR OS CAMBIOS
  
  
  
* Agora no controlador, se queremos verificar se o usuario pasou a autenticación podemos facelo coa orde: '''Auth::check()''' (facendo un use da clase Auth).
 
  
 +
'''PASO 6:'''
 +
: Agora necesitamos algunha forma de que un usuario poida rexistrarse e obter o API KEY. Para iso imos empregar un controlador e unha ruta de nome /register na que enviaremos un nome, un email e un password e devolveranos o token (que tería que gardar dito usuario) para poder facer operación sobre a base de datos.
 +
: Fixarse que neste caso deixamos rexistar aos usuarios. Nunha empresa, ditos usuarios xa estarían previamente rexistrados a través dun formulario web dentro da organización.
  
* Para enviar a información do login/password:
+
: Executamos dende a consola no cartafol do proxecto o comando: php artisan make:controller ApiAuthController
: Para facelo, esta información ten que ir na cabeceira.
+
: Arquivo /app/Http/Controllers/ApiAuthController.php
 +
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 +
<?php
  
:* Se estamos a utilizar o programa HTTP REQUEST debemos premer no botón '''Authentification''' e aparecerán dúas caixas de texto, unha para o login e outra para o password.
+
namespace App\Http\Controllers;
:: Esta información irá na cabeceira codificada en base64 polo que pode ser facilmente descifrada se os paquetes son capturados.
 
  
[[Imagen:php_laravel_servizo_1.jpg|500px|center]]
+
use App\Models\User;
 +
use Illuminate\Http\Request;
 +
use Illuminate\Support\Facades\Hash;
 +
use Laravel\Passport\Passport;
  
 +
class ApiAuthController extends Controller
 +
{
  
 +
    public function register(Request $request)
 +
    {
 +
        $validatedData = $request->validate([
 +
            'name' => 'required|max:255',
 +
            'email' => 'required|email|unique:users',
 +
            'password' => 'required|min:6',
 +
        ]);
  
:* Se queremos utilizar un cliente que se conecte á nosa API (isto o veremos no seguinte punto):
+
        $user = User::create([
 +
            'name' => $validatedData['name'],
 +
            'email' => $validatedData['email'],
 +
            'password' => Hash::make($validatedData['password']),
 +
        ]);
  
::<syntaxhighlight lang="java" line enclose="div" highlight="3-6" >
+
         $token = $user->createToken('Personal Access Token')->accessToken;
         $client = new \GuzzleHttp\Client();
 
        $res = $client->get('http://localhost:8000/marcas',[
 
            'auth' =>
 
                ['username',
 
                'password'
 
                ]
 
        ]);
 
        echo $res->getStatusCode(); // 200
 
        $datos = json_decode($res->getBody(),true);
 
  
</syntaxhighlight>
+
        return response()->json(['token' => $token], 200);
 +
    }
  
== Usar un servizo externo ==
+
}
  
* [http://guzzle.readthedocs.io/en/latest/ Gruzzle].
+
</syntaxhighlight>
: * Comando para instalación: '''composer require guzzlehttp/guzzle'''
 
  
 +
: Fixarse que o que devolve é en formato JSON o token xerado. É importante que a comunición esta cifrida (https) para que non se obteña dito token.
  
=== Exemplo de código ===
 
  
  
* Neste código imos amosar como facer unha chamada a unha API Externa.
+
'''PASO 7:'''
 +
: Agora temos que protexer as rutas.
 +
: Imos protexer todas as rutas que modifiquen a base de datos e deixamos sen protexer as que sean de consultas.
 +
: Creamos unha ruta para poder rexistrarse e obter o token.
 +
: Arquivo /app/routes/api.php
 +
::<syntaxhighlight lang="java" enclose="div" highlight="7,21,24,26,27,33,34" >
 +
<?php
  
::<syntaxhighlight lang="java" line enclose="div" highlight="3,9-13" >
+
use Illuminate\Support\Facades\Route;
  
........
+
use App\Http\Controllers\MarcaController;
use \GuzzleHttp\Client;
+
use App\Http\Controllers\PerfumeController;
 +
use App\Http\Controllers\ApiAuthController;
  
class ApiExternaController extends Controller
+
/*
{
+
|--------------------------------------------------------------------------
 +
| API Routes
 +
|--------------------------------------------------------------------------
 +
|
 +
| Here is where you can register API routes for your application. These
 +
| routes are loaded by the RouteServiceProvider and all of them will
 +
| be assigned to the "api" middleware group. Make something great!
 +
|
 +
*/
  
    public function index()
 
    {
 
        $client = new \GuzzleHttp\Client();
 
        $res = $client->get('https://XXXX.XXXXX.XX/api/XXXXXXXX/XXXXXX/json?key=XXXXXXXXXXXXXX&query=parametro&language=es');
 
        echo $res->getStatusCode(); // 200
 
        $datos = json_decode($res->getBody(),true);
 
        var_dump($datos);
 
    }
 
    ..................
 
  
</syntaxhighlight>
+
Route::post('/register', [ApiAuthController::class, 'register']);
  
* Liña 3: Temos que facer uso da clase Client de GuzzleHttp.
+
// RUTAS PROTEXIDAS
* Liña 10: Facemos a petición a API Externa. Fixarse que normalmente teredes que enviar algún tipo de autenticación. Neste caso enviamos unha API Key xa que o servizo pertence a Google.
+
Route::group(['prefix'=>'v1', 'middleware' => 'auth:api'], function()
:: Dita API espera recibir na URL o formato de saída (xml ou json), e tamén pode levar parámetros como uha cadea de busca ou a linguaxe do resultado.
+
{
* Liña 11: Podemos obter o código de erro devolto pola API (200 é correcto como xa vimos anteriormente).
+
    Route::apiResource('marcas', MarcaController::class)->only(['update','destroy','store']);
* Liña 12: Utilizamos a [http://php.net/manual/es/function.json-decode.php función json_decode] có parámetro a true para que devolva os datos como un array asociativo.
+
    Route::apiResource('marcas.perfumes', PerfumeController::class)->only(['update','destroy','store'])->shallow();
 +
});
  
 +
// RUTAS SEN PROTEXER
 +
Route::group(['prefix'=>'v1'], function()
 +
{
 +
  Route::apiResource('marcas', MarcaController::class)->except(['update','destroy','store']);
 +
  Route::apiResource('marcas.perfumes', PerfumeController::class)->except(['update','destroy','store'])->shallow();
 +
  Route::get('perfumes',[PerfumeController::class,'index_todos']);
 +
});
 +
</syntaxhighlight>
  
* O var_dump dará como resultado:
 
::<syntaxhighlight lang="java" line enclose="div" highlight="6" >
 
array (size=4)
 
  'html_attributions' =>
 
    array (size=0)
 
      empty
 
  'next_page_token' => string 'CvQB5QAAAB-Q718qEtEfzZnccxK0THwIjfCrwGbkVsvEoQaiGyMWBj6Z3-DW1_9UWJaKANfpAahtoMP7AEjcb1JzLzRvJa9gBIueMXhxGzSPhHwKXxiI6Gp2NVQUHRcr0wW2VgAmVHKCGYid01wI8C_r0SaD3iHIB-0Fr5h2IRy4DsmjN6L9oJMaOQTBtAgCN7sCH6imRmMd2lq5XZW9pWHA3JeOAJt4HhKb34X8fjNNKgiIvOi0XPPcRPoIYJ-NcphGShTgA_Fhnl02vyqnn8mbzGMA9xU4yBObOx-cfd5qMKHYLdb5p0L6ctNa_SIV5YM_YUSFwRIQthy2TYyPTmUNwbBfFDMP5xoUPNoKN8AtwpDHFVgOb44DjIBGffU' (length=383)
 
  'results' =>
 
    array (size=20)
 
      0 =>
 
        array (size=10)
 
          'formatted_address' => string 'Rúa María, 2, 15402 Ferrol, A Coruña, España' (length=48)
 
          'geometry' =>
 
            array (size=2)
 
              ...
 
          'icon' => string 'https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png' (length=67)
 
          'id' => string '7c7088e7c654ba30efa1e384e0881bebee8cf065' (length=40)
 
          'name' => string 'Asador Gavia' (length=12)
 
          'photos' =>
 
            array (size=1)
 
              ...
 
          'place_id' => string 'ChIJ22OacwV2Lg0RLObpSmq3D6k' (length=27)
 
          'rating' => float 4.2
 
          'reference' => string 'CmRSAAAAG-LM_2yc9TTbC8l4HoczU2OPnFPs6eYHzoLwJBwGeSiFT9XvalSDcOb0bLZXHDxp3tSAswl57eXpcZGvAIDiYfckbnl7eIxhLl722qHdn4qv-py6_umtqt2pvzoWGC3EEhA8MgKa1gD9lEzOD_EGSqlgGhT8YjPOdYP3d9C9oB9IdHEr92yvEQ' (length=190)
 
          'types' =>
 
            array (size=4)
 
              ...
 
      1 =>
 
        array (size=10)
 
          'formatted_address' => string 'Lugar Bosque, 73 - Bajo, 15405 Ferrol, A Coruña, España' (length=57)
 
          'geometry' =>
 
            array (size=2)
 
              ...
 
          'icon' => string 'https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png' (length=67)
 
          'id' => string 'd0d3a7fb96efe558e00268ba18bf7c9f7e003d67' (length=40)
 
          'name' => string 'Medulio' (length=7)
 
          'photos' =>
 
            array (size=1)
 
              ...
 
          'place_id' => string 'ChIJI7bO3KPYLQ0R5CVGKcTigTg' (length=27)
 
          'rating' => float 4.2
 
          'reference' => string 'CmRRAAAA_pB5EDe4puf3vqpGI2ACBvg5C5AchtIyS306-m7j_TBzbYsN9KNz8zU-W2u7qhPh1WptaIC5La2JXJxOeGfIlqekxmtKF27PQwz8q503Nbizc7sNH7OKcIhbKm_rssA1EhC9ikBsyUqi3ruykyou0NHGGhTpsMBsUvdbBYkcutfmME-AuOwzWA' (length=190)
 
          'types' =>
 
            array (size
 
</syntaxhighlight>
 
  
 +
'''PASO 8:'''
 +
: Agora xa podemos probar as rutas, pero necesitamos primeiro o token.
 +
: Para obter o token temos que empregar curl dende a consola (lembrar que é desexable que a comunicación sexa http'''S''').
 +
: <u>Importante:</u> Mirade que cumprides coas regras de validación dos campos que están definidos no controlador. Por exemplo, o password está definido que ten que ter definido como mínimo 6 caracteres.
 +
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 +
curl -X POST http://localhost:8000/api/register \
 +
    -H "Content-Type: application/json" \
 +
    -d '{"name":"angel","email":"angel@prueba.es","password":"angelangel"}'
 +
</syntaxhighlight>
  
* Liña 6: A nos interésanos a clave 'results', polo tanto:  <b>$datos['results']</b> (que é un array asociativo) accederemos ao conxunto de resultados. Soamente teremos que percorrer dito array e por cada elemento acceder ás propiedades que nos interesen ($dato['name'] ou $dato['formatted_address'] por exemplo).
+
: Ao executar o código anterior vos devolverá o token. '''GARDADEO xa que vai ser empregado polas rutas protexidas'''.
:: Esta información sería gardada nun array que será enviado á vista para que amose o resultado.
+
: Exemplo de token:
 
+
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIxIiwianRpIjoiZTNlYTE1MWY4NTU5NTc5ZjIwZmUwZWNlNzAyNjA3NjQ5NThjZmRhYjQ3MzExZDY4ZjhiOThlYmZmODgwNDI1MWExMTVmZjE2ZWEyYzBlNDQiLCJpYXQiOjE3MDg5NDc1NjIuMTY0OTE5LCJuYmYiOjE3MDg5NDc1NjIuMTY0OTIsImV4cCI6MTc0MDU2OTk2Mi4xMzY1ODYsInN1YiI6IjEiLCJzY29wZXMiOltdfQ.F7QoYQW3D9KM8Kk5I2UEXk-5Y5lKuhBb5T59WZ6JP3lv_DGf3IP7boBJZV_0kTbjhPSKBUuJ8-ig4br4ER9qP8U7k4FobY_LUu2oO3V9iTXi4ep7N28-2TVsZnMsYIiLXqeOV7lh1hY3ubxXdplUICEoYIuIx5yqApuwJ3HZYvQ99aUz2LUhJRbW3RdBnsbJOXuyEIt1rOeRCRBLz1gJjVRWjmHiKHhMbcphPmDlZT6LGZL30c_u_HJlShXZB1ZJCrDY3yKHx05vjr0bkk5TFnKZvSXhU_aJb4J5INijV1ZlbXNmuGU9CpajJ9ogckWQztvU8zbS3ZIZKM_rHm5lf2ciiJ1-CTXB4yLzcOEjQQdAK1U5N_vhN90OP73b-Km6SEDEJ1OI-mmtmUaqTSbZBceUq7tkbOU6PuEJsA8ptlNN-aTtRZVuLQHP6zJhIQ_9JVQ1r5pRo39e3LXZfaGNzzCTgMykjAfMv5LtcHb3Mcb1mMSaR7BqsXKaHJ8hAps66K6PsWrILZoNAUM0rsHqxQrUUC_YGmc6k4h2ITCGmejY0199lI6ojEvJ8UW3JNLRLr1rYDrjbx2zZZf_DOOQY4nQXRR8dPCXoexAT_QgpCzYtomzDL4I0CObqJFD_Vg-n3Gv-THnQNQXafPG8NUMEBQHxtIR1XTZ1QRSp7EpKhE
  
  
  
* Se a API espera recibir un login e ou password, debemos envialo desta forma:
+
'''PASO 9:'''
::<syntaxhighlight lang="java" line enclose="div" highlight="3-6" >
+
: Agora só queda probar as rutas dende unhas das utilidades:
        .....................
+
: Exemplo de ruta protexida. Ao non enviar o token da un erro.
        $res = $client->get('url',[
+
[[Imagen:Php_laravel_servizos_seg_3.JPG|800px]]
            'auth' =>
 
                ['username',
 
                'password'
 
                ]
 
        ]);
 
</syntaxhighlight>
 
  
 +
: Exemplo de ruta protexida. Ao enviar o token funcion (neste caso avisa que existe un perfume asociado á marca)
 +
: Para enviar o token, este vai na cabeceira (header) e ten que levar:
 +
:* Nome: Authorization
 +
:* Password: '''Bearer ''' e o token (ten que poñer Bearer e un espazo en branco)
 +
[[Image:Php_laravel_servizos_seg_4.JPG|800px]]
  
* Lembrar que esta información envíase cifrada con base64 e se se captura o tráfico pódese descifrar facilmente.
 
: O ideal sería utilizar unha comunicación '''https''' cifrada con SSL.
 
  
  
== Seguridade ==
 
 
* Tedes [http://restcookbook.com/Basics/loggingin/ neste enlace] unha explicación das diferentes formas de seguridade no envío de contrasinais para acceder a recursos.
 
* [http://www.thegameofcode.com/2012/07/conceptos-basicos-de-oauth2.html Neste enlace explica en que consiste a seguridade baseada en OAuth2.
 
  
* Tedes [https://medium.com/@mshanak/laravel-5-token-based-authentication-ae258c12cfea neste enlace] un exemplo para Laravel de OAuth2.
 
  
  
Línea 1237: Línea 1410:
  
  
<br> -- [[Usuario:angelfg|Ángel D. Fernández González]] -- (2017).
+
<br> -- [[Usuario:angelfg|Ángel D. Fernández González]] -- (2024).

Revisión actual del 18:37 3 mar 2024

Introdución

  • Para entender esta sección, os alumnos deben ter realizados os exercicios e visto os vídeos nos que se explica o funcionamento de Laravel.
Partimos dun proyecto de Laravel de nome beleza, creado coa orde: composer create-project laravel/laravel beleza "10.*"


  • Para facilitarnos o traballo ides a instalar no Visual Studio Code a extensión Laravel Extension Pack que nos vai axudar a programar en Laravel.






Manual de exemplo

Aclaracións ao manual anterior:
  • Previamente tedes que ter creada unha base de datos en MySql (eu vou poñer de nome api-new) cun usuario / password de acceso para poder facer operacións de create, alter, drop, insert, update, delete e select sobre a base de datos.
Unha vez feito, modificade o arquivo .env nas entradas DB_YYYYYYYYY que se atopen.
  • Paso 1: Para crear o proxecto: composer create-project laravel/laravel api-new
  • Paso 2.1:
  • O arquivo para modificar o método run se atopa en: /database/seeders/UserSeeder.php
  • Hai que empregar estos uses para que acceda ás clases User e Hash:
use App\Models\User;
use Illuminate\Support\Facades\Hash;
  • Paso 2.1: O comando é con dous guións: php artisan migrate --seed
  • Paso 3.1: O comando é con dous guións: php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"



Servizo REST: Características

  • REST é o acrónimo de Representational State Transfer.


  • Para que sexa considerado REST ten que cumprir estas características:
  • Baseado nun protocolo de cliente/servidor sen estado (protocolo Http)
  • Cacheable
  • Escalable
  • Cunhas operacións ben definidas no que os recursos estean identificados de forma única por URIs.


  • Ao utilizar o protocolo HTTP, xa vimos que non garda o estado polo que deberemos de enviar os datos necesarios para a autenticación en cada petición que fagamos.


  • Cada petición que fagamos levará asociada o uso dun verbo que indicará un tipo de acción a realizar:
  • GET: Utilízase para acceder a un recurso
  • POST: Envía datos para crear un recurso. Os datos non van na URI, se non no corpo da mensaxe.
  • PUT: Utilizado para editar un recurso.
  • DELETE: Elimina un recurso
  • PATCH: Utilízase para modificar parcialmente un recurso. Normalmente faise con PUT.


  • Cando fagamos algunha das operacións anteriores deberemos de informar a quen fixo a petición do éxito ou fracaso da operación. Non existe un estándar que nos obrigue a utilizar uns códigos ou outros, pero deberemos de aproveitar' os código HTML para facilitar o 'consumo' do noso servizo REST:
  • 200 → OK : Petición recibida e procesada de forma correcta
  • 201 → Created : Petición completada. Creouse un novo recurso
  • 204 → No Content: Petición procesada correctamente, pero a resposta non ten ningún contido
  • 400 → Bad Request: Indica que faltan datos para solicitar la información o están mal o incompletos.Por exemplo, para dar de alta un alumno, necesitas o seu nome. Se non se envía como dato enviarímos este código de volta.
  • 401 → Unauthorized: A información de autenticación non é válida
  • 404 → Not found: O recurso non se atopou.
Temos neste enlace os código de resposta do protocolo HTTP.
Temos neste enlace a sintaxe da resposta que debe dar un servizo e cando se deben enviar cada un dos códigos de resposta do protocolo HTTP.


  • Cacheable:
Isto quere dicir que ten que ter a posibilidade de poder 'cachear' os resultados para ter un mellor desempeño.
As peticións deben indicar se o resultado pode ou non ser cacheado.


  • Escalable:
O servidor encargado de recibir e procesar as respostas debe ser capaz de ser dividido en capas (cada capa se encargará de procesar as peticións de diferentes recursos). Desta forma podemos ter diferentes políticas de seguridade.


  • Identificación de recursos mediante URIs:
Normalmente un servizo REST vai permitir realizar operacións de consulta, actualización, engadir e borrado sobre diferentes elementos de informacións. Es estes elementos denomínanse recursos (por exemplo, engadir un novo libro se xestionamos unha librería, obter o listado de clientes, obter o prezo de todas as pantallas de 14 se xestionamos unha tenda de computadores,...).
Cando fagamos unha operación sobre un destes recursos, temos que utilizar unha URI que deberá cumprir as seguintes propiedades:
  • Deben ser únicas, non pode existir máis dunha URI para identificar o mesmo recurso.
  • Deben ser independentes do formato no que queiramos consultar el recurso (a URI debe ser a mesma se devolvemos a información en JSON ou XML, por exemplo)
  • Deben manter una xerarquía na ruta do recurso
  • No deben indicar accións, polo que non debemos usar verbos á hora de definir unha URI (como viñamos facendo nas aplicacións WEB)


Por exemplo, se queremos xestionar un recurso 'libros':


Nota: Normalmente o recurso estará en minúsculas e en plural a no ser que o recurso sexa único (por exemplo a configuración).
En caso de querer filtrar os recursos faremos uso da URI e enviaremos a información de filtrado usando parámetros da forma: ?param1=valor1&param2=valor.
Por exemplo, se queremos obter a lista de libros de informática: http://meusitio.es/libros?tematica=informatica.
Temos que ter coidado coa xerarquía de recursos e acceder atendendo a dita xerarquía.
Así se xestiono varias tendas de libros, o acceso a un libro dunha tenda debería ser: http://meusitio.es/tendas/1/libros/5 e non ao revés (http://meusitio.es/libros/5/tendas/1).


Outras características que deberían cumprir as URI's:
  • Utilizar minúsculas y guións ou guións baixos (snake-case) no canto de maiúsculas y minúsculas (CamelCase)
  • Non utilizar caracteres que necesiten codificación URL como por exemplo espazos en branco, comillas, etc.
  • Non utilizar parámetros de consulta (?tipo=1) en peticións que no sexan de consulta.


  • Formato de resposta:
Normalmente o formato vai ser XML ou JSON.
Como comentamos anteriormente na URI non debe ir ningunha información sobre o tipo de formato da resposta.
O tipo de formato da resposta será indicado na cabeceira da petición.

Creación dun servizo web

Base de datos

  • Partimos da seguinte base de datos de nome belleza.
CREATE SCHEMA `belleza` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_spanish2_ci ;


  • A base de datos está composta polas seguintes táboas:

Táboa MARCAS:

CREATE TABLE `belleza`.`marcas` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `descripcion` VARCHAR(100) NOT NULL,
  PRIMARY KEY (`id`));

Táboa perfumes:

CREATE TABLE `belleza`.`perfumes` (
  `id` int NOT NULL AUTO_INCREMENT,
  `descripcion` varchar(45) NOT NULL,
  `prezo` decimal(8,2) NOT NULL,
  `data_compra` date DEFAULT NULL,
  `marca_id` int NOT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_PERFUMES_1_idx` (`id`),
  KEY `fk_PERFUMES_MARCA_idx` (`marca_id`),
  CONSTRAINT `fk_PERFUMES_MARCA` FOREIGN KEY (`marca_id`) REFERENCES `marcas` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_spanish2_ci;


  • Algúns datos de exemplo:
INSERT INTO `marcas` VALUES (1,'CHANNEL'),(2,'ADIDAS'),(3,'ARAMIS');
INSERT INTO `perfumes` VALUES (1,'Channel 1',46.22,'2017-02-01',1),(2,'Adidas',1.33,NULL,2),(18,'Best Aramis',54.32,'2024-01-18',3);

Nota: Indicar que Laravel como xa comentamos anteriormente permite a creación de táboas usando migracións e enchendo de datos con seeders.


  • Agora en Laravel creamos os modelos correspondentes a cada táboa:
Podemos crealos dende consola, coa orde: php artisan make:model NomeDoModelo
Arquivo /app/Models/Marca.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Marca extends Model
{
    protected $table ='marcas';   // Non faria falta poñelo
    protected $primaryKey='id';   // Non faria falta poñelo
    
    public $timestamps = false;
    
    protected $fillable =   [
                                'descipcion'
                            ];
    // Se queremos que certos campos non sexan enviados en formato json os poñemos na propiedade $hidden
    // protected $hidden = ['password'];

    
    public function perfumes(){
        return $this->hasMany('App\Models\Perfume','marca_id','id');
    }
}


Arquivo /app/Perfume.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Perfume extends Model
{
    protected $table ='perfumes';
    protected $primaryKey='id';
    
    public $timestamps = false;
    
    protected $dates = ['data_compra'];
    //protected $dateFormat = 'd-m-Y';

    protected $fillable =   [
                                'descipcion',
                                'prezo',
                                'data_compra',
                                'marca_id'
                            ];
    // Se queremos que certos campos non sexan enviados en formato json os poñemos na propiedade $hidden
    // protected $hidden = ['password'];

    public function marca(){
        return $this->belongsTo('App\Models\Marca','marca_id','id');
    }
}
  • Liña 15: Se devolvemos datos en json podemos facer uso do atributo $dateFormat para que os campos de tipo Date teñan un formato específico. O problema desta solución é que despois da un erro cando engadimos datos....

Rutas

  • Nota: Lembrar que nun servizo non existen formularios de entrada de datos, polo que as rutas/funcións que dan acceso a ditos formularios sobran.
  • Partimos da idea de que un perfume sempre vai pertencer a unha marca e polo tanto non existe se non existe a marca.


  • As rutas que imos ter van ser:



  • Imos ter polo tanto dous controladores asociados a estas rutas:
  • MarcaController
  • PerfumeController


  • Ningún deles vai ter unha vista que vaia a devolver ao usuario, polo tanto nas rutas podemos non crear as opcións 'create' e 'edit' xa que estas son empregadas para amosar unha vista (formulario) para crear un modelo e modificar un modelo.
Os comandos para crear ditos controladores serán:
  • Versión 5.1 ou anteriores:
php artisan make:controller MarcaController
php artisan make:controller PerfumeController
  • Versión 10 ou posteriores:
php artisan make:controller MarcaController --api
php artisan make:controller PerfumeController --api


O que estamos a facer poñendo --api é que o controlador non teña os métodos create e edit.


  • Nota: Na creación dos controladores podedes poñer a seguinte opción: php artisan make:controller MarcaController --api --model=Marca
O que estamos a facer poñendo --model=Modelo é que os métodos do controlador van ter como entrada un obxecto da clase asociada. Nos imos enviar a ditos método o id da marca e laravel vaise encargar de buscar todos os datos na BD e inxectalos no obxecto.
Por exemplo, no caso de Marca:
    public function destroy(Marca $marca)
    {
        //
    }
No caso de servizos web non imos empregar esta forma xa que en caso de non atopar a marca devolvería unha páxina web e nos imos devolver un código de erro en formato json.


  • Agora necesitamos crear as rutas que van a relacionar unha url cun método do controlador.
Importante: A partires de Laravel 5.3 as rutas do servizo REST deberían gardarse no arquivo routes/api.php e as rutas 'normais' no arquivo routes/web.php.
Ao facelo desta forma, se optimizan os recursos empregados por Laravel para atender as peticións (nas peticións normais se están aplicando filtros para inicializar a sesión, as cookies, os bindings e a protección CSRF).
Se queremos acceder a unha ruta que se atopa definida en api.php, teremos que empregar a palabra api na ruta desta forma: http://meusitio.es/api/marcas/{marcas}.



  • O podemos facer de forma automática ou ben manualmente.
  • Automaticamente:
Editamos o arquivo /app/routes/api.php e escribimos esta liña:
use App\Http\Controllers\MarcaController;
....
Route::apiResource('marcas', MarcaController::class);  // Ao ser apiResource xa non engade as rutas create e edit
O anterior crea as seguintes rutas de forma automática:
  GET|HEAD        api/marcas ............................................................................ marcas.index  MarcaController@index
  POST            api/marcas ............................................................................ marcas.store  MarcaController@store
  GET|HEAD        api/marcas/{marca} ...................................................................... marcas.show  MarcaController@show
  PUT|PATCH       api/marcas/{marca} .................................................................. marcas.update  MarcaController@update
  DELETE          api/marcas/{marca} ................................................................ marcas.destroy  MarcaController@destroy
  • Manualmente, editando o arquivo /app/routes/api.php
Route::get('/marcas', [MarcaController::class, 'index']);
Route::post('/marcas', [MarcaController::class, 'store']);
Route::get('/marcas/{marca}', [MarcaController::class, 'show']);
Route::put('/marcas/{marca}', [MarcaController::class, 'update']);
Route::delete('/marcas/{marca}', [MarcaController::class, 'destroy']);



  • É unha práctica recomendable antepoñer un prefixo ás rutas para indicar a versión da API que estamos a definir. De tal forma que se cambiamos as rutas ou parámetros nunha nova versión, poderíamos deixar funcionando a versión anterior da api. Para poñer un prefixo temos que facer no arquivo /app/routes/api.php:
use App\Http\Controllers\MarcaController;
.....

Route::group(['prefix'=>'v1'], function()
{
  Route::apiResource('marcas', MarcaController::class);
});
Utilizando números de versións podemos facer que diferentes clientes utilicen diferentes versións nas que podemos aumentar as funcionalidades. Por exemplo nunha versión v2 poderíamos ter unha nova ruta (teríamos que repetir todas as rutas anteriores máis a nova).


  • Unha vez creadas as rutas podemos comprobalas có comando: php artisan route:list
Polo de agora có anterior cubrimos as rutas que teñen que ver coa marca.
Faltan as rutas dos perfumes.
  • Primeiro debemos crear o controlador para os perfumes se non o temos feito anteriormente: php artisan make:controller PerfumeController --api --model=Perfume
  • Agora para as rutas, poderíamos facer o mesmo que con marca, pero desa forma a ruta creada para dar de alta un perfume sería: POST /api/v1/perfumes.
Sen embargo, se queremos empregar o nomeado de rutas nun Restful debería ser algo parecido a isto: POST /api/v1/marcas/XX/perfumes, indicando que se quere dar de alta un perfume da marca XX.


Se lembrades, iso é o que estaba posto ao principio de todo:


  • Como podemos lograr isto ?
Pois indicando que perfume depende de marca no arquivo onde definimos as rutas.
Arquivo /app/routes/api.php
use App\Http\Controllers\MarcaController;
use App\Http\Controllers\PerfumeController;
.....
Route::group(['prefix'=>'v1'], function()
{
  Route::apiResource('marcas', MarcaController::class);
  Route::apiResource('marcas.perfumes', PerfumeController::class);

});


Fixarse como está posto 'marcas.perfumes'. Isto fai que se cre as seguintes rutas:
  GET|HEAD        api/v1/marcas/{marca}/perfumes ............................................. marcas.perfumes.index  PerfumeController@index
  POST            api/v1/marcas/{marca}/perfumes ............................................. marcas.perfumes.store  PerfumeController@store
  GET|HEAD        api/v1/marcas/{marca}/perfumes/{perfume} ..................................... marcas.perfumes.show  PerfumeController@show
  PUT|PATCH       api/v1/marcas/{marca}/perfumes/{perfume} ................................. marcas.perfumes.update  PerfumeController@update
  DELETE          api/v1/marcas/{marca}/perfumes/{perfume} ............................... marcas.perfumes.destroy  PerfumeController@destroy


Pero sobre isto podemos facer unha mellora. Se vos fixades, para borrar un perfume concreto estou poñendo como ruta: api/v1/marcas/{marca}/perfumes/{perfume} pero isto non ten moito sentido, xa que {perfume} vai ser o perfume concreto. Non necesitaría enviarlle a marca á que pertence dito perfume.
As rutas de show, update e delete non necesitan a marca. Para simplificar ditas rutas podemos empregar un método shallow() da forma:
use App\Http\Controllers\MarcaController;
use App\Http\Controllers\PerfumeController;
.....
Route::group(['prefix'=>'v1'], function()
{
  Route::apiResource('marcas', MarcaController::class);
  Route::apiResource('marcas.perfumes', PerfumeController::class)->shallow();

});
Desta forma se crean estas rutas:
  GET|HEAD        api/v1/marcas/{marca}/perfumes ............................................. marcas.perfumes.index  PerfumeController@index
  POST            api/v1/marcas/{marca}/perfumes ............................................. marcas.perfumes.store  PerfumeController@store
  GET|HEAD        api/v1/perfumes/{perfume} ........................................................... perfumes.show  PerfumeController@show
  PUT|PATCH       api/v1/perfumes/{perfume} ....................................................... perfumes.update  PerfumeController@update
  DELETE          api/v1/perfumes/{perfume} ..................................................... perfumes.destroy  PerfumeController@destroy


Se vos fixades, tanto store (crea un perfume da marca indicada) como index (amosa todos os perfumes da marca indicada) van necesitar o id da marca ao que pertence o perfume.
Polo tanto imos necesitar modificar os métodos do controlador para que reciban dita información dende a url.
Arquivo /app/Http/Controllers/PerfumeController.php
....
    /**
     * Display a listing of the resource.
     */
    public function index($id)
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request,$id)
    {
        //
    }

.....


  • Agora soamente quedan por definir a seguinte ruta:
Creamos unha función no controlador de Perfumes (/app/Http/Controllers/PerfumeController.php) para esta rutas:
  • public function index_todos() {}: Que amosará todos os perfumes.
Creamos a ruta no arquivo /app/routes/api.php:
....
Route::group(['prefix'=>'v1'], function()
{
  Route::apiResource('marcas', MarcaController::class);
  Route::apiResource('marcas.perfumes', PerfumeController::class)->shallow();
  Route::get('perfumes',[PerfumeController::class,'index_todos']);

});

.....
Có anterior temos todas estas rutas:
  GET|HEAD        api/v1/marcas ......................................................................... marcas.index  MarcaController@index
  POST            api/v1/marcas ......................................................................... marcas.store  MarcaController@store
  GET|HEAD        api/v1/marcas/{marca} ................................................................... marcas.show  MarcaController@show
  PUT|PATCH       api/v1/marcas/{marca} ............................................................... marcas.update  MarcaController@update
  DELETE          api/v1/marcas/{marca} ............................................................. marcas.destroy  MarcaController@destroy
  GET|HEAD        api/v1/marcas/{marca}/perfumes ............................................. marcas.perfumes.index  PerfumeController@index
  POST            api/v1/marcas/{marca}/perfumes ............................................. marcas.perfumes.store  PerfumeController@store
  GET|HEAD        api/v1/perfumes .............................................................................. PerfumeController@index_todos
  GET|HEAD        api/v1/perfumes/{perfume} ........................................................... perfumes.show  PerfumeController@show
  PUT|PATCH       api/v1/perfumes/{perfume} ....................................................... perfumes.update  PerfumeController@update
  DELETE          api/v1/perfumes/{perfume} ..................................................... perfumes.destroy  PerfumeController@destroy

Probando as rutas

  • IMPORTANTE:
  • Lembrar que debe estar iniciado o servidor web de Laravel, executando dende consola e dentro do cartafol do proxecto: php artisan serve
  • Lembrar que a ruta, se a tedes 'agrupada' polo número de versión será: http://localhost:8000/api/v1/???????


  • Antes de probar as rutas imos a modificar o Controller para que amose un texto en cada un dos métodos que vai responder a cada unha das rutas.
Arquivo: /app/Http/Controller/MarcaController.php
<?php

namespace App\Http\Controllers;

use App\Models\Marca;
use Illuminate\Http\Request;

class MarcaController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        return "Amosa todas as marcas";
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        return "Crea una nova marca: $request->descripcion";
    }

    /**
     * Display the specified resource.
     */
    public function show($id)
    {
        $marca = Marca::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
        return "Amosa os datos da marca: $marca->descripcion";
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request,$id)
    {
        $marca = Marca::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
        return "Modifica os datos ($request->descripcion) da marca $marca->id";
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy($id)
    {
        $marca = Marca::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
        return "Borra a marca $marca->id";
    }
}


Arquivo: /app/Http/Controller/PerfumeController.php

<?php

namespace App\Http\Controllers;

use App\Models\Perfume;
use Illuminate\Http\Request;

class PerfumeController extends Controller
{

    public function index_todos(){
        return 'Amosa todos os perfumes';
    }
    /**
     * Amosa os perfumes dunha marca dada
     */
    public function index($id_marca)
    {
        $marca = Marca::find($id_marca);  // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
        return "Amosa todos os perfumes da marca indicada $marca->id";
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request,$id_marca)
    {
        $marca = Marca::find($id_marca);  // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
        return "Garda un perfume novo ($request->descripcion) que pertence á marca indicada: $marca->id";
    }

    /**
     * Display the specified resource.
     */
    public function show($id)
    {
        $perfume = Perfume::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa o perfume
        return "Amosa os datos do perfume indicado: $perfume->descripcion";
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, $id)
    {
        $perfume = Perfume::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa o perfume
        return "Modifica os datos ($request->descripcion) do perfume indicado ($perfume->id)";
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy($id)
    {
        $perfume = Perfume::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa o perfume
        return "Borra o perfume: $perfume->id";
    }
}


  • Para comprobalas podemos facer uso:
Máis información neste enlace.
  • Complemento para:
  • Firefox:
  • Chome::




  • Exemplos para probar as rutas:
  • Empregando a extensión de Chrome.
  • Probando rutas con Chrome
  • Unha vez instalado, o executamos. Prememos no nome da extensión.

  • Exemplo de chamada empregando o verbo GET.

  • Ao ser unha chamada de tipo GET podemos empregar o navegador.

  • Exemplo de chamada empregando o verbo POST. Ao ser unha chamada cunha API, está desabilitado a protección por token CSRF.


    • Empregando o comando curl:
    curl -i "http://localhost:8000/api/v1/marcas"
    
    Resultado:
    HTTP/1.1 200 OK
    Host: localhost:8000
    Date: Thu, 22 Feb 2024 16:39:38 GMT
    Connection: close
    X-Powered-By: PHP/8.1.2-1ubuntu2.14
    Content-Type: text/html; charset=UTF-8
    Cache-Control: no-cache, private
    Date: Thu, 22 Feb 2024 16:39:38 GMT
    X-RateLimit-Limit: 60
    X-RateLimit-Remaining: 59
    Access-Control-Allow-Origin: *
    
    Amosa todas as marcas
    


    Comando:
    curl -i -H "Accept: application/json" -X POST "http://localhost:8000/api/v1/marcas"
    
    Resultado:
    HTTP/1.1 200 OK
    Host: localhost:8000
    Date: Thu, 22 Feb 2024 16:40:34 GMT
    Connection: close
    X-Powered-By: PHP/8.1.2-1ubuntu2.14
    Content-Type: text/html; charset=UTF-8
    Cache-Control: no-cache, private
    Date: Thu, 22 Feb 2024 16:40:34 GMT
    X-RateLimit-Limit: 60
    X-RateLimit-Remaining: 58
    Access-Control-Allow-Origin: *
    
    Crea una nova marca
    

    Devolvendo datos json

    • Agora é o momento de implementar o código dos controladores para que devolvan en formato json os datos a quen faga uso da API REST que estamos a implementar.
    Soamente temos que chamar á función toJson para converter os datos a dito modelo da forma:
    $jsonPerfumes = Perfumes::all()->toJson();
    
    Unha vez convertido temos que enviar a resposta ao cliente.
    Isto o faremos có obxecto response da forma: response()->json(['variable'=>'valor']);
    Desta forma convirte a json.
    Poderíamos converter os datos primeiro a json da forma:
    $marcasjson= Marca::all()->toJson();
    Pero iso xa o fai o response...
    Se ademais queremos devolver un código de resposta HTTP o poñeremos ao final: response()->json(['variable'=>'valor','status'=>'ok'],200);
    Máis información neste enlace.
    Todas as respostas e formatos deste exercicio están seguindo as normas indicadas neste enlace.


    • Arquivo app/Http/Controller/MarcasController.php
    <?php
    
    namespace App\Http\Controllers;
    
    use App\Models\Marca;
    use App\Models\Perfume;
    
    
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Validator;
    
    class MarcaController extends Controller
    {
         /**
         * Display a listing of the resource.
         */
        public function index()
        {
            return response()->json(['status'=>'ok','data'=>Marca::all()],200);
        }
    
        /**
         * Store a newly created resource in storage.
         */
        public function store(Request $request)
        {
            if(!$request->input('descripcion')){
                return response()->json(['errors'=>['status'=>'422','title'=>'Datos incompletos','detail'=>'Falta a descripcion da marca']],400);
            }
            $novaMarca = new Marca;
            $novaMarca->descripcion=$request->input("descripcion");
            $novaMarca->save();
    
            return response()->json(['status'=>'ok','data'=>$novaMarca],201)->header('Location', 'http://www.dominio.es/marcas/'.$novaMarca->id_marca);  // No header iría a URL onde se pode consultar o novo recurso creado. No voso caso sería: 'http://localhost:8000/api/v1/marcas/' .$novaMarca->id_marca
        }
    
        /**
         * Display the specified resource.
         * Ao definir un obxecto da clase Marca, automaticamente vai buscala polo id enviado.
         */
        public function show($id)
        {
            $marca = Marca::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
            if (!$marca){
                return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
            }
    
            return response()->json(['status'=>'ok','data'=>$marca],200);
        }
    
        /**
         * Update the specified resource in storage.
         */
        public function update(Request $request,$id)
        {
            // Validamos os datos que nos chegan:
            $validator = Validator::make($request->all(),[
                'descripcion' => 'required|min:1|max:100'
            ]);
            if ($validator->fails()){
               return response()->json(['errors'=>['status'=>'422','title'=>'Datos incompletos','detail'=>'Falta a descripcion da marca ou ten mais de 100 caracteres']],400);
            }
    
            // Pode vir polo método PUT ou polo método PATCH
            // Non imos facer distinción.
            $modificado=false;
            $marca = Marca::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
            if (!$marca){
                return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
            }
    
            if($request->has('descripcion')){
                $modificado=true;
                $marca->descripcion=$request->input('descripcion');
            }
    
            // Non se modificou nada. Neste caso non pode darse o caso xa que obrigamos a enviar a descripcion no caso de modificación. Pero se tivésemos máis campos, teríamos que quitar o atribute required.
            if(!$modificado) {
                return response()->json(['errors'=>['status'=>'304','title'=>'Marca non modificada']],304);
            }
    
            $marca->save();
            return response()->json(['status'=>'ok'],200);
    
        }
    
        /**
         * Remove the specified resource from storage.
         */
        public function destroy($id)
        {
            $marca = Marca::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
    
            if (!$marca){
                return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
            }
    
            // Comprobamos se temos perfumes asociados á marca
            if($marca->perfumes->count()>0){
                return response()->json(['errors'=>['status'=>'409','title'=>'Existen perfumes asociados']],409);
            }
    
            $marca->delete();
            return response()->json(null,204);  // Tamén se pode poñer: return response()->noContent();
    
        }
    }
    





    • Imos probar todas estas rutas.
    • Nota Importante:
    • Se vos sae o erro: 500 Internal Server Error, comprobade que tedes configurado ben o acceso á base de datos no arquivo .env e cun usuario/password que teña permiso de acceso e modificación sobre a base de datos.
    • Se aínda asi recibides o erro 500, podedes poñer o código que vos faia nunha ruta GET do controlador (por exemplo INDEX) e chamar dende un navegador a dita ruta e 'depurar' amosando coa orde var_dump() ou dd() o contido do que ides facendo.


  • Probando rutas de Marca con Chrome
  • Probando a ruta (método index do controller) que recupera todas as marcas.

  • Probando a ruta (método store do controller) que engade unha nova marca. Neste caso enviamos os datos (descripcion) na URL xa que estamos a empregar QUERY PARAMETERS.

  • Probando a ruta (método store do controller) que engade unha nova marca. Neste caso enviamos os datos (descripcion) na corpo da páxina xa que estamos a empregar FORM PARAMETERS.

  • Probando a ruta (método store do controller) que engade unha nova marca. Neste caso enviamos os datos (descripcion) na corpo da páxina en formato JSON. ESTA VAI SER A FORMA QUE IMOS EMPREGAR DENDE LARAVEL PARA CONSUMIR UN SERVIZO WEB.

  • Probando a ruta (método show do controller) que amosa os datos dunha marca.

  • Probando a ruta (método update do controller) que actualiza unha marca. Aquí temos que enviar dous datos (neste Modelo), o id da marca a modificar e a nova descripción (non deixamos actualizar o id). O id da marca vai na URL (mirade as rutas có comando php artisan route:list) a os datos van en formato JSON como fixemos no paso anterior.

  • Probando a ruta (método destroy do controller) que borra unha marca. Neste caso non deixa xa que o id desta marca ten perfumes asociados.

  • Probando a ruta (método destroy do controller) que borra unha marca. Comprobade que tedes ese número de id na táboa de Marcas.




    • Arquivo app/Http/Controller/PerfumesController.php
    <?php
    
    namespace App\Http\Controllers;
    
    use App\Models\Marca;
    use App\Models\Perfume;
    
    use Illuminate\Support\Facades\Validator;
    use Illuminate\Http\Request;
    
    class PerfumeController extends Controller
    {
        // Formato dd/mm/YYYY => devuelve YYYY-mm-dd
        function cambiaf_a_mysql($fecha){
    
            if (empty($fecha)) return null;
    
            $partes = explode("-",$fecha);
    
            return $partes[2] . '-' . $partes[1] . '-' . $partes[0];
        }
    
        public function index_todos() {
            return response()->json(['status'=>'ok','data'=>Perfume::all()],200);
        }
        /**
         * Amosa os perfumes dunha marca dada
         */
        public function index($id_marca)
        {
            $marca = Marca::find($id_marca);  // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
            if (!$marca){
                return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
            }
    
            $perfumes = $marca->perfumes()->get();
            return response()->json(['status'=>'ok','data'=>$perfumes],200);
        }
    
        /**
         * Store a newly created resource in storage.
         */
        public function store(Request $request,$id_marca)
        {
            // Validamos os datos que nos chegan:
            $validator = Validator::make($request->all(),[
                'descripcion' => 'required|max:45',
                'prezo' => 'required|regex:/[0-9]{1,3},[0-9]{2}/',
                'data_compra' => 'date_format:"d-m-Y"'  // 4 díxitos para o ano
            ]);
            if ($validator->fails()){
               return response()->json(['errors'=>['status'=>'422','title'=>'Datos incompletos','detail'=>'Faltan datos ou con formato incorrecto: descripcion, prezo, data_compra']],400);
            }
    
            $marca = Marca::find($id_marca);  // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
            // Comprobamos se a marca existe
            if (!$marca){
                return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
            }
    
            //SE NON TIVISEMOS QUE FORMATEAR OS DATOS PODERÍAMOS FACELO ASÍ,
            //   $marca->perfumes()->create($request->all());
            //  PERO CAMBIAMOS O FORMATO DA DATA e teríamos que engador ao obxecto $request o id da marca
    
            $novoPerfume=new Perfume;
            $novoPerfume->descripcion=$request->input('descripcion');
            $novoPerfume->prezo=str_replace(',','.',$request->input('prezo'));
            $novoPerfume->data_compra=$this->cambiaf_a_mysql($request->input('data_compra'));
            $novoPerfume->marca_id=$marca->id;
            $novoPerfume->save();
    
            return response()->json(['status'=>'ok','data'=>$novoPerfume],201)->header('Location', 'http://www.dominio.es/perfumes/' . $novoPerfume->id_perfume);  // No header iría a URL onde se pode consultar o novo recurso creado. No voso caso sería: 'http://localhost:8000/api/v1/perfumes/' .$novoPerfume->id_perfume
        }
    
        /**
         * Display the specified resource.
         */
        public function show($id)
        {
            $perfume = Perfume::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa o perfume
            if (!$perfume){
                return response()->json(['errors'=>['status'=>'404','title'=>'Perfume non atopado']],404);
            }
    
            return response()->json(['status'=>'ok','data'=>$perfume],200);
    
        }
    
        /**
         * Update the specified resource in storage.
         */
        public function update(Request $request, $id)
        {
            // Validamos os datos que nos chegan:
            $validator = Validator::make($request->all(),[
                'descripcion' => 'min:1|max:45',
                'prezo' => 'regex:/[0-9]{1,3},[0-9]{2}/',
                'data_compra' => 'date_format:"d/m/Y"',
                'marca_id' => 'integer'
            ]);
            if ($validator->fails()){
               return response()->json(['errors'=>['status'=>'422','title'=>'Datos incompletos','detail'=>'Faltan datos ou con formato incorrecto: descripcion, prezo, data_compra']],400);
            }
            if($request->has('marca_id')){
                // Comprobamos se a marca nova existe
                $marcadestino = Marca::find($request->marca_id);
                if (!$marcadestino){
                    return response()->json(['errors'=>['status'=>'422','title'=>'Datos incompletos','detail'=>'A marca non existe']],400);
                }
            }
    
            $perfume = Perfume::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa o perfume
            if($request->has('descripcion')){
                $perfume->descripcion=$request->descripcion;
            }
            if($request->has('prezo')){
                $perfume->prezo=str_replace(',','.',$request->prezo);
            }
            if($request->has('data_compra')){
                $perfume->data_compra=$this->cambiaf_a_mysql($request->data_compra);
            }
            if($request->has('marca_id')){
                $perfume->marca_id=$request->marca_id;
            }
    
            $perfume->save();
            return response()->json(['status'=>'ok','data'=>$perfume],200);
        }
    
        /**
         * Remove the specified resource from storage.
         */
        public function destroy($id)
        {
            $perfume = Perfume::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa o perfume
            if (!$perfume){
                return response()->json(['errors'=>['status'=>'404','title'=>'Perfume non atopado']],404);
            }
    
            $perfume->delete();
            return response()->json(null,204);  // Tamén se pode poñer: return response()->noContent();
    
        }
    }
    



    • Imos probar todas estas rutas.
    • Nota Importante:
    • Se vos sae o erro: 500 Internal Server Error, comprobade que tedes configurado ben o acceso á base de datos no arquivo .env e cun usuario/password que teña permiso de acceso e modificación sobre a base de datos.
    • Se aínda asi recibides o erro 500, podedes poñer o código que vos faia nunha ruta GET do controlador (por exemplo INDEX) e chamar dende un navegador a dita ruta e 'depurar' amosando coa orde var_dump() ou dd() o contido do que ides facendo.


  • Probando rutas de Marca con Chrome
  • Probando a ruta (método index_todos do controller) que recupera todos perfumes.

  • Probando a ruta (método index do controller) que recupera todos perfumes dunha marca dada. No exemplo da marca có id=1.

  • Probando a ruta (método store do controller) que engade un novo perfume. Neste caso enviamos os datos (descripcion, prezo e data_compra) no corpo da páxina en formato JSON. Observar o formato da data e do prezo. Dito formato está definido no controlador, no método Validate. O prezo leva coma e a data guión.

  • No exemplo anterior podemos ver como o servizo devolve os datos do perfume creado, incluído o seu id.

  • Probando a ruta (método show do controller) que amosa os datos dun perfume. No exemplo busca os datos do perfume có id=1

  • Php laravel servizos 16.JPG

    Probando a ruta (método update do controller) que actualiza un perfume. Aquí temos que enviar dous datos (neste Modelo), o id do perfume a modificar (no exemplo está na URL, valor 1) e os datos que queremos actualizar (no exemplo actualiza a descripcion soamente). Os datos van en formato JSON como fixemos no paso anterior.

  • Probando a ruta (método destroy do controller) que borra un perfume.

  • Seguridade

    IMPORTANTE: Facade unha copia do proxecto anterior xa que imos probar diferentes formas de autenticación. Podedes copiar e pegar o proxecto e dádalle outro nome ao cartafol raíz do mesmo (por exemplo, beleza_basica, beleza_token,...)

    • Vimos anteriormente como podemos ter rutas nun servizo que leven consigo modificar a base de datos. O lóxico é que estas rutas estean protexidas por algún tipo de seguridade baseado en autentificación.




    • Tedes neste enlace unha explicación das diferentes formas de seguridade no envío de contrasinais para acceder a recursos.
    • Neste enlace explica en que consiste a seguridade baseada en OAuth2.




    • NOTA IMPORTANTE:: Todas as comunicacións có servidor debería empregar httpS para que vaian cifradas.



    Seguridade básica

    • Imos ver a forma menos segura de integrar unha seguridade básica nas rutas do servizo que queiramos protexer.
    Indicar que en todos os casos a comunicación empregando httpS é necesaria para que a información vaia cifrada.



    Autenticar

    • Tedes máis información en:


    • Como comentamos anteriormente, un servizo REST non ten estado, polo que teremos que enviar en cada petición os datos necesarios para autenticar ao usuario.
    Normalmente as petición que realizan consulta non necesitan autenticación, pero si as que van a facer operacións de modificación na base de datos.



    Autenticación manual
    • Creade un proxecto baleiro que sexa copia do proxecto feito ata o de agora. Podedes chamarlle beleza_manual, por exemplo e traballade sobre esta copia.


    • Neste caso teremos que ter creada unha táboa 'Usuarios' cos campos que nos interesen que se vaian a utilizar para validar.
    Normalmente teremos un campo password 'encriptado' coa orde bcrypt de Laravel.
    • Os pasos son so seguintes:
    Imos crear unha táboa que teña un campo password que é o que imos a utilizar para deixar ou non realizar unha operación.
    Creamos a táboa en MYSQL:
    CREATE TABLE `users_laravel` (
      `id` int NOT NULL AUTO_INCREMENT,
      `password` varchar(100) NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1;
    


    Modificamos o arquivo app\User.php e quitamos a opción de que o usuario poida resetear o password e indicamos que campos van poder ser 'escritos' (fillable) e que campos non van poder enviarse en formato json (hidden).
    <?php
    
    namespace App\Models;
    
    use Illuminate\Auth\Authenticatable;
    use Illuminate\Database\Eloquent\Model;
    //use Illuminate\Auth\Passwords\CanResetPassword;
    use Illuminate\Foundation\Auth\Access\Authorizable;
    use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
    use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
    //use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
    
    class User extends Model implements AuthenticatableContract,
                                        AuthorizableContract
                                            //,CanResetPasswordContract
    {
        use Authenticatable, Authorizable;
            //, CanResetPassword;
    
        /**
         * The database table used by the model.
         *
         * @var string
         */
        protected $table = 'users_laravel';
        public $timestamps = false;                            
                                        
                                            
        /**
         * The attributes that are mass assignable.
         *
         * @var array
         */
        protected $fillable = ['password'];
    
        /**
         * The attributes excluded from the model's JSON form.
         *
         * @var array
         */
        protected $hidden = ['password'];
    }
    


    • Creamos un usuario. Para iso o podemos facer dende Laravel facendo un 'create' do modelo User (User::create(['password' => bcrypt('password')]);) ou ben podemos ir á consola e poñer o comando: php artisan tinker
    A continuación teclearemos: bcrypt('password')
    Por exemplo, se poñemos 'password' eu obteño: $2y$12$1PYR/k5MbfGRs3DYGD6yRO7da8EGFxDsMaXhGcM4jZY/xjxJzr1Ea
    O resultado o copiaremos en Mysql nun rexistro novo dun usuario novo.
    Premeremos exit para saír da consola.


    • Agora na función dentro do controlador, que queiramos protexer teremos que escribir o seguinte tendo en conta que en todas eles debemos de enviar o password:
        public function destroy(Request $request, $id)
        {
            $user = User::where('password',$request->password)->first();
            if (!$user){
                return response('Non tes permiso de acceso.', 401);
            }
            $marca = Marca::find($id);  // Poderíamos empregar findOrFail, pero así temos control se non atopa a marca
            if (!$marca){
                return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
            }
    
            if (!$marca){
                return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404);
            }
    
            // Comprobamos se temos perfumes asociados á marca
            if($marca->perfumes && $marca->perfumes->count()>0){
                return response()->json(['errors'=>['status'=>'409','title'=>'Existen perfumes asociados']],409);
            }
    
            $marca->delete();
            return response()->json(null,204); 
    
        }
    

    Php laravel servizos seg 5.JPG



    • Como vemos o usuario terá que enviar como parámetro o password para poder facer a operación.
    Poderíamos poñer máis campos dentro do array da forma: Auth::attempt(['user' => $request->user, 'password' => $request->password])


    Esta forma non é segura e deberá empregarse o emprego de tokens.

    Seguridade baseada en OAuth2 empregando tokens de acceso

    • Passport => Elixe Passport se: a túa aplicación necesita cumprir co estándar OAuth2, especialmente se vas interactuar con servizos de terceiros ou proporcionar a túa API a aplicacións de terceiros.
    • Sanctum => Elixe Sanctum se: estás desenvolvendo unha SPA ou unha aplicación móbil que consome unha API no mesmo dominio, e buscas unha solución máis sinxela sen as complexidades de OAuth2.
    Aclaración: Nunha SPA, a maior parte do código necesario (HTML, JavaScript e CSS) cárgase unha soa vez, ou os recursos necesarios cárganse dinamicamente e engádense á páxina segundo sexa necesario, xeralmente en resposta a accións do usuario ou eventos. Isto significa que, despois da carga inicial da aplicación, a SPA pode actualizar o contido que se mostra ao usuario sen necesidade de recargar a páxina completa, o que se logra a través de chamadas asíncronas ao servidor (como peticións AJAX) para obter datos e actualizar a vista presentada ao usuario.



    PASO 1:

    Dende a consola e no cartafol do proxecto laravel, instalamos Passport:
    composer require laravel/passport
    


    PASO 2:

    Debemos modificar o arquivo App/config/app.php e engadir o provider: Laravel\Passport\PassportServiceProvider::class,

    Php laravel servizos seg 2.JPG

    e premer gardar o arquivo.


    PASO 3:

    Dende a consola e no cartafol do proxecto laravel, creamos as táboas necesarias para manexar os tokens:
    php artisan migrate
    


    PASO 3:

    Dende a consola e no cartafol do proxecto laravel, este comando creará as claves de cifrado necesarias para xerar tokens de acceso seguros. Ademais, o comando creará clientes de "acceso persoal" e "concesión por contrasinal" que se utilizarán para xerar tokens de acceso:
    php artisan passport:install
    


    PASO 4:

    Modificamos o arquivo App/Models/User.php
    <?php
    namespace App\Models;
    
    use Illuminate\Database\Eloquent\Factories\HasFactory;
    use Illuminate\Foundation\Auth\User as Authenticatable;
    use Illuminate\Notifications\Notifiable;
    //use Laravel\Sanctum\HasApiTokens;
    use Laravel\Passport\HasApiTokens;
    
    class User extends Authenticatable
    {
        use HasApiTokens, HasFactory, Notifiable;
    
        /**
         * The attributes that are mass assignable.
         *
         * @var array<int, string>
         */
        protected $fillable = [
            'name',
            'email',
            'password',
        ];
    
      .................
    
    GARDAR OS CAMBIOS


    PASO 5:

    Modificamos o arquivo App/config/auth.php
        'guards' => [
            'web' => [
                'driver' => 'session',
                'provider' => 'users',
            ],
            'api' => [
                'driver' => 'passport',
                'provider' => 'users',
            ],
        ],
    
    GARDAR OS CAMBIOS



    PASO 6:

    Agora necesitamos algunha forma de que un usuario poida rexistrarse e obter o API KEY. Para iso imos empregar un controlador e unha ruta de nome /register na que enviaremos un nome, un email e un password e devolveranos o token (que tería que gardar dito usuario) para poder facer operación sobre a base de datos.
    Fixarse que neste caso deixamos rexistar aos usuarios. Nunha empresa, ditos usuarios xa estarían previamente rexistrados a través dun formulario web dentro da organización.
    Executamos dende a consola no cartafol do proxecto o comando: php artisan make:controller ApiAuthController
    Arquivo /app/Http/Controllers/ApiAuthController.php
    <?php
    
    namespace App\Http\Controllers;
    
    use App\Models\User;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Hash;
    use Laravel\Passport\Passport;
    
    class ApiAuthController extends Controller
    {
    
        public function register(Request $request)
        {
            $validatedData = $request->validate([
                'name' => 'required|max:255',
                'email' => 'required|email|unique:users',
                'password' => 'required|min:6',
            ]);
    
            $user = User::create([
                'name' => $validatedData['name'],
                'email' => $validatedData['email'],
                'password' => Hash::make($validatedData['password']),
            ]);
    
            $token = $user->createToken('Personal Access Token')->accessToken;
    
            return response()->json(['token' => $token], 200);
        }
    
    }
    
    Fixarse que o que devolve é en formato JSON o token xerado. É importante que a comunición esta cifrida (https) para que non se obteña dito token.


    PASO 7:

    Agora temos que protexer as rutas.
    Imos protexer todas as rutas que modifiquen a base de datos e deixamos sen protexer as que sean de consultas.
    Creamos unha ruta para poder rexistrarse e obter o token.
    Arquivo /app/routes/api.php
    <?php
    
    use Illuminate\Support\Facades\Route;
    
    use App\Http\Controllers\MarcaController;
    use App\Http\Controllers\PerfumeController;
    use App\Http\Controllers\ApiAuthController;
    
    /*
    |--------------------------------------------------------------------------
    | API Routes
    |--------------------------------------------------------------------------
    |
    | Here is where you can register API routes for your application. These
    | routes are loaded by the RouteServiceProvider and all of them will
    | be assigned to the "api" middleware group. Make something great!
    |
    */
    
    
    Route::post('/register', [ApiAuthController::class, 'register']);
    
    // RUTAS PROTEXIDAS
    Route::group(['prefix'=>'v1', 'middleware' => 'auth:api'], function()
    {
        Route::apiResource('marcas', MarcaController::class)->only(['update','destroy','store']);
        Route::apiResource('marcas.perfumes', PerfumeController::class)->only(['update','destroy','store'])->shallow();
    });
    
    // RUTAS SEN PROTEXER
    Route::group(['prefix'=>'v1'], function()
    {
      Route::apiResource('marcas', MarcaController::class)->except(['update','destroy','store']);
      Route::apiResource('marcas.perfumes', PerfumeController::class)->except(['update','destroy','store'])->shallow();
      Route::get('perfumes',[PerfumeController::class,'index_todos']);
    });
    


    PASO 8:

    Agora xa podemos probar as rutas, pero necesitamos primeiro o token.
    Para obter o token temos que empregar curl dende a consola (lembrar que é desexable que a comunicación sexa httpS).
    Importante: Mirade que cumprides coas regras de validación dos campos que están definidos no controlador. Por exemplo, o password está definido que ten que ter definido como mínimo 6 caracteres.
    curl -X POST http://localhost:8000/api/register \
         -H "Content-Type: application/json" \
         -d '{"name":"angel","email":"angel@prueba.es","password":"angelangel"}'
    
    Ao executar o código anterior vos devolverá o token. GARDADEO xa que vai ser empregado polas rutas protexidas.
    Exemplo de token:

    eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIxIiwianRpIjoiZTNlYTE1MWY4NTU5NTc5ZjIwZmUwZWNlNzAyNjA3NjQ5NThjZmRhYjQ3MzExZDY4ZjhiOThlYmZmODgwNDI1MWExMTVmZjE2ZWEyYzBlNDQiLCJpYXQiOjE3MDg5NDc1NjIuMTY0OTE5LCJuYmYiOjE3MDg5NDc1NjIuMTY0OTIsImV4cCI6MTc0MDU2OTk2Mi4xMzY1ODYsInN1YiI6IjEiLCJzY29wZXMiOltdfQ.F7QoYQW3D9KM8Kk5I2UEXk-5Y5lKuhBb5T59WZ6JP3lv_DGf3IP7boBJZV_0kTbjhPSKBUuJ8-ig4br4ER9qP8U7k4FobY_LUu2oO3V9iTXi4ep7N28-2TVsZnMsYIiLXqeOV7lh1hY3ubxXdplUICEoYIuIx5yqApuwJ3HZYvQ99aUz2LUhJRbW3RdBnsbJOXuyEIt1rOeRCRBLz1gJjVRWjmHiKHhMbcphPmDlZT6LGZL30c_u_HJlShXZB1ZJCrDY3yKHx05vjr0bkk5TFnKZvSXhU_aJb4J5INijV1ZlbXNmuGU9CpajJ9ogckWQztvU8zbS3ZIZKM_rHm5lf2ciiJ1-CTXB4yLzcOEjQQdAK1U5N_vhN90OP73b-Km6SEDEJ1OI-mmtmUaqTSbZBceUq7tkbOU6PuEJsA8ptlNN-aTtRZVuLQHP6zJhIQ_9JVQ1r5pRo39e3LXZfaGNzzCTgMykjAfMv5LtcHb3Mcb1mMSaR7BqsXKaHJ8hAps66K6PsWrILZoNAUM0rsHqxQrUUC_YGmc6k4h2ITCGmejY0199lI6ojEvJ8UW3JNLRLr1rYDrjbx2zZZf_DOOQY4nQXRR8dPCXoexAT_QgpCzYtomzDL4I0CObqJFD_Vg-n3Gv-THnQNQXafPG8NUMEBQHxtIR1XTZ1QRSp7EpKhE


    PASO 9:

    Agora só queda probar as rutas dende unhas das utilidades:
    Exemplo de ruta protexida. Ao non enviar o token da un erro.

    Php laravel servizos seg 3.JPG

    Exemplo de ruta protexida. Ao enviar o token funcion (neste caso avisa que existe un perfume asociado á marca)
    Para enviar o token, este vai na cabeceira (header) e ten que levar:
    • Nome: Authorization
    • Password: Bearer e o token (ten que poñer Bearer e un espazo en branco)

    Php laravel servizos seg 4.JPG






    -- Ángel D. Fernández González -- (2024).