Servicio web RESTful con PHP

En esta entrada te muestro la implementación de un servicio web RESTful con PHP, que nos permite realizar operaciones CRUD sobre una tabla de usuarios en una base de datos SQLite:

URL HTTP Verb Cuerpo de la petición Resultado
api/usuarios GET Obtener la lista completa de usuarios
api/usuarios/{id} GET Obtener el usuario con el identificador {id}
api/usuarios POST un nuevo usuario Crea el nuevo usuario y lo devuelve en el cuerpo de la respuesta con el id que se le ha asignado
api/usuarios/{id} PUT un usuario existente modificado Modifica el usuario y lo devuelve en el cuerpo de la respuesta
api/usuarios/{id} DELETE Borra el usuario con el identificador {id}

Fichero .htaccess

En un fichero de script PHP, podemos implementar sin grandes dificultades, la gestión de peticiones con los diferentes verbos GET, POST, PUT y DELETE. Pero, necesitamos un sistema de enrutamiento para las url de tipo api/usuarios/{id}. Una solución, sería escribir una regla Rewrite en un fichero .htaccess:

	RewriteCond %{REQUEST_FILENAME} !-f
	RewriteCond %{REQUEST_FILENAME} !-d
	RewriteRule ^(.*)/([0-9]+)$ $1.php [L]

Con esto tanto las peticiones a la URL api/usuarios/{id}, como a la URL api/usuarios se procesan en api/usuarios.php. El id no lo perdemos, porque lo podemos sacar de la variable $_SERVER[‘REQUEST_URI’].

Clase ClienteSQLite – sqlite.php

Para facilitar el acceso a la base de datos SQLite, he creado la siguiente clase:

<?php
class ClienteSQLite
{
	private $lastInsertId=0;
	
	public function Command($comando,$params=array())
	{
		$myArray =array();
		$conn = new PDO('sqlite:../db/dap.db');
		$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
		$statement = $conn->prepare($comando);
		$statement->execute($params);
		$this->lastInsertId = $conn->lastInsertId();
		$myArray = $statement->fetchAll(PDO::FETCH_OBJ);
		$conn = null;
		return $myArray;
	}
	
	public function LastInsertId()
	{
		return $this->lastInsertId;
	}
}
?>

Servicio web RESTful – usuarios.php

El código del servicio web RESTful en PHP es el que se muestra a continuación:

<?php
require 'sqlite.php';

/*******RESPUESTA EN FORMATO JSON********/
function sendJSONResponse($body, $statusCode) 
{
	header("Content-Type: application/json; charset=UTF-8");
	http_response_code($statusCode);
	echo(json_encode($body));
}

/*******OBTENER EL IDENTIFICADOR DE LA PETICION REST********/
function getLastPartURL() 
{
	$lastPartURL=array_pop(explode('/', $_SERVER['REQUEST_URI']));
	return	 $lastPartURL;
}

/*******OBTENER EL CUERPO JSON DE LA PETICION HTTP********/
function getJSONBody() 
{
	$inputJSON = file_get_contents('php://input');
	$objeto= json_decode( $inputJSON ); 
	return	 $objeto;
}

/*********SCRIPT CONTROLADOR DE USUARIOS*********/
$clienteSQL=new ClienteSQLite();

switch ($_SERVER['REQUEST_METHOD']) {
	case 'POST':
		try
		{		
			$objeto= getJSONBody() ;
			$params=array(':nombre' => $objeto->Nombre,':password' => $objeto->Password);
			$clienteSQL->Command("INSERT INTO Usuarios (Nombre, Password) VALUES (:nombre,:password)",$params);
			$objeto->UsuarioId=$clienteSQL->LastInsertId();
			sendJSONResponse($objeto,"200");
		}
		catch(Exception $e)
		{
			sendJSONResponse(array("Error al crear usuario"), 500) ;
		}
    break;
	case 'PUT':
		try
		{	
			$lastPartURL=getLastPartURL();
			if (is_numeric($lastPartURL))
			{		
				$objeto= getJSONBody() ;
				$params=array(':id' => $lastPartURL, ':nombre' => $objeto->Nombre,':password' => $objeto->Password);
				$clienteSQL->Command("UPDATE Usuarios SET Nombre=:nombre,Password=:password WHERE UsuarioId=:id",$params);
				sendJSONResponse($objeto,"200");
			}
			else
			{
				sendJSONResponse(array("Formato de URL no válido"), 500) ;
			}
		}
		catch(Exception $e)
		{
			sendJSONResponse(array("Error al actualizar usuario"), 500) ;
		}
    break;
	case 'GET':
		try
		{
			$lastPartURL=getLastPartURL();
			if (is_numeric($lastPartURL))
			{
				$params=array(':id' => $lastPartURL);
				$myArray=$clienteSQL->Command("SELECT * FROM Usuarios WHERE UsuarioId=:id",$params);
				if (count($myArray)==1)
					sendJSONResponse($myArray[0],"200");
				else
					http_response_code(204);
			}
			else
			{
				$myArray=$clienteSQL->Command("SELECT * FROM Usuarios");
				if (count($myArray)>0)
					sendJSONResponse($myArray,"200");
				else
					http_response_code(204);
			}
		}
		catch(Exception $e)
		{
			sendJSONResponse(array("Error al consultar usuario/s"), 500) ;
		}
	break;
	case 'DELETE':
		try
		{
			$lastPartURL=getLastPartURL();
			if (is_numeric($lastPartURL))
			{		
				$params=array(':id' => $lastPartURL);
				$clienteSQL->Command("DELETE FROM Usuarios WHERE UsuarioId=:id",$params);
				http_response_code(204);
			}
			else
			{
				sendJSONResponse(array("Formato de URL no válido"), 500) ;
			}
		}
		catch(Exception $e)
		{
			sendJSONResponse(array("Error al borrar usuario"), 500) ;
		}
	break;
  }
