Diferencia entre revisiones de «PHP Servizos Web»
Ir a la navegación
Ir a la búsqueda
Línea 1087: | Línea 1087: | ||
− | :* Se queremos utilizar un cliente que se conecte á nosa API: | + | :* Se queremos utilizar un cliente que se conecte á nosa API (isto o veremos no seguinte punto): |
::<syntaxhighlight lang="java" line enclose="div" highlight="3-6" > | ::<syntaxhighlight lang="java" line enclose="div" highlight="3-6" > |
Revisión del 20:25 23 abr 2017
Sumario
Introdución
- Arquitectura REST.
- Arquitectura REST e regras que debería cumprirse.
- Que é un servizo REST (en español) e regras que debe cumprir.
- Neste manual non aplica ben as regras para que sexa Rest (os código de erro de resposta HTTP).
Servizo REST: Características
- Información obtida deste enlace.
- 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
- 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':
- POST http://meusitio.es/libros → Para crear un libro
- GET http://meusitio.es/libros/{id} → Para obter a información dun libro concreto
- PUT http://meusitio.es/libros/{id} → Para modificar os datos dun libro concreto
- DELETE http://meusitio.es/libros/{id} → Para eliminar un libro concreto
- GET http://meusitio.es/libros → Para obter o listado de 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¶m2=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 composta polas seguintes táboas:
Táboa MARCAS:
1 CREATE TABLE `BELLEZA`.`MARCAS` ( 2 `id_marca` INT NOT NULL AUTO_INCREMENT, 3 `descripcion` VARCHAR(100) NOT NULL, 4 PRIMARY KEY (`id_marca`));
Táboa PERFUMES:
1 CREATE TABLE `PERFUMES` ( 2 `id_perfume` int(11) NOT NULL AUTO_INCREMENT, 3 `descripcion` varchar(45) COLLATE utf8_spanish2_ci NOT NULL, 4 `prezo` decimal(8,2) NOT NULL, 5 `data_compra` date DEFAULT NULL, 6 `marca_id` int(11) NOT NULL, 7 PRIMARY KEY (`id_perfume`), 8 KEY `fk_PERFUMES_1_idx` (`id_perfume`), 9 KEY `fk_PERFUMES_MARCA_idx` (`marca_id`), 10 CONSTRAINT `fk_PERFUMES_MARCA` FOREIGN KEY (`marca_id`) REFERENCES `MARCAS` (`id_marca`) ON DELETE NO ACTION ON UPDATE CASCADE 11 ) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COLLATE=utf8_spanish2_ci;
- Algúns datos de exemplo:
1 INSERT INTO `MARCAS` VALUES (1,'CHANNEL'),(2,'ADIDAS'),(3,'ARAMIS');
1 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);
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:
- Arquivo /app/Marca.php
1 <?php 2 3 namespace App; 4 5 use Illuminate\Database\Eloquent\Model; 6 7 class Marca extends Model 8 { 9 protected $table ='MARCAS'; 10 protected $primaryKey='id_marca'; 11 12 public $timestamps = false; 13 14 protected $fillable = [ 15 'descipcion' 16 ]; 17 // Se queremos que certos campos non sexan enviados en formato json os poñemos na propiedade $hidden 18 // protected $hidden = ['password']; 19 20 21 public function perfumes(){ 22 return $this->hasMany('App\Perfume','marca_id','id_marca'); 23 } 24 }
- Arquivo /app/Perfume.php
1 <?php 2 3 namespace App; 4 5 use Illuminate\Database\Eloquent\Model; 6 7 class Perfume extends Model 8 { 9 protected $table ='PERFUMES'; 10 protected $primaryKey='id_perfume'; 11 12 public $timestamps = false; 13 14 protected $dates = ['data_compra']; 15 //protected $dateFormat = 'd-m-Y'; 16 17 protected $fillable = [ 18 'descipcion', 19 'prezo', 20 'data_compra', 21 'marca_id' 22 ]; 23 // Se queremos que certos campos non sexan enviados en formato json os poñemos na propiedade $hidden 24 // protected $hidden = ['password']; 25 26 public function marca(){ 27 return $this->belongsTo('App\Marca','marca_id','id_marca'); 28 } 29 }
- 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:
- POST http://meusitio.es/marcas → Para crear unha marca
- GET http://meusitio.es/marcas/{marcas} → Para obter a información dunha marca concreta
- PUT http://meusitio.es/marcas/{marcas} → Para modificar os datos dunha marca concreta
- DELETE http://meusitio.es/marcas/{marcas} → Para eliminar unha marca concreta
- GET http://meusitio.es/marcas → Para obter o listado de marcas.
- POST http://meusitio.es/marcas/{marcas}/perfumes → Para crear un perfume
- PUT http://meusitio.es/marca/{marcas}/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.
- DELETE http://meusitio.es/marcas/{marcas}/perfumes/{id} → Para eliminar un perfume concreto
- GET http://meusitio.es/perfumes → Para obter o listado de perfumes.
- GET http://meusitio.es/perfumes/{id} → Para obter a información dun perfume concreto
- Imos ter polo tanto tres controladores asociados a estas rutas:
- MarcasController
- MarcasPerfumesController
- PerfumesController
- Os comandos para crear ditos controladores serán:
- Versión 5.1 ou anteriores:
1 php artisan make:controller MarcaController 2 php artisan make:controller PerfumeController 3 php artisan make:controller MarcaPerfumeController
- Versión 5.2 ou posteriores:
1 php artisan make:controller MarcaController --resource 2 php artisan make:controller PerfumeController --resource 3 php artisan make:controller MarcaPerfumeController --resource
- Editamos o arquivo /app/Http/routes.php
1 Route::resource('marcas','MarcasController', 2 ['only' => ['store', 'show', 'update', 'destroy','index']]); 3 4 Route::resource('marcas.perfumes','MarcasPerfumesController', 5 ['only' => ['store', 'update', 'index', 'destroy','']]); 6 7 Route::resource('perfumes','PerfumesController', 8 ['only' => ['index', 'show']]);
- Nota: Información sobre as rutas neste enlace.
- 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}.
- Unha vez creadas as rutas podemos comprobalas có comando: php artisan route:list
Probando as rutas
- 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 curl
- Máis información neste enlace.
- Complemento de Firefox restclient ou httprequest.
- Importante: 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
1 ........... 2 protected $middleware = [ 3 \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class, 4 \App\Http\Middleware\EncryptCookies::class, 5 \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 6 \Illuminate\Session\Middleware\StartSession::class, 7 \Illuminate\View\Middleware\ShareErrorsFromSession::class, 8 // \App\Http\Middleware\VerifyCsrfToken::class, 9 ];
- Arquivo: /app/Http/Controller/MarcaController.php
1 <?php 2 3 namespace App\Http\Controllers; 4 5 use Illuminate\Http\Request; 6 7 use App\Http\Requests; 8 use App\Http\Controllers\Controller; 9 10 class MarcasController extends Controller 11 { 12 /** 13 * Display a listing of the resource. 14 * 15 * @return \Illuminate\Http\Response 16 */ 17 public function index() 18 { 19 return 'Amosamos as marcas'; 20 } 21 22 23 /** 24 * Store a newly created resource in storage. 25 * 26 * @param \Illuminate\Http\Request $request 27 * @return \Illuminate\Http\Response 28 */ 29 public function store(Request $request) 30 { 31 return "Creando unha nova marca"; 32 } 33 34 /** 35 * Display the specified resource. 36 * 37 * @param int $id 38 * @return \Illuminate\Http\Response 39 */ 40 public function show($id) 41 { 42 return "Amosando datos da marca $id"; 43 } 44 45 /** 46 * Update the specified resource in storage. 47 * 48 * @param \Illuminate\Http\Request $request 49 * @param int $id 50 * @return \Illuminate\Http\Response 51 */ 52 public function update(Request $request, $id) 53 { 54 return "Actualizando os datos da marca $id"; 55 } 56 57 /** 58 * Remove the specified resource from storage. 59 * 60 * @param int $id 61 * @return \Illuminate\Http\Response 62 */ 63 public function destroy($id) 64 { 65 return "Eliminando marca $id"; 66 } 67 }
Arquivo: /app/Http/Controller/MarcaPerfumeController.php
1 <?php 2 3 namespace App\Http\Controllers; 4 5 use Illuminate\Http\Request; 6 7 use App\Http\Requests; 8 use App\Http\Controllers\Controller; 9 10 class MarcasPerfumesController extends Controller 11 { 12 /** 13 * Display a listing of the resource. 14 * 15 * @return \Illuminate\Http\Response 16 */ 17 public function index($idMarca) 18 { 19 return "Amosando os perfumes da marca $idMarca"; 20 } 21 22 23 /** 24 * Store a newly created resource in storage. 25 * 26 * @param \Illuminate\Http\Request $request 27 * @return \Illuminate\Http\Response 28 */ 29 public function store(Request $request,$idMarca) 30 { 31 return "Se crea un novo perfume da marca $idMarca"; 32 } 33 34 35 36 /** 37 * Update the specified resource in storage. 38 * 39 * @param \Illuminate\Http\Request $request 40 * @param int $id 41 * @return \Illuminate\Http\Response 42 */ 43 public function update(Request $request, $idMarca,$idPerfume) 44 { 45 return "Se actualizan os datos do perfume $idPerfume da marca $idMarca"; 46 } 47 48 /** 49 * Remove the specified resource from storage. 50 * 51 * @param int $id 52 * @return \Illuminate\Http\Response 53 */ 54 public function destroy($idMarca,$idPerfume) 55 { 56 return "Se elimina perfume $idPerfume da marca $idMarca"; 57 } 58 }
Arquivo: /app/Http/Controller/PerfumeController.php
1 <?php 2 3 namespace App\Http\Controllers; 4 5 use Illuminate\Http\Request; 6 7 use App\Http\Requests; 8 use App\Http\Controllers\Controller; 9 10 class PerfumesController extends Controller 11 { 12 /** 13 * Display a listing of the resource. 14 * 15 * @return \Illuminate\Http\Response 16 */ 17 public function index() 18 { 19 return "Amósanse todos os perfumes"; 20 } 21 22 23 /** 24 * Display the specified resource. 25 * 26 * @param int $id 27 * @return \Illuminate\Http\Response 28 */ 29 public function show($idPerfume) 30 { 31 return "Amosa os datos do perfume $idPerfume"; 32 } 33 34 }
- Exemplos para probar as rutas:
- Comando:
1 curl -i "http://localhost:8000/marcas"
- Resultado:
1 HTTP/1.1 200 OK 2 Host: localhost:8000 3 Connection: close 4 X-Powered-By: PHP/5.5.9-1ubuntu4.19 5 Cache-Control: no-cache 6 Date: Mon, 17 Apr 2017 09:25:34 GMT 7 Content-Type: text/html; charset=UTF-8 8 Set-Cookie: laravel_session=eyJpdiI6IjIrbzRrS08yaU51bjVwdk1WY2VrcGc9PSIsInZhbHVlIjoiQmhUVU9SdVhKVzBVckNqUitJV0JlR2tSNkkydWFmY2hGSWdmWUV0Q053MlV1QTVyb21FcG1tMDFveGREaDZmTk55Nnk1VDNkclBCQng5OExXM1ZnQmc9PSIsIm1hYyI6Ijc2YWQ5YTY0MmZjOWRkZjRmNTU4ZTllNWNjZTA0NjRkZGUwMmEzNTIyNjhiMmJkMTBiZWMzYzczYTQ0Zjk2ZGEifQ%3D%3D; expires=Mon, 17-Apr-2017 11:25:34 GMT; Max-Age=7200; path=/; httponly 9 10 Amosamos as marcas
- Comando:
1 curl -i -H "Accept: application/json" -X POST http://localhost:8000/marcas
- Resultado:
1 HTTP/1.1 200 OK 2 Host: localhost:8000 3 Connection: close 4 X-Powered-By: PHP/5.5.9-1ubuntu4.19 5 Cache-Control: no-cache 6 Date: Mon, 17 Apr 2017 09:26:43 GMT 7 Content-Type: text/html; charset=UTF-8 8 Set-Cookie: laravel_session=eyJpdiI6InYrZXU0Y1BsbGM0NGdWR212aUNlQXc9PSIsInZhbHVlIjoiNG1QM0lEaG02QmFkS2p1XC9UZzZ6R2dCT2FxUFwvbURSNWNJN21ibUpvZzFvWVE1b3JBaFB4a0F3ZjFIc0NVZzNPVWxOSmhsT2wwdnFIdTJhck5PeHBCdz09IiwibWFjIjoiODg0YTNjOTQ2NjAyOTNhNTI0OGI5MjZkYjIwZWU0YmJiMTM1ODFmMDkzNTU3MmZjMzg5OGY3NWE0NzNjZDlkZiJ9; expires=Mon, 17-Apr-2017 11:26:43 GMT; Max-Age=7200; path=/; httponly 9 10 Creando unha 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:
1 $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
1 <?php 2 3 namespace App\Http\Controllers; 4 5 use Illuminate\Http\Request; 6 7 use App\Http\Requests; 8 use App\Http\Controllers\Controller; 9 10 use App\Marca; 11 use Validator; 12 13 class MarcasController extends Controller 14 { 15 /** 16 * Display a listing of the resource. 17 * 18 * @return \Illuminate\Http\Response 19 */ 20 public function index() 21 { 22 23 return response()->json(['status'=>'ok','data'=>Marca::all()],200); 24 } 25 26 27 /** 28 * Store a newly created resource in storage. 29 * 30 * @param \Illuminate\Http\Request $request 31 * @return \Illuminate\Http\Response 32 */ 33 public function store(Request $request) 34 { 35 if(!$request->input('descripcion')){ 36 return response()->json(['errors'=>['status'=>'422','title'=>'Datos incompletos','detail'=>'Falta a descripcion da marca']],400); 37 } 38 $novaMarca = new Marca; 39 $novaMarca->descripcion=$request->input("descripcion"); 40 $novaMarca->save(); 41 42 return response()->json(['status'=>'ok','data'=>$novaMarca],201)->header('Location', 'http://www.dominio.es/marcas/'.$novaMarca->id_marca)->header('Content-Type', 'application/json'); 43 } 44 45 /** 46 * Display the specified resource. 47 * 48 * @param int $id 49 * @return \Illuminate\Http\Response 50 */ 51 public function show($idMarca) 52 { 53 $marca = Marca::find($idMarca); 54 if (!$marca){ 55 return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404); 56 } 57 58 return response()->json(['status'=>'ok','data'=>$marca],200); 59 } 60 61 /** 62 * Update the specified resource in storage. 63 * 64 * @param \Illuminate\Http\Request $request 65 * @param int $id 66 * @return \Illuminate\Http\Response 67 */ 68 public function update(Request $request, $idMarca) 69 { 70 // Validamos os datos que nos chegan: 71 $validator = Validator::make($request->all(),[ 72 'descripcion' => 'required|min:1|max:100' 73 ]); 74 if ($validator->fails()){ 75 return response()->json(['errors'=>['status'=>'422','title'=>'Datos incompletos','detail'=>'Falta a descripcion da marca ou ten mais de 100 caracteres']],400); 76 } 77 78 // Pode vir polo método PUT ou polo método PATCH 79 // Non imos facer distinción. 80 $modificado=false; 81 $marcaModificar = Marca::find($idMarca); 82 if (!$marcaModificar){ 83 return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404); 84 } 85 if($request->has('descripcion')){ 86 $modificado=true; 87 $marcaModificar->descripcion=$request->input('descripcion'); 88 } 89 90 // 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. 91 if(!$modificado) { 92 return response()->json(['errors'=>['status'=>'304','title'=>'Marca non modificada']],304); 93 } 94 95 $marcaModificar->save(); 96 return response()->json(['status'=>'ok'],200); 97 98 } 99 100 /** 101 * Remove the specified resource from storage. 102 * 103 * @param int $id 104 * @return \Illuminate\Http\Response 105 */ 106 public function destroy($idMarca) 107 { 108 $marcaBorrar = Marca::find($idMarca); 109 if (!$marcaBorrar){ 110 return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404); 111 } 112 113 // Comprobamos se temos perfumes asociados á marca 114 if($marcaBorrar->perfumes && $marcaBorrar->perfumes->count()>0){ 115 return response()->json(['errors'=>['status'=>'409','title'=>'Existen perfumes asociados']],409); 116 } 117 118 $marcaBorrar->delete(); 119 return response()->json('',204); 120 121 122 } 123 }
- Arquivo app/Http/Controller/PerfumesController.php
1 <?php 2 3 namespace App\Http\Controllers; 4 5 use Illuminate\Http\Request; 6 7 use App\Http\Requests; 8 use App\Http\Controllers\Controller; 9 use App\Perfume; 10 11 class PerfumesController extends Controller 12 { 13 /** 14 * Display a listing of the resource. 15 * 16 * @return \Illuminate\Http\Response 17 */ 18 public function index() 19 { 20 return response()->json(['status'=>'ok','data'=>Perfumes::all()],200); } 21 22 23 /** 24 * Display the specified resource. 25 * 26 * @param int $id 27 * @return \Illuminate\Http\Response 28 */ 29 public function show($idPerfume) 30 { 31 $perfume=Perfume::find($idPerfume); 32 if (!$perfume){ 33 return response()->json(['errors'=>['status'=>'404','title'=>'Perfume non atopado']],404); 34 } 35 36 return response()->json(['status'=>'ok','data'=>$perfume],200); 37 } 38 39 }
- Arquivo app/Http/Controller/MarcasPerfumesController.php
1 <?php 2 3 namespace App\Http\Controllers; 4 5 use Illuminate\Http\Request; 6 7 use App\Http\Requests; 8 use App\Http\Controllers\Controller; 9 10 use App\Marca; 11 use App\Perfume; 12 use Validator; 13 14 class MarcasPerfumesController extends Controller 15 { 16 // Formato dd/mm/YYYY => devuelve YYYY-mm-dd 17 function cambiaf_a_mysql($fecha){ 18 19 if (empty($fecha)) return null; 20 21 $numeroDia = substr($fecha,0,2); 22 $mes = substr($fecha,3,2); 23 $anio = substr($fecha,6,4); 24 return $anio . '-' . $mes . '-' . $numeroDia; 25 } 26 27 28 /** 29 * Display a listing of the resource. 30 * 31 * @return \Illuminate\Http\Response 32 */ 33 public function index($idMarca) 34 { 35 $marca = Marca::find($idMarca); 36 if (!$marca){ 37 return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404); 38 } 39 40 $perfumes = $marca->perfumes()->get(); 41 return response()->json(['status'=>'ok','data'=>$perfumes],200); 42 } 43 44 45 /** 46 * Store a newly created resource in storage. 47 * 48 * @param \Illuminate\Http\Request $request 49 * @return \Illuminate\Http\Response 50 */ 51 public function store(Request $request,$idMarca) 52 { 53 // Validamos os datos que nos chegan: 54 $validator = Validator::make($request->all(),[ 55 'descripcion' => 'required|max:45', 56 'prezo' => 'required|regex:/[0-9]{1,3},[0-9]{2}/', 57 'data_compra' => 'date_format:"d/m/Y"' 58 ]); 59 if ($validator->fails()){ 60 return response()->json(['errors'=>['status'=>'422','title'=>'Datos incompletos','detail'=>'Faltan datos ou con formato incorrecto: descripcion, prezo, data_compra']],400); 61 } 62 63 // Comprobamos se a marca existe 64 $marca = Marca::find($idMarca); 65 if (!$marca){ 66 return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404); 67 } 68 69 //SE NON TIVISEMOS QUE FORMATEAR OS DATOS PODERÍAMOS FACELO ASÍ: 70 // $marca->perfumes()->create($request->all()); 71 // pero desta forma teríamos que enviar o id da marca tamén como dato e comprobar que sexa o mesmo que $idMarca 72 $novoPerfume=new Perfume; 73 $novoPerfume->descripcion=$request->input('descripcion'); 74 $novoPerfume->prezo=str_replace(',','.',$request->input('prezo')); 75 $novoPerfume->data_compra=$this->cambiaf_a_mysql($request->input('data_compra')); 76 $novoPerfume->marca_id=$idMarca; 77 $novoPerfume->save(); 78 79 return response()->json(['status'=>'ok','data'=>$novoPerfume],201)->header('Location', 'http://www.dominio.es/perfumes/' . $novoPerfume->id_perfume)->header('Content-Type', 'application/json'); 80 81 } 82 83 84 85 /** 86 * Update the specified resource in storage. 87 * 88 * @param \Illuminate\Http\Request $request 89 * @param int $id 90 * @return \Illuminate\Http\Response 91 */ 92 public function update(Request $request, $idMarca,$idPerfume) 93 { 94 // Validamos os datos que nos chegan: 95 $validator = Validator::make($request->all(),[ 96 'descripcion' => 'min:1|max:45', 97 'prezo' => 'regex:/[0-9]{1,3},[0-9]{2}/', 98 'data_compra' => 'date_format:"d/m/Y"', 99 'marca_id' => 'integer' 100 ]); 101 if ($validator->fails()){ 102 return response()->json(['errors'=>['status'=>'422','title'=>'Datos incompletos','detail'=>'Faltan datos ou con formato incorrecto: descripcion, prezo, data_compra']],400); 103 } 104 if($request->has('marca_id')){ 105 // Comprobamos se a marca nova existe 106 $marcadestino = Marca::find($request->input('marca_id')); 107 if (!$marcadestino){ 108 return response()->json(['errors'=>['status'=>'422','title'=>'Datos incompletos','detail'=>'A marca non existe']],400); 109 } 110 } 111 112 113 // Comprobamos se a marca existe 114 $marca = Marca::find($idMarca); 115 if (!$marca){ 116 return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404); 117 } 118 // Comprobamos se o perfume existe 119 $perfume=$marca->perfumes()->find($idPerfume); 120 if (!$perfume){ 121 return response()->json(['errors'=>['status'=>'404','title'=>'Perfume non atopado']],404); 122 } 123 124 $modificado=false; 125 if($request->has('descripcion')){ 126 $perfume->descripcion=$request->input('descripcion'); 127 $modificado=true; 128 } 129 if($request->has('prezo')){ 130 $perfume->prezp=str_replace(',','.',$request->input('prezo')); 131 $modificado=true; 132 } 133 if($request->has('data_compra')){ 134 $perfume->data_compra=$this->cambiaf_a_mysql($request->input('data_compra')); 135 $modificado=true; 136 } 137 if($request->has('marca_id')){ 138 $perfume->marca_id=$request->input('marca_id'); 139 $modificado=true; 140 } 141 142 if(!$modificado) { 143 return response()->json(['errors'=>['status'=>'304','title'=>'Marca non modificada']],304); 144 } 145 146 $perfume->save(); 147 return response()->json(['status'=>'ok','data'=>$perfume],200); 148 } 149 150 /** 151 * Remove the specified resource from storage. 152 * 153 * @param int $id 154 * @return \Illuminate\Http\Response 155 */ 156 public function destroy($idMarca,$idPerfume) 157 { 158 $marca = Marca::find($idMarca); 159 if (!$marca){ 160 return response()->json(['errors'=>['status'=>'404','title'=>'Marca non atopada']],404); 161 } 162 163 // Comprobamos se o perfume existe 164 $perfumeBorrar=$marca->perfumes()->find($idPerfume); 165 if (!$perfumeBorrar){ 166 return response()->json(['errors'=>['status'=>'404','title'=>'Perfume non atopado']],404); 167 } 168 169 170 //$perfumeBorrar->delete(); 171 return response()->json('',204); 172 } 173 }
Autenticar
- Tedes máis información en:
- ajgallego.gitbooks.io => Punto Autenticación HTTP básica sin estado.
- Documentación oficial => Punto Autenticación HTTP básica sin estado.
- 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
- 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:
1 CREATE TABLE `USERS_LARAVEL` ( 2 `id` int(11) NOT NULL AUTO_INCREMENT, 3 `password` varchar(100) NOT NULL, 4 PRIMARY KEY (`id`) 5 ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;
- 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).
1 <?php 2 3 namespace App; 4 5 use Illuminate\Auth\Authenticatable; 6 use Illuminate\Database\Eloquent\Model; 7 //use Illuminate\Auth\Passwords\CanResetPassword; 8 use Illuminate\Foundation\Auth\Access\Authorizable; 9 use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; 10 use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; 11 //use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; 12 13 class User extends Model implements AuthenticatableContract, 14 AuthorizableContract 15 //,CanResetPasswordContract 16 { 17 use Authenticatable, Authorizable; 18 //, CanResetPassword; 19 20 /** 21 * The database table used by the model. 22 * 23 * @var string 24 */ 25 protected $table = 'USERS_LARAVEL'; 26 public $timestamps = false; 27 28 29 /** 30 * The attributes that are mass assignable. 31 * 32 * @var array 33 */ 34 protected $fillable = ['password']; 35 36 /** 37 * The attributes excluded from the model's JSON form. 38 * 39 * @var array 40 */ 41 protected $hidden = ['password']; 42 }
- 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')
- O resultado o copiaremos en Mysql nun rexistro novo dun usuario novo.
- Premeremos exit para saír.
- Agora na función dentro do controlador, que queiramos protexer teremos que escribir:
1 public function store(Request $request) 2 { 3 if (Auth::attempt(['password' => $request->password])){ 4 // Retornamos a información 5 } 6 else{ 7 return response('Unauthorized.', 401); 8 } 9 }
- Como vemos o usuario terá que enviar como parámetro o password para poder facer operación.
- 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
- Os pasos son so seguintes:
- 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 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).
- Creamos un middleware có comando: php artisan make:middleware AutenticacionBasica
- Escribimos o seguinte código:
1 <?php 2 namespace App\Http\Middleware; 3 4 use Illuminate\Support\Facades\Auth; 5 use Closure; 6 7 class AutenticacionBasica 8 { 9 /** 10 * Handle an incoming request. 11 * 12 * @param \Illuminate\Http\Request $request 13 * @param \Closure $next 14 * @return mixed 15 */ 16 public function handle($request, Closure $next) 17 { 18 return Auth::onceBasic() ?: $next($request); 19 } 20 }
- 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 neste enlace.
- Modificamos o arquivo /app/Http/Kernel.php e rexistramos o middleware:
1 .............. 2 protected $routeMiddleware = [ 3 'auth' => \App\Http\Middleware\Authenticate::class, 4 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 5 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 6 'auth.basic.once' => \App\Http\Middleware\AutenticacionBasica::class, 7 ];
- 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('password') ?: $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('emial','password') ?: $next($request);
- Esto é igual ao comportamento por defecto...
- Debemos rexistrar o middleware no controlador. Existen varias formas de facelo, unha delas é no constructor:
1 .................. 2 public function __construct(){ 3 $this->middleware('auth.basic.once'); 4 }
- Se queremos que o middleware afecta soamente a algún/s método/s: $this->middleware('auth.basic.once',['only' => ['store']]);
- 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).
- Para enviar a información do login/password:
- Para facelo, esta información ten que ir na cabeceira.
- 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.
- Esta información irá na cabeceira codificada en base64 polo que pode ser facilmente descifrada se os paquetes son capturados.
- Se queremos utilizar un cliente que se conecte á nosa API (isto o veremos no seguinte punto):
1 $client = new \GuzzleHttp\Client(); 2 $res = $client->get('http://localhost:8000/marcas',[ 3 'auth' => 4 ['username', 5 'password' 6 ] 7 ]); 8 echo $res->getStatusCode(); // 200 9 $datos = json_decode($res->getBody(),true);
Usar un servizo externo
- * Comando para instalación: composer require guzzlehttp/guzzle
Exemplo de código
- Neste código imos amosar como facer unha chamada a unha API Externa.
1 ........ 2 use \GuzzleHttp\Client; 3 4 class ApiExternaController extends Controller 5 { 6 7 public function index() 8 { 9 $client = new \GuzzleHttp\Client(); 10 $res = $client->get('https://XXXX.XXXXX.XX/api/XXXXXXXX/XXXXXX/json?key=XXXXXXXXXXXXXX&query=parametro&language=es'); 11 echo $res->getStatusCode(); // 200 12 $datos = json_decode($res->getBody(),true); 13 var_dump($datos); 14 } 15 ..................
- Liña 3: Temos que facer uso da clase Client de GuzzleHttp.
- 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.
- 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).
- Liña 12: Utilizamos a función json_decode có parámetro a true para que devolva os datos como un array asociativo.
- O var_dump dará como resultado:
1 array (size=4) 2 'html_attributions' => 3 array (size=0) 4 empty 5 'next_page_token' => string 'CvQB5QAAAB-Q718qEtEfzZnccxK0THwIjfCrwGbkVsvEoQaiGyMWBj6Z3-DW1_9UWJaKANfpAahtoMP7AEjcb1JzLzRvJa9gBIueMXhxGzSPhHwKXxiI6Gp2NVQUHRcr0wW2VgAmVHKCGYid01wI8C_r0SaD3iHIB-0Fr5h2IRy4DsmjN6L9oJMaOQTBtAgCN7sCH6imRmMd2lq5XZW9pWHA3JeOAJt4HhKb34X8fjNNKgiIvOi0XPPcRPoIYJ-NcphGShTgA_Fhnl02vyqnn8mbzGMA9xU4yBObOx-cfd5qMKHYLdb5p0L6ctNa_SIV5YM_YUSFwRIQthy2TYyPTmUNwbBfFDMP5xoUPNoKN8AtwpDHFVgOb44DjIBGffU' (length=383) 6 'results' => 7 array (size=20) 8 0 => 9 array (size=10) 10 'formatted_address' => string 'Rúa MarÃa, 2, 15402 Ferrol, A Coruña, España' (length=48) 11 'geometry' => 12 array (size=2) 13 ... 14 'icon' => string 'https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png' (length=67) 15 'id' => string '7c7088e7c654ba30efa1e384e0881bebee8cf065' (length=40) 16 'name' => string 'Asador Gavia' (length=12) 17 'photos' => 18 array (size=1) 19 ... 20 'place_id' => string 'ChIJ22OacwV2Lg0RLObpSmq3D6k' (length=27) 21 'rating' => float 4.2 22 'reference' => string 'CmRSAAAAG-LM_2yc9TTbC8l4HoczU2OPnFPs6eYHzoLwJBwGeSiFT9XvalSDcOb0bLZXHDxp3tSAswl57eXpcZGvAIDiYfckbnl7eIxhLl722qHdn4qv-py6_umtqt2pvzoWGC3EEhA8MgKa1gD9lEzOD_EGSqlgGhT8YjPOdYP3d9C9oB9IdHEr92yvEQ' (length=190) 23 'types' => 24 array (size=4) 25 ... 26 1 => 27 array (size=10) 28 'formatted_address' => string 'Lugar Bosque, 73 - Bajo, 15405 Ferrol, A Coruña, España' (length=57) 29 'geometry' => 30 array (size=2) 31 ... 32 'icon' => string 'https://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png' (length=67) 33 'id' => string 'd0d3a7fb96efe558e00268ba18bf7c9f7e003d67' (length=40) 34 'name' => string 'Medulio' (length=7) 35 'photos' => 36 array (size=1) 37 ... 38 'place_id' => string 'ChIJI7bO3KPYLQ0R5CVGKcTigTg' (length=27) 39 'rating' => float 4.2 40 'reference' => string 'CmRRAAAA_pB5EDe4puf3vqpGI2ACBvg5C5AchtIyS306-m7j_TBzbYsN9KNz8zU-W2u7qhPh1WptaIC5La2JXJxOeGfIlqekxmtKF27PQwz8q503Nbizc7sNH7OKcIhbKm_rssA1EhC9ikBsyUqi3ruykyou0NHGGhTpsMBsUvdbBYkcutfmME-AuOwzWA' (length=190) 41 'types' => 42 array (size
- Liña 6: A nos interésanos a clave 'results', polo tanto: $datos['results'] (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).
- Esta información sería gardada nun array que será enviado á vista para que amose o resultado.
-- Ángel D. Fernández González -- (2017).