En la entrada de hoy, vamos a programar un CRUD básico con PHP.
Será independiente del sistema de persistencia que utilicemos, para aligerar el contenido de esta entrada, guardaremos la información en un archivo .txt, pero es perfectamente aplicable a cualquier sistema de almacenamiento de información como MySQLi, que es frecuentemente usado junto a PHP
Existen métodos más óptimos según el sistema de persistencia que tengamos y el número de datos a trabajar. Pero como guía para que podáis construir vuestro propio CRUD os puede valer.
¿Qué es CRUD?
Si os estáis preguntando que es CRUD, sus siglas corresponden a las 4 operaciones básicas que se hacen al trabajar con sistema de persistencia Create, Read, Update y Delete, o sea, crear, leer, actualizar y borrar.
Sin más demora vamos primero a crear nuestro CRUD. Para evitar configuraciones tediosas, vamos a usar XAMPP como servidor.
Configurar un servidor PHP con XAMPP
Si no sabéis como instalarlo podéis seguir los siguientes pasos:
- Descargar instalable desde la página oficial: https://www.apachefriends.org/es/
- Instalar el archivo, con la configuración por defecto hasta el final.
- Al finalizar os ofrecerá la opción de abrir el Panel de Control, y lo dejamos marcado:
4. Para iniciar el servidor le damos a Start/Iniciar Apache, y en el caso que queráis usar base de datos, le damos también a Start/Iniciar MySQL
5. De esta manera ya tenemos nuestro servidor corriendo. Si abrimos un navegador y escribimos localhost nos aparecerá la pantalla de inicio de XAMPP.
6. Para empezar a programar vamos a la carpeta donde se ha instalado XAMPP por defecto y entramos en la carpeta htdocs
Podemos borrar todo lo que tenemos dentro y crear ahí nuestro index.php
Dentro podemos escribir el siguiente código
Y si lo tenemos todo correcto, recargamos el navegador donde teníamos el localhost y nos sale esto
Recuerda que es una buena práctica no cerrar el tag de php en caso que no vayamos a escribir nada más luego.
Ahora que lo tenemos todo configurado ya estamos listos para empezar a programar!!
Hacer un CRUD
Ahora vamos a crear nuestro archivo crud.php que contendrá nuestra clase Crud, la cual manejará el sistema de persistencia.
Ahora lo ideal es que cada vez que instanciemos la clase Crud, le digamos el nombre de la tabla con la que queremos trabajar, por tanto ese argumento se lo pasamos por constructor a la clase Crud, el cual será asignado a un atributo privado para poder acceder a él desde cualquier método interno de la clase Crud, pero no desde fuera de la clase.
Por cuestiones de integridad, deberíamos obligar a que cada vez que se instancie la clase Crud, se tenga que pasar el nombre de la tabla obligatoriamente, para así no tener instanciaciones inconsistentes dentro de nuestra aplicación. Vamos a añadir una validación que lance un error en caso de no pasar ningún argumento. Cabe destacar que podríamos añadir más condiciones de validación, así como una manera de capturar ese error, pero no es objetivo del curso, simplemente queremos detener el flujo de nuestra aplicación en caso de que alguien instancie la clase CRUD sin el nombre de la tabla.
Como podemos ver hemos creado el método privado setTableName(), cuya función como dice su nombre es la de asignar un valor al atributo privado tableName de la clase CRUD. Para separar responsabilidades (Principio de Responsabilidad Única), separamos la asignación de la validación, así en el momento que queramos modificar los criterios de validación solo tenemos que tocar el método cuya responsabilidad es la de validar tableNameValidate().
Ahora vamos a crear nuestros cuatro métodos CRUD.
Como podemos ver, al método create() le pasamos como argumento la información que queremos guardar. Recuerda que cada información que guardamos debemos asignarle un id, en ese caso hemos decidido que ese id lo genere nuestra propia clase CRUD, en otros casos, y es muy buena práctica generar el id desde fuera de la clase CRUD, a través de algún sistema generador de id como uuid (pero eso da para otra entrada).
El método read() nos revolverá la información con el id que le pasemos por argumento. Update() nos actualiza la información que tenga el id que recibe por argumento con la data que también recibe por argumento y delete(), simplemente borrará la información que tenga el id que recibe por argumento.
Para obtener toda la información de la tabla podríamos utilizar el método read(), evaluando que no se le pase argumento, y así devolver todos los registro de la tabla, pero preferimos respectar el Principio de responsabilidad única y el Principio Abierto Cerrado, así que crearemos un quinto método que nos devuelva todos los registros readAll().
Para ir almacenando todos los registros de la tabla, vamos a crear otro atributo privado llamado data con el cual interactuarán nuestros cinco métodos públicos.
Como podemos ver hemos declarado el atributo privado data en la línea 5, y luego le asignamos un valor durante la instanciación de la clase en la línea 10 usando el método privado setData(). Por ahora ese método solo asigna un array en blanco, aunque más adelante hará más cosas.
BIEN!!! Ahora que ya tenemos la variable con la que va a interactuar nuestro CRUD, vamos a crear la lógica de cada uno de nuestros métodos.
Con el método create() simplemente tenemos que añadir la variable data que recibe por argumento al atributo privado data, por tanto podemos hacerlo con el método array_push() el cual añade la información al final del array y le asigna un id incremental. Como nuestro método es el que asigna un id al registro insertado, es buena práctica que devuelva dicho id, por si quien ha llamado a nuestro método create() lo necesita para seguir con su ejecución.
El método read() nos tiene que devolver el elemento del atributo privado data que tenga el id que ha recibido por parámetro.
El método readAll() nos tiene que devolver directamente el atributo privado data.
El método update() simplemente tiene que asignarle el valor data que recibe por parámetro al elemento del atributo privado data. Es buena práctica que si el proceso de actualización es correcto este método devuelva un valor booleano true.
Por último el método delete() elimina el registro del atributo privado data que tenga el id que recibe por parámetro. Al igual que update(), es buena práctica que si el proceso de borrado ha sido exitoso, este método nos devuelva un valor booleano true.
VAMOS AVANZANDO!!!!
Ahora seguramente te estarás preguntado. Hasta aquí todo muy bien, pero NO HEMOS GUARDADO LOS DATOS TODAVÍA EN NINGÚN SISTEMA DE PERSISTENCIA.
Efectivamente, y en eso radica todo esto, en que toda la lógica del CRUD que estamos haciendo es independiente del sistema de persistencia que vayamos a usar, ya sean variables de sesion, una SQL, una no SQL, una api externa, un archivo .json o un .txt. NO GUARDÉIS INFORMACIÓN EN UN .json NI EN UN .txt.
Trabajar así generalmente es bueno, aunque depende de cada caso, hay que tener en cuenta la escalabilidad y el rendimiento.
¿Cómo añadimos el sistema de persistencia?
Una rápida solución es crear dos método, uno que guarde en el sistema de persistencia usando una clave y un valor, donde la clave sea el nombre de la tabla(tableName) y el valor sería todo el contenido de el método privado data, lo llamaremos save(), y otro método que obtenga el valor(data) dada una clave (tableName) lo llamaremos get().
Usamos serialize() para guardar la data como texto y unserialize() para dar formato al texto obtenido
El proceso es el siguiente, una vez instanciada la clase CRUD, obtenemos la data del sistema de persistencia y la guardamos en el atributo privado data (en el caso que no tenga información asignamos un array vacío), y después de hacer cualquier cambio en el atributo privado data, guardamos el atributo privado data en el sistema de persistencia.
Como podemos ver hemos modificado setData(), donde obtenemos la data del sistema de persistencia también llamado repositorio, si nos retorna un null, es que no existe la tabla y por tanto inicializamos un array vacio.
También podemos observar que despues de los métodos create(), update() y delete() que son los que modifican el atributo privado data, siempre llamamos a save() para que actualice la información en el sistema de persistencia.
Hasta aquí tendriamos lo que es un CRUD básico, aunque nos dejamos muuuuchas cosas en el tintero para optimizar mejor este CRUD, si que podemos añadirle algunas restricciones, por ejemplo, ¿Qué pasa si intentamos leer, actualizar o eliminar un elemento con un id que no existe?
Vamos a añadir un método que devuelva true si existe un elemento con un id determinado y false en caso contrario.
Y añadimos esta validación siempre que usemos un elemento con id.
Como vemos hemos encapsulado la validación en una funcion checkThatElementExistWithId() que verifica que existe un elemento con el id pasado por el argumento, lanzando una excepción en caso incorrecto.
Nuestra clase CRUD quedaría así:
<?php class Crud { private $tableName, $data; public function __construct($tableName) { $this->setTableName($tableName); $this->setData(); } public function create($data) { array_push($this->data, $data); $this->save(); return count($this->data); } public function read($id) { $this->checkThatElementExistsWithId($id); return $this->data[$id]; } public function readAll() { return $this->data; } public function update($id, $data) { $this->checkThatElementExistsWithId($id); $this->data[$id] = $data; $this->save(); return true; } public function delete($id) { $this->checkThatElementExistsWithId($id); unset($this->data, $id); $this->save(); return true; } private function setTableName($tableName) { $this->tableNameValidate($tableName); $this->tableName = $tableName; } private function setData() { $dataRepository = $this->get($this->tableName); $this->data = $dataRepository == null ? [] : $dataRepository; } private function tableNameValidate($tableName) { if ($tableName == "" || $tableName == null) { throw new Exception("Table name required"); } if (!is_string($tableName)) { throw new Exception("Table name must be a string"); } } private function save() { $dataToSave = serialize($this->data); $filename = "./db/db.txt"; $file = fopen($filename, "w"); if ($file == false) { throw new Exception("Error in opening file"); exit(); } fwrite($file, $dataToSave); fclose($file); } private function get() { $filename = "./db/db.txt"; $file = fopen($filename, "r"); if ($file == false) { exit(); return null; } $filetext = fgets($file); fclose($file); return unserialize($filetext); } private function existElementWithId($id){ if(isset($this->data[$id])){ return true; } return false; } private function checkThatElementExistsWithId($id){ if(!$this->existElementWithId($id)){ throw new Exception("This element does not exist"); } } }
Nuestro index.php quedaría así añadiendo una implementación de CRUD:
Y tendríamos este resultado en el navegador:
Bueno, nos dejamos muchas cosas, pero como ejercicio creo que cumple si objetivo.
Hemos desarrollado un CRUD, donde nos acoplamos lo mínimo posible al sistema de persistencia (repositorio), desarrollando toda la lógica principal del CRUD nosotros mismos y de esta manera tener el control. Esto nos permite cambiar el sistema de persistencia del CRUD cambiando como mucho 2 líneas de código.
Como siempre, no estoy diciendo que sea el método más efectivo, ya que generalmente los sistemas de persistencia ya traen CRUD quizá más optimizados, sacrificando que si en algún momento tenemos que cambiar el sistema de persistencia tendríamos que modificar muchas más líneas de código O PUEDE QUE NO NECESARIAMENTE JEJE (pero eso lo veremos en otra entrada)
Quizá te interese esta otra entrada sobre como hacer un CRUD en Javascript