?>

Consultar, modificar y validar datos con Entity Framework

En la entrada anterior Formulario WPF de mantenimiento de usuarios se desarrolló una aplicación de mantenimiento de usuarios. En dicha aplicación se trabajaba con datos en memoria, ahora veremos como gestionar la persistencia de los datos en una tabla de SQL Server mediante el uso del ORM Entity Framework.

1. Instalación de Entity Framework

Mediante la opción de Visual Studio Tools>Nuget Package Manager>Manage Nuget Packages for solution instalamos Entity Framework en nuestro proyecto.

2. Creación de la cadena de conexión a la base de datos

Una vez instalado Entity Framework, en el proyecto aparecerá automáticamente un fichero xml llamado App.config donde agregamos la cadena de conexión a nuestra base de datos:


<connectionStrings>
<add name="DAPContext" connectionString="Data Source=localhost\SQLEXPRESS;uid=sa;pwd=123456;database=DAP" providerName="System.Data.SqlClient" />
</connectionStrings>

3. Creación de la clase Usuario

La clase que se mapea con la tabla de Usuarios es la siguiente:

    [Table("Usuarios")]
    public class Usuario :IValidatableObject
    {
        public int UsuarioId { get; set; }
        public string Nombre { get; set; }
        public string Password { get; set; }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            if (Nombre==null || !(Nombre.Length >= 5 && Nombre.Length <= 20))
            {
                yield return new ValidationResult("El nombre debe tener una longitud entre 5 y 20 caracteres", new[] { "Nombre" });
            }
            if (Password==null || !(Password.Length >= 5 && Password.Length <= 20))
            {
                yield return new ValidationResult("El password debe tener una longitud entre 5 y 20 caracteres", new[] { "Password" });
            }
        }
    }

Es necesario poner [Table(“Usuarios”)], puesto que el nombre de la clase Usuario no coincide con el nombre de tabla Usuarios.

La validación de datos la realizamos implementando la interfaz IValidatableObject. Otra forma de hacer validaciones es usando atributos de validación, como: MaxLength, Required, etc.

4. Creación de la clase DAPContext

DbContext es la clase principal que necesitamos para poder interactuar con la base de datos. Debemos crear una clase que herede de DbContext:

    public class DAPContext:DbContext
    {
        public DAPContext(): base("DAPContext")
        {
            Database.SetInitializer<DAPContext>(null); //no initialization
        }
        public DbSet<Usuario> Usuarios { get; set; }
    }

En el constructor ponemos el código Database.SetInitializer(null) para que no se realice ningún cambio en la estructura de las tablas de la base de datos.

5. Consulta y modificación de datos

