BD PostgreSQL

Conectar PostgreSQL y PHP usando PDO | CRUD

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 😉

Conectar PHP con PostgreSQL

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.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Ver comentarios

  • 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.-

  • 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

Entradas recientes

Creador de credenciales web – Aplicación gratuita

Hoy te voy a presentar un creador de credenciales que acabo de programar y que…

1 semana hace

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

2 semanas hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

2 semanas hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

2 semanas hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

2 semanas hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

2 semanas hace

Esta web usa cookies.