APP PHPRunner server RESTfull API

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

  1. El sistema debe facilitar la lista de vehículos de segunda mano, que están disponibles para la venta.
  2. Los vehículos tendrán una codificación por Marca y por Modelo.
  3. La aplicación web tendrá un interface para consultar y mantener todos estos datos.
  4. 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:

  1. Consulta de todos los vehículos de la base de datos .
    GET  http://localhost/car/restapi/v1/auto
  2. Consulta de los catálogos de las Marcas y Modelos
    GET  http://localhost/car/restapi/v1/modelo
  3. 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.

index.php
 
<?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.

config.php
 
<?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.

BdAuto.php
 
<?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]

 

 

Adjuntos

Archivo Tamaño de archivo Descargas
zip PHPRunner 10.2 (Proyecto y Server) 102 KB 87
zip Modelo de datos en Mysql WorkBnech 8 KB 81
zip Backup de la Base de Datos 2 KB 81

Blog personal

Las cookies nos permiten ofrecer nuestros servicios. Al utilizar nuestros servicios, aceptas el uso que hacemos de las cookies.

"