A continuación te muestro el código para poder consultar y modificar datos usando la clase DAPContext:


    public class ServicioUsuarios
    {
        public List<Usuario> Retrieve()
        {
            List<Usuario> lista;
            using (var db = new DAPContext())
            {
                var usuarios = from user in db.Usuarios
                        select user;
                lista = usuarios.ToList();
            }
            return lista;            
        }

        public Usuario Retrieve(int id)
        {
            Usuario usuario;
            using (var db = new DAPContext())
            {
                usuario = (from us in db.Usuarios
                            where us.UsuarioId == id
                            select us).Single();
            }
            return (usuario);
        }

        public int Create(Usuario obj)
        {
            var nuevoUsuario = new Usuario() { Nombre=obj.Nombre,Password=obj.Password};
            using (var dbCtx = new DAPContext())
            {
                dbCtx.Usuarios.Add(nuevoUsuario);
                dbCtx.SaveChanges();
            }
            return nuevoUsuario.UsuarioId;
        }

        public void Update(Usuario obj)
        {
            Usuario user = new Usuario() { UsuarioId = obj.UsuarioId, Nombre = obj.Nombre, Password = obj.Password };
            using (var dbCtx = new DAPContext())
            {
                dbCtx.Entry(user).State = EntityState.Modified;
                dbCtx.SaveChanges();
            }
        }

        public void Delete(int id)
        {
            var user = new Usuario { UsuarioId = id };
            using (var dbCtx = new DAPContext())
            {
                dbCtx.Usuarios.Attach(user);
                dbCtx.Usuarios.Remove(user);
                dbCtx.SaveChanges();
            }
        }
    }


Formulario AngularJS de mantenimiento de usuarios

En la entrada anterior Formulario WPF de mantenimiento de usuarios vimos cómo desarrollar una aplicación windows para el mantenimiento de una lista de usuarios, ahora he realizado una aplicación web con AngularJS, con la misma funcionalidad que teníamos antes.

Aquí te dejo la aplicación para que la pruebes:

{{displayName}}



Id Nombre Password
{{ x.id }} {{ x.nombre }} {{ x.password }}


Próximamente realizaremos un servicio web, que nos pertitirá leer y almacenar los usuarios en una base de datos. Este servicio web será usado tanto por la aplicación windows como por la aplicación web.


Formulario WPF de mantenimiento de usuarios

He creado una aplicación WPF sencilla para el mantenimiento de una lista de usuarios. Se usa el patrón de diseño Model-View-ViewModel, para mejorar la calidad del código y su mantenibilidad.

FormularioUsuarios

En la siguiente figura se puede ver a groso modo el diseño realizado:

MantenimientoUsuarios

  • Las clases Usuario, UsuarioView, UsuarioVM y UsuarioDA son específicas para el mantenimiento de usuarios.
  • Las clases Notifier, RelayCommand, BaseVm, Maintainable y la interfaz IModelBase se pueden reutilizar para realizar el mantenimiento de otros tipos de entidades, por ejemplo productos, clientes…
  • La interfaz INotifyPropertyChanged y la interfaz ICommand son parte de .NET Framework.

Descargar proyecto de Visual Studio 2010

Clases e interfaces creadas

1. Clase abstracta Notifier

Esta clase implementa la interfaz INotifyPropertyChanged, las clases que creemos a partir de esta clase base, pueden notificar fácilmente los cambios en sus propiedades con una llamada al método RaisePropertyChanged.

2. Clase RelayCommand

Esta clase implementa la interfaz ICommand y nos facilita el trabajo de crear comandos en las clases ViewModel.

3. Clase abstracta genérica BaseVM

A partir de esta clase base podemos crear viewmodels que serán usados en formularios de mantenimiento de datos.

4. Interfaz IModelBase

Interfaz que contiene la declaración de la propiedad Id, que deben tener todas las clases modelo.

5. Clase Usuario

Clase que usamos para la creación de objetos tipo Usuario. Tiene las propiedades Id, Nombre y Password. Se generan eventos tipo PropertyChanged cuando se modifica cualquiera de sus propiedades.

6. Clase abstracta genérica Maintainable

Esta clase base define métodos abstractos genéricos para realizar operaciones CRUD. Es un adaptador entre las clases viewmodel y una fuente de datos.

7. Clase UsuarioDA

Clase que implementa la clase abstracta Maintainable para el acceso a datos de Usuarios que se guardan en memoria. Se pueden crear nuevas clases que hereden de Maintainable para acceder a diferentes fuentes de datos: base de datos, fichero, servicio web…

8. Clase UsuarioVM

Viewmodel para la vista de mantenimiento de usuarios. Es una implementación de la clase abstracta BaseVM.

9. Clase UsuarioView

Código XAML del formulario de mantenimiento de Usuarios.

Cifras y Letras

¿Recuerdas el programa “Cifras y Letras”? En la prueba de cifras se debe obtener un número entero (del 101 al 999) con las operaciones aritméticas de suma, resta, multiplicación y división con seis números (del 1 al 10, 25, 50, 75 y 100). No es obligatorio usar todos los números, pero no se puede repetir ninguno.

Aquí dispones de una aplicación web desarrollada con Python, que encuentra la solución exacta, en caso de que la haya o la más aproximada si no la hubiese. Descargar script Python