En este post vamos a ver cómo consumir una base de datos de Postgres a través de PHP usando el controlador PDO.
Haremos las 4 operaciones básicas del motor: create, read, update y delete; lo que es crear, leer, actualizar y eliminar, todo desde PHP.
Como en todos mis tutoriales, vamos a conectar a una base de datos SQL evitando inyecciones SQL 😉
Nota: recuerda habilitar la extensión de PostgreSQL (o mira este otro si usas cPanel).
Nota 2: puedes ver el código completo en GitHub.
Base de datos
Vamos a usar una base de datos llamada mascotas, que tendrá a su vez una tabla llamada mascotas.
Las mascotas tendrán un id serial, un nombre y una edad. Esto es para ejemplificar con datos simples y sin relaciones.
/*
CRUD con PostgreSQL y PHP
@author parzibyte [parzibyte.me/blog]
@date 2019-06-17
================================
Este archivo define la tabla e inserta algunos datos
para trabajar
================================
*/
CREATE TABLE mascotas(
id serial primary key,
nombre varchar(50) NOT NULL,
edad smallint NOT NULL
);
insert into mascotas
(nombre, edad)
values
('Maggie', 3),
('Guayaba', 2),
('Capuchina', 2),
('Snowball', 1),
('Panqué', 1);
Cadena de conexión
Vamos a poner todo lo relativo a la conexión a la base de datos en un archivo que más tarde vamos a incluir.
Comenzamos creando un objeto PDO; la cadena de conexión es la siguiente:
<?php
/*
CRUD con PostgreSQL y PHP
@author parzibyte [parzibyte.me/blog]
@date 2019-06-17
================================
Este archivo se encarga de conectar a la base de datos
y traer un objeto PDO
Recuerda cambiar tus credenciales, y tal vez ponerlas en
un archivo env: https://parzibyte.me/blog/2018/06/30/leer-archivo-configuracion-ini-php/
================================
*/
$contraseña = "hunter2";
$usuario = "parzibyte";
$nombreBaseDeDatos = "mascotas";
# Puede ser 127.0.0.1 o el nombre de tu equipo; o la IP de un servidor remoto
$rutaServidor = "127.0.0.1";
$puerto = "5432";
try {
$base_de_datos = new PDO("pgsql:host=$rutaServidor;port=$puerto;dbname=$nombreBaseDeDatos", $usuario, $contraseña);
$base_de_datos->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (Exception $e) {
echo "Ocurrió un error con la base de datos: " . $e->getMessage();
}
Lo único que hay que hacer es cambiar las credenciales de acuerdo a nuestra configuración.
Si no puedes realizar la conexión crea un usuario y una base de datos nueva.
Plantilla base
Vamos a tomar la plantilla de Bootstrap 4. No te debería importar mucho el diseño, pues importa más la aplicación, así que no te confundas por las clases o los estilos.
Definiremos un encabezado con el menú de navegación, y un pie que cierra las etiquetas.
El contenido será puesto dentro del elemento main
.
Insertar: formulario
La forma más común con la que el usuario ingresa datos a nuestra app es a través de formularios web. Veamos cómo se define:
<?php
/*
CRUD con PostgreSQL y PHP
@author parzibyte [parzibyte.me/blog]
@date 2019-06-17
================================
Este archivo muestra un formulario que
se envía a insertar.php, el cual guardará
los datos
================================
*/
?>
<?php include_once "encabezado.php" ?>
<div class="row">
<div class="col-12">
<h1>Agregar</h1>
<form action="insertar.php" method="POST">
<div class="form-group">
<label for="nombre">Nombre</label>
<input required name="nombre" type="text" id="nombre" placeholder="Nombre de mascota" class="form-control">
</div>
<div class="form-group">
<label for="edad">Edad</label>
<input required name="edad" type="number" id="edad" placeholder="Edad de mascota" class="form-control">
</div>
<button type="submit" class="btn btn-success">Guardar</button>
<a href="./listar.php" class="btn btn-warning">Ver todas</a>
</form>
</div>
</div>
<?php include_once "pie.php" ?>
Presta atención al atributo name
de cada campo, pues con ese valor vamos a leer los datos al recibirlos.
También mira el action
del formulario, va a insertar.php
Guardar datos del formulario
Aquí es en donde realmente guardamos los datos. Lo que se envió en el formulario se encuentra en el arreglo superglobal $_POST
y accedemos a cada valor a través del atributo name
.
Para tener disponible la conexión a la base de datos simplemente hacemos un include
del archivo y listo, tenemos la variable $base_de_datos
.
Se debe crear una sentencia o prepared statement con el método prepare
, pasando la consulta pero en lugar de datos se ponen placeholders con signos de interrogación.
Los verdaderos datos se envían cuando se llama a execute
de la sentencia, y se pasan en forma de arreglo en el mismo orden que aparecen los signos de interrogación.
<?php
/*
CRUD con PostgreSQL y PHP
@author parzibyte [parzibyte.me/blog]
@date 2019-06-17
================================
Este archivo inserta los datos
enviados a través de formulario.php
================================
*/
?>
<?php
#Salir si alguno de los datos no está presente
if (!isset($_POST["nombre"]) || !isset($_POST["edad"])) {
exit();
}
#Si todo va bien, se ejecuta esta parte del código...
include_once "base_de_datos.php";
$nombre = $_POST["nombre"];
$edad = $_POST["edad"];
/*
Al incluir el archivo "base_de_datos.php", todas sus variables están
a nuestra disposición. Por lo que podemos acceder a ellas tal como si hubiéramos
copiado y pegado el código
*/
$sentencia = $base_de_datos->prepare("INSERT INTO mascotas(nombre, edad) VALUES (?, ?);");
$resultado = $sentencia->execute([$nombre, $edad]); # Pasar en el mismo orden de los ?
#execute regresa un booleano. True en caso de que todo vaya bien, falso en caso contrario.
#Con eso podemos evaluar
if ($resultado === true) {
# Redireccionar a la lista
header("Location: listar.php");
} else {
echo "Algo salió mal. Por favor verifica que la tabla exista";
}
El método execute
regresa un booleano, y si todo es correcto, hacemos una redirección al archivo listar.php.
Obtener datos
Voy a exponer dos maneras de obtener datos o hacer un select
. El primer método trae los datos en un arreglo, pero recordemos que el arreglo se guarda en memoria y por lo tanto para un arreglo grande esto no es recomendable.
El segundo método (se le llama iterar con un cursor) es más ligero y usa un ciclo, el cual simplemente lee dato por dato (cargando una fila en memoria a la vez) sin importar el número de los mismos.
Con arreglo
Para traer los datos en un arreglo se usa el siguiente código:
<?php
include_once "base_de_datos.php";
$sentencia = $base_de_datos->query("select id, nombre, edad from mascotas");
$mascotas = $sentencia->fetchAll(PDO::FETCH_OBJ);
Así de simple, ahora podemos enviar ese arreglo como salida JSON, o recorrerlo y dibujar elementos.
Haremos esto último y por lo tanto el código que lista o dibuja los datos es el siguiente:
<?php
/*
CRUD con PostgreSQL y PHP
@author parzibyte [parzibyte.me/blog]
@date 2019-06-17
================================
Este archivo lista todos los
datos de la tabla, obteniendo a
los mismos como un arreglo
================================
*/
?>
<?php
include_once "base_de_datos.php";
$sentencia = $base_de_datos->query("select id, nombre, edad from mascotas");
$mascotas = $sentencia->fetchAll(PDO::FETCH_OBJ);
?>
<!--Recordemos que podemos intercambiar HTML y PHP como queramos-->
<?php include_once "encabezado.php" ?>
<div class="row">
<!-- Aquí pon las col-x necesarias, comienza tu contenido, etcétera -->
<div class="col-12">
<h1>Listar con arreglo</h1>
<a href="//parzibyte.me/blog" target="_blank">By Parzibyte</a>
<br>
<div class="table-responsive">
<table class="table table-bordered">
<thead class="thead-dark">
<tr>
<th>ID</th>
<th>Nombre</th>
<th>Edad</th>
<th>Editar</th>
<th>Eliminar</th>
</tr>
</thead>
<tbody>
<!--
Atención aquí, sólo esto cambiará
Pd: no ignores las llaves de inicio y cierre {}
-->
<?php foreach($mascotas as $mascota){ ?>
<tr>
<td><?php echo $mascota->id ?></td>
<td><?php echo $mascota->nombre ?></td>
<td><?php echo $mascota->edad ?></td>
<td><a class="btn btn-warning" href="<?php echo "editar.php?id=" . $mascota->id?>">Editar 📝</a></td>
<td><a class="btn btn-danger" href="<?php echo "eliminar.php?id=" . $mascota->id?>">Eliminar 🗑️</a></td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
</div>
</div>
<?php include_once "pie.php" ?>
Todos los datos son dibujados en una tabla y adicional a ello se crea un enlace o ancla que llevará a dos archivos que veremos más adelante: editar.php y eliminar.php
Cada enlace tendrá el id de la mascota actual, por ejemplo, uno se verá así:
editar.php?id=1
Y otro así: eliminar.php?id=1
Esto es para que podamos recuperar el id en los demás archivos.
Obtener datos con cursor
Ahora veamos el otro enfoque que es más óptimo en algunos casos. Es casi lo mismo, pero en un ciclo while que se realizará siempre y cuando fetchObject
traiga algo distinto de null
.
<?php
/*
CRUD con PostgreSQL y PHP
@author parzibyte [parzibyte.me/blog]
@date 2019-06-17
================================
Este archivo lista todos los
datos de la tabla, pero en un
ciclo usando un cursor, no
a través de un arreglo
(se supone que es más eficiente)
================================
*/
?>
<?php
include_once "base_de_datos.php";
$consulta = "select id, nombre, edad from mascotas";
# Preparar sentencia e indicar que vamos a usar un cursor
$sentencia = $base_de_datos->prepare($consulta, [
PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL,
]);
# Ejecutar sin parámetros
$sentencia->execute();
# Abajo iteramos
?>
<!--Recordemos que podemos intercambiar HTML y PHP como queramos-->
<?php include_once "encabezado.php" ?>
<div class="row">
<!-- Aquí pon las col-x necesarias, comienza tu contenido, etcétera -->
<div class="col-12">
<h1>Listar con cursor</h1>
<a href="//parzibyte.me/blog" target="_blank">By Parzibyte</a>
<br>
<table class="table table-bordered">
<thead class="thead-dark">
<tr>
<th>ID</th>
<th>Nombre</th>
<th>Edad</th>
<th>Editar</th>
<th>Eliminar</th>
</tr>
</thead>
<tbody>
<!--
Y aquí usamos el ciclo while y fecthObject, el cuerpo
del ciclo queda intacto pero ahora estamos usando
cursores :)
-->
<?php while ($mascota = $sentencia->fetchObject()){ ?>
<tr>
<td><?php echo $mascota->id ?></td>
<td><?php echo $mascota->nombre ?></td>
<td><?php echo $mascota->edad ?></td>
<td><a class="btn btn-warning" href="<?php echo "editar.php?id=" . $mascota->id?>">Editar 📝</a></td>
<td><a class="btn btn-danger" href="<?php echo "eliminar.php?id=" . $mascota->id?>">Eliminar 🗑️</a></td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
</div>
<?php include_once "pie.php" ?>
Igualmente accedemos al objeto con el operador flecha ->
haciendo el código más expresivo.
Editar
Editar tiene 2 etapas. Una en donde se muestra el formulario para editar (con los campos rellenados por defecto) y otra en donde se guardan los datos editados.
Comencemos viendo el formulario. Tenemos que recuperar el id de la URL (el que se dibujó con la tabla) a través de $_GET
.
Cuando tenemos el id, consultamos los datos frescos de la mascota, y rellenamos el formulario poniendo el valor en el value
de cada input
.
<?php
/*
CRUD con PostgreSQL y PHP
@author parzibyte [parzibyte.me/blog]
@date 2019-06-17
================================
Este archivo muestra un formulario llenado automáticamente
(a partir del ID pasado por la URL) para editar
================================
*/
if (!isset($_GET["id"])) {
exit();
}
$id = $_GET["id"];
include_once "base_de_datos.php";
$sentencia = $base_de_datos->prepare("SELECT id, nombre, edad FROM mascotas WHERE id = ?;");
$sentencia->execute([$id]);
$mascota = $sentencia->fetchObject();
if (!$mascota) {
#No existe
echo "¡No existe alguna mascota con ese ID!";
exit();
}
#Si la mascota existe, se ejecuta esta parte del código
?>
<?php include_once "encabezado.php"?>
<div class="row">
<div class="col-12">
<h1>Editar</h1>
<form action="guardarDatosEditados.php" method="POST">
<input type="hidden" name="id" value="<?php echo $mascota->id; ?>">
<div class="form-group">
<label for="nombre">Nombre</label>
<input value="<?php echo $mascota->nombre; ?>" required name="nombre" type="text" id="nombre" placeholder="Nombre de mascota" class="form-control">
</div>
<div class="form-group">
<label for="edad">Edad</label>
<input value="<?php echo $mascota->edad; ?>" required name="edad" type="number" id="edad" placeholder="Edad de mascota" class="form-control">
</div>
<button type="submit" class="btn btn-success">Guardar</button>
<a href="./listar.php" class="btn btn-warning">Volver</a>
</form>
</div>
</div>
<?php include_once "pie.php"?>
El action
del formulario es guardarDatosEditados.php que funciona casi igual que el que inserta, pero haciendo un update.
Por cierto, el id se guarda en un input oculto que el usuario, teóricamente, no puede modificar. Así tenemos un registro del id sin que el usuario tenga que escribirlo.
Para pasar los datos se usa igualmente un arreglo y se ponen placeholders para evitar inyecciones SQL.
<?php
/*
CRUD con PostgreSQL y PHP
@author parzibyte [parzibyte.me/blog]
@date 2019-06-17
================================
Este archivo guarda los datos del formulario
en donde se editan
================================
*/
?>
<?php
#Salir si alguno de los datos no está presente
if (
!isset($_POST["nombre"]) ||
!isset($_POST["edad"]) ||
!isset($_POST["id"])
) {
exit();
}
#Si todo va bien, se ejecuta esta parte del código...
include_once "base_de_datos.php";
$id = $_POST["id"];
$nombre = $_POST["nombre"];
$edad = $_POST["edad"];
$sentencia = $base_de_datos->prepare("UPDATE mascotas SET nombre = ?, edad = ? WHERE id = ?;");
$resultado = $sentencia->execute([$nombre, $edad, $id]); # Pasar en el mismo orden de los ?
if ($resultado === true) {
header("Location: listar.php");
} else {
echo "Algo salió mal. Por favor verifica que la tabla exista, así como el ID del usuario";
}
En caso de éxito se redirige a listar, y en caso contrario, se imprime el error.
Eliminar
El de eliminar es posiblemente la parte más sencilla, pero debes tener cuidado si esta operación necesita confirmación.
En el caso más simple deberías usar un token csrf como el que se utiliza en un sistema de cotizaciones que hice hace tiempo.
Si solo es un proyecto personal o escolar, no importa que lo hagas con un simple enlace.
<?php
/*
CRUD con PostgreSQL y PHP
@author parzibyte [parzibyte.me/blog]
@date 2019-06-17
================================
Este archivo elimina un dato por ID sin
pedir confirmación. El ID viene de la URL
================================
*/
if (!isset($_GET["id"])) {
exit();
}
$id = $_GET["id"];
include_once "base_de_datos.php";
$sentencia = $base_de_datos->prepare("DELETE FROM mascotas WHERE id = ?;");
$resultado = $sentencia->execute([$id]);
if ($resultado === true) {
header("Location: listar.php");
} else {
echo "Algo salió mal";
}
En el código se hace un delete (evitando inyecciones SQL de nuevo) con un where
, es un simple execute como el que se hace un update
y un insert
.
Conclusión
Gracias a PDO podemos conectar PHP con bases de datos de una manera simple y genérica, ya que si en un futuro queremos cambiar de base de datos simplemente cambiamos la creación del objeto PDO.
De hecho, este código es casi idéntico al que utilicé en la conexión con SQL Server.
Si quieres ver más como esto te invito a leer:
No olvides que puedes navegar por todo el código fuente.
Buen día. Te comento que me da el siguiente error:
Ocurrió un error con la base de datos: could not find driver
Notice: Undefined variable: base_de_datos in C:\xampp\htdocs\mascotas\listar.php on line 15
Fatal error: Uncaught Error: Call to a member function query() on null in C:\xampp\htdocs\mascotas\listar.php:15 Stack trace: #0 {main} thrown in C:\xampp\htdocs\mascotas\listar.php on line 15
Lo probé en 2 máquinas: Win7 y Win10. En ambas tengo la misma configuración: XAMPP ControlPanel v3.3.0 con Postgre13 (pgAdmi4). ¿Alguna sugerencia? Muchas gracias.-
El mensaje está indicando el error: could not find the driver
hola ya cambie la sentencia pero me da error en la siguiente linea
Parse error: syntax error, unexpected T_STRING in C:\wamp\www\2019-A-01\CRUD-postgresql-php-master\editar.php on line 21
17 $id = $_GET[“id”];
18 include_once “base_de_datos.php”;
19 $sentencia = $base_de_datos->prepare(“SELECT id, nombre, edad FROM mascotas WHERE id = ?;”);
20 $sentencia = array($id);
21 $mascota = $sentencia fetchObject();
Te recomiendo colocar tu código en gist.github.com para que pueda leerlo correctamente, ya que mi sitio remueve algunas etiquetas. Una vez que lo pongas en un gist, copia y pega el enlace en los comentarios
Hola me Permito Pedirte una ayuda ya que al tratar de ejecutarlas opciones de Insertar da este error: Parse error: syntax error, unexpected ‘[‘, expecting ‘)’ in C:\wamp\www\2019-A-01\CRUD-postgresql-php-master\editar.php on line 20
y tengo el codigo
Hola, buen día. Es por la sintaxis corta del arreglo, pues has de usar una versión anterior de PHP. Para solucionarlo, en lugar de:
[$id]
Utiliza:
array($id)
Más información sobre la sintaxis corta: https://parzibyte.me/blog/2018/10/11/sintaxis-corta-array-php/
Pingback: Extensión PDO de PostgreSQL con PHP y Linux Ubuntu - Parzibyte's blog