En este ejemplo voy a explicar cómo, cualquier aplicación realizada en PHPRunner puede disponer de este interfaz para que otras aplicaciones puedan consumir datos y objetos de negocio.
El ejemplo está hecho en PHPRunnner 10.2, pero podría estar hecho en versión 9.8 u otra, ya que el API es PHP y no es generado por PHPRunner, aunque sí se utilizan recursos de PHPRunner, como es la conexión a la base de datos.
Requisitos funcionales del aplicativo
- El sistema debe facilitar la lista de vehículos de segunda mano, que están disponibles para la venta.
- Los vehículos tendrán una codificación por Marca y por Modelo.
- La aplicación web tendrá un interface para consultar y mantener todos estos datos.
- El sistema dispondrá de una Api Restfull, con los siguientes métodos:
- Lista de todos los vehículos disponibles.
- Lista de las Marcas y Modelos del catálogo.
- Alta de un nuevo vehículo y si la Marca y/o Modelo son nuevos, lata de los mismos en los catálogos.
Modelo de Datos
Es un modelo muy simple porque el objetivo del ejemplo es facilitar información del server de Restfull API.
Se ha normalizado para que se vea que se puede tener, sin complejidad, el modelo normalizado y disponer de estas API’s.
DEMO: https://fhumanes.com/car
Descripción del server Restfull API
Para hacer este ejercicio me he apoyado en la información de Federico Guzmán, en su artículo de RESTful API: ¿Cómo hacer un API con PHP y Mysql?.
Es muy didáctico y está muy bien explicado, por lo que os pido que lo leáis con detenimiento para conocer el detalle del ejercicio.
Uno de los aspectos más relevantes son las herramientas de testeo del desarrollo, es decir, clientes de Restfull API que vamos a utilizar para comprobar el fucnionamiento.
- RESTClient, es un Add-on de Firefox que funciona muy bien
- Postman, aplicación de escritorio que está en todos los sistemas operativos y que soporta tanto Restfull API como los webservices y otros protocolos de intercambio de datos.
A nivel de ubicación del código del servidor, voy a utilizar el directorio “restapi”, inmediatamente debajo del directorio de la aplicación.
Con lo que el directorio raíz para los métodos de acceso se queda en: http://localhost/car/restapi/v1/ .
Los métodos de acceso quedan:
- Consulta de todos los vehículos de la base de datos .
GET http://localhost/car/restapi/v1/auto - Consulta de los catálogos de las Marcas y Modelos
GET http://localhost/car/restapi/v1/modelo - Crear un nuevo vehículo. Esta opción requiere autenticación
POST http://localhost/car/restapi/v1/auto
En cabecera del mensaje requiere:
Authorization: 3d524a53c110e4c22463b10ed32cef9d
Content-Type: application/x-www-form-urlencoded
En Body (cuerpo del mensaje) requiere pasar los parámetros:
make=Citroen&model=Sara&year=2010&msrp=2563.15
Partiendo del ejemplo de Federico Guzmán he modificado su código para disponer de la persistencia de los datos en una base de datos de Mysql utilizando la misma y la misma conexión que tenemos en la aplicación de PHPRunner.
Así pues los códigos han quedado:
index.php . El programa principal de la gestión de las peticiones.
<?php /** * * @About: API Interface * @File: index.php * @Date: $Date:$ Nov-2015 * @Version: $Rev:$ 1.0 * @Developer: Federico Guzman ([email protected]) and modified Fernando Humanes ([email protected]) /* Los headers permiten acceso desde otro dominio (CORS) a nuestro REST API o desde un cliente remoto via HTTP * Removiendo las lineas header() limitamos el acceso a nuestro RESTfull API a el mismo dominio * Nótese los métodos permitidos en Access-Control-Allow-Methods. Esto nos permite limitar los métodos de consulta a nuestro RESTfull API * Mas información: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS **/ header("Access-Control-Allow-Origin: *"); header('Access-Control-Allow-Credentials: true'); header('Access-Control-Allow-Methods: PUT, GET, POST, DELETE, OPTIONS'); header("Access-Control-Allow-Headers: X-Requested-With"); header('Content-Type: text/html; charset=utf-8'); header('P3P: CP="IDC DSP COR CURa ADMa OUR IND PHY ONL COM STA"'); include_once '../include/Config.php'; require_once("../../include/dbcommon.php"); // DataBase PHPRunner /* Puedes utilizar este file para conectar con base de datos incluido en este demo; * si lo usas debes eliminar el include_once del file Config ya que le mismo está incluido en DBHandler **/ //require_once '../include/DbHandler.php'; require '../libs/Slim/Slim.php'; \Slim\Slim::registerAutoloader(); $app = new \Slim\Slim(); /* Usando GET para consultar los autos */ $app->get('/auto', function() { $response = array(); $autos = array(); $sql="SELECT brand.`Brand`,model.`Model`,car.`Year`,car.`Price` FROM car join brand on (car.`brand_idbrand` = brand.`idbrand`) join model on (car.`model_idmodel` = model.`idmodel`) order by 1,2,3,4"; $rsSql=db_query($sql,$conn); while ($data = db_fetch_array($rsSql)){ $autos[] = array('make'=>$data['Brand'], 'model'=>$data['Model'], 'year'=>$data['Year'], 'MSRP'=>number_format($data['Price'], 2, '.', ',')); } $response["error"] = false; $response["message"] = "Autos cargados: " . count($autos); //podemos usar count() para conocer el total de valores de un array $response["autos"] = $autos; echoResponse(200, $response); }); /* Usando POST para crear un auto */ $app->post('/auto', 'authenticate', function() use ($app) { // check for required params verifyRequiredParams(array('make', 'model', 'year', 'msrp')); $response = array(); //capturamos los parametros recibidos y los almacxenamos como un nuevo array $param['make'] = $app->request->post('make'); $param['model'] = $app->request->post('model'); $param['year'] = $app->request->post('year'); $param['msrp'] = $app->request->post('msrp'); /* Podemos inicializar la conexion a la base de datos si queremos hacer uso de esta para procesar los parametros con DB */ require_once '../include/DBauto.php'; $db = new DbAuto(); /* Podemos crear un metodo que almacene el nuevo auto, por ejemplo: */ $auto = $db->createAuto($param); if ( is_array($param) ) { $response["error"] = false; $response["message"] = "Auto creado satisfactoriamente!"; $response["auto"] = $param; } else { $response["error"] = true; $response["message"] = "Error al crear auto. Por favor intenta nuevamente."; } echoResponse(201, $response); }); /* Usando GET para consultar los modelos */ $app->get('/modelo', function() { $response = array(); $modelos = array(); $sql="SELECT brand.`Brand`, model.`Model` FROM brand join model on (model.`brand_idbrand` = brand.`idbrand`) order by 1,2"; $rsSql=db_query($sql,$conn); while ($data = db_fetch_array($rsSql)){ $modelos[] = array('make'=>$data['Brand'], 'model'=>$data['Model']); } $response["error"] = false; $response["message"] = "Modelos cargados: " . count($modelos); //podemos usar count() para conocer el total de valores de un array $response["modelos"] = $modelos; echoResponse(200, $response); }); /* corremos la aplicación */ $app->run(); /*********************** USEFULL FUNCTIONS **************************************/ /** * Verificando los parametros requeridos en el metodo o endpoint */ function verifyRequiredParams($required_fields) { $error = false; $error_fields = ""; $request_params = array(); $request_params = $_REQUEST; // Handling PUT request params if ($_SERVER['REQUEST_METHOD'] == 'PUT') { $app = \Slim\Slim::getInstance(); parse_str($app->request()->getBody(), $request_params); } foreach ($required_fields as $field) { if (!isset($request_params[$field]) || strlen(trim($request_params[$field])) <= 0) { $error = true; $error_fields .= $field . ', '; } } if ($error) { // Required field(s) are missing or empty // echo error json and stop the app $response = array(); $app = \Slim\Slim::getInstance(); $response["error"] = true; $response["message"] = 'Required field(s) ' . substr($error_fields, 0, -2) . ' is missing or empty'; echoResponse(400, $response); $app->stop(); } } /** * Validando parametro email si necesario; un Extra ;) */ function validateEmail($email) { $app = \Slim\Slim::getInstance(); if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { $response["error"] = true; $response["message"] = 'Email address is not valid'; echoResponse(400, $response); $app->stop(); } } /** * Mostrando la respuesta en formato json al cliente o navegador * @param String $status_code Http response code * @param Int $response Json response */ function echoResponse($status_code, $response) { $app = \Slim\Slim::getInstance(); // Http response code $app->status($status_code); // setting response content type to json $app->contentType('application/json'); echo json_encode($response); } /** * Adding an intermediate layer of authentication for one or all methods, use as needed * Revisa si la consulta contiene un Header "Authorization" para validar */ function authenticate(\Slim\Route $route) { // Getting request headers $headers = apache_request_headers(); $response = array(); $app = \Slim\Slim::getInstance(); // Verifying Authorization Header if (isset($headers['Authorization'])) { //$db = new DbHandler(); //utilizar para manejar autenticacion contra base de datos // get the api key $token = $headers['Authorization']; // validating api key if (!($token == API_KEY)) { //API_KEY declarada en Config.php // api key is not present in users table $response["error"] = true; $response["message"] = "Acceso denegado. Token inválido"; echoResponse(401, $response); $app->stop(); //Detenemos la ejecución del programa al no validar } else { //procede utilizar el recurso o metodo del llamado } } else { // api key is missing in header $response["error"] = true; $response["message"] = "Falta token de autorización"; echoResponse(400, $response); $app->stop(); } } ?>
config.php. La clave API de control de la aplicación.
<?php /** * Database configuration define('DB_USERNAME', ''); define('DB_PASSWORD', 'h'); define('DB_HOST', ''); define('DB_NAME', ''); */ //referencia generado con MD5(uniqueid(<some_string>, true)) define('API_KEY','3d524a53c110e4c22463b10ed32cef9d'); /** * API Response HTTP CODE * Used as reference for API REST Response Header * */ /* 200 OK 201 Created 304 Not Modified 400 Bad Request 401 Unauthorized 403 Forbidden 404 Not Found 422 Unprocessable Entity 500 Internal Server Error */ ?>
DbAuto.php . La clase que nos va a facilitar el alta de los nuevos vehículos.
<?php /** * * @About: ADD of a new car * @File: DbAuto.php * @Date: $Date:$ April 2020 * @Version: $Rev:$ 1.0 * @Developer: fernando humanes **/ class DbAuto { private $connDB; // Field 'make', 'model', 'year', 'msrp' function __construct() { global $conn; // Connection data base of PHPRunner $this->connDB = $conn; } public function createAuto($param) { $id_make = $this->buscaMake($param['make']); $id_model = $this->buscaModel($id_make,$param['model']); $year = $param['year']; $msrp = $param['msrp']; $sql="INSERT INTO car (brand_idbrand, model_idmodel, `Year`, Price) VALUES ($id_make,$id_model,$year,$msrp);"; $rsSql=db_query($sql,$this->connDB); return $param; } private function buscaMake($make) { $sql="SELECT idbrand, Brand FROM brand where upper(Brand) = upper('$make')"; $rsSql=db_query($sql,$this->connDB); $row_cnt = $rsSql->num_rows; $row= db_fetch_array($rsSql); if ($row_cnt == 0) { // If it does not exist, a new catalog is registered $sql="INSERT INTO brand (Brand) VALUES ('$make')"; $rsSql=db_query($sql,$this->connDB); $id = $this->connDB->insert_id; } else { $id = $row['idbrand']; } return $id; } private function buscaModel($id_make,$model) { $sql="SELECT idmodel, brand_idbrand, Model FROM model where brand_idbrand = $id_make and upper(Model) = upper('$model')"; $rsSql=db_query($sql,$this->connDB); $row_cnt = $rsSql->num_rows; $row= db_fetch_array($rsSql); if ($row_cnt == 0) { // If it does not exist, a new catalog is registered $sql="INSERT INTO model (brand_idbrand, Model) VALUES ($id_make,'$model')"; $rsSql=db_query($sql,$this->connDB); $id = $this->connDB->insert_id; } else { $id = $row['idmodel']; } return $id; } } ?>
Os adjunto todos los fuentes del ejemplo y como siempre, para cualquier duda o necesidad que os surja de este ejemplo, contactar conmigo en [email protected]