K. Carpeta « srv »

Versión para imprimir.

A. Carpeta « srv / modelo »

Versión para imprimir.

1. srv / modelo / Archivo.php

1<?php
2
3require_once __DIR__ . "/../../lib/php/ProblemDetails.php";
4
5class Archivo
6{
7
8 public int $id;
9 public string $bytes;
10
11 public function __construct(string $bytes = "", int $id = 0)
12 {
13 $this->id = $id;
14 $this->bytes = $bytes;
15 }
16
17 public function valida()
18 {
19 if ($this->bytes === "")
20 throw new ProblemDetails(
21 status: ProblemDetails::BadRequest,
22 type: "/error/archivovacio.html",
23 title: "Archivo vacío.",
24 detail: "Selecciona un archivo que no esté vacío."
25 );
26 }
27}
28

2. srv / modelo / Producto.php

1<?php
2
3require_once __DIR__ . "/../../lib/php/validaNombre.php";
4require_once __DIR__ . "/../../lib/php/ProblemDetails.php";
5require_once __DIR__ . "/Archivo.php";
6
7class Producto
8{
9
10 public int $id;
11 public string $nombre;
12 public ?Archivo $archivo;
13
14 public function __construct(
15 string $nombre = "",
16 Archivo $archivo = null,
17 int $id = 0,
18 ) {
19 $this->id = $id;
20 $this->nombre = $nombre;
21 $this->archivo = $archivo;
22 }
23
24 public function validaNuevo()
25 {
26 validaNombre($this->nombre);
27 if ($this->archivo === null)
28 throw new ProblemDetails(
29 status: ProblemDetails::BadRequest,
30 type: "/error/faltaarchivo.html",
31 title: "Falta el archivo.",
32 detail: "Selecciona un archivo que no esté vacío."
33 );
34 $this->archivo->valida();
35 }
36
37 public function valida()
38 {
39 validaNombre($this->nombre);
40 }
41}
42

B. srv / srvArchivo.php

1<?php
2
3require_once __DIR__ . "/../lib/php/ejecutaServicio.php";
4require_once __DIR__ . "/../lib/php/pdFaltaId.php";
5require_once __DIR__ . "/../lib/php/ProblemDetails.php";
6require_once __DIR__ . "/../lib/php/leeEntero.php";
7require_once __DIR__ . "/bd/archivoBusca.php";
8
9mb_internal_encoding("UTF-8");
10try {
11 // Evita que la imagen se cargue en el caché de la computadora.
12 header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
13 header("Cache-Control: post-check=0, pre-check=0", false);
14 header("Pragma: no-cache");
15 $id = leeEntero("id");
16 if ($id === null) throw pdFaltaId();
17 $archivo = archivoBusca($id);
18 if ($archivo === false) {
19 throw new ProblemDetails(
20 status: ProblemDetails::BadRequest,
21 type: "/error/archivonoencontrado.html",
22 title: "Archivo no encontrado.",
23 detail: "No se encontró ningún archivo con el id solicitado."
24 );
25 }
26 echo $archivo->bytes;
27} catch (ProblemDetails $details) {
28 procesa_problem_details($details);
29} catch (Throwable $throwable) {
30 procesa_problem_details(new ProblemDetails(
31 status: ProblemDetails::InternalServerError,
32 type: "/error/errorinterno.html",
33 title: "Error interno del servidor.",
34 detail: $throwable->getMessage()
35 ));
36}
37

C. srv / srvProductoAgrega.php

1<?php
2
3require_once __DIR__ . "/../lib/php/ejecutaServicio.php";
4require_once __DIR__ . "/../lib/php/JsonResponse.php";
5require_once __DIR__ . "/../lib/php/leeBytes.php";
6require_once __DIR__ . "/../lib/php/leeTexto.php";
7require_once __DIR__ . "/modelo/Archivo.php";
8require_once __DIR__ . "/modelo/Producto.php";
9require_once __DIR__ . "/bd/productoAgrega.php";
10
11ejecutaServicio(function () {
12 $bytes = leeBytes("bytes");
13 $archivo = $bytes === "" ? null : new Archivo(bytes: $bytes);
14
15 $nombre = leeTexto("nombre");
16 $modelo = new Producto(
17 nombre: $nombre === null ? "" : trim($nombre),
18 archivo: $archivo
19 );
20
21 productoAgrega($modelo);
22
23 $id = htmlentities($modelo->id);
24 // Los bytes se descargan con SrvArchivo; no desde aquí.
25 return JsonResponse::created("/srv/srvProductoBusca.php?id=$id", [
26 "id" => ["value" => $modelo->id],
27 "nombre" => ["value" => $modelo->nombre],
28 "imagen" => [
29 "src" => $archivo === null
30 ? ""
31 : "srv/srvArchivo.php?id=" . $archivo->id,
32 ]
33 ]);
34});
35

D. srv / srvProductoBusca.php

1<?php
2
3require_once __DIR__ . "/../lib/php/ejecutaServicio.php";
4require_once __DIR__ . "/../lib/php/pdFaltaId.php";
5require_once __DIR__ . "/../lib/php/ProblemDetails.php";
6require_once __DIR__ . "/../lib/php/leeEntero.php";
7require_once __DIR__ . "/bd/productoBusca.php";
8
9ejecutaServicio(function () {
10 $id = leeEntero("id");
11 if ($id === null) throw pdFaltaId();
12 $modelo = productoBusca($id);
13 if ($modelo === false) {
14 $htmlId = htmlentities($id);
15 throw new ProblemDetails(
16 status: ProblemDetails::NotFound,
17 type: "/error/productonoencontrado.html",
18 title: "Producto no encontrado.",
19 detail: "No se encontró ningún producto con el id $htmlId.",
20 );
21 } else {
22 $archivo = $modelo->archivo;
23 return [
24 "id" => ["value" => $modelo->id],
25 "nombre" => ["value" => $modelo->nombre],
26 "imagen" => [
27 "src" => $archivo === null
28 ? ""
29 : "srv/srvArchivo.php?id=" . $archivo->id
30 ]
31 ];
32 }
33});
34

E. srv / srvProductoConsulta.php

1<?php
2
3require_once __DIR__ . "/../lib/php/ejecutaServicio.php";
4require_once __DIR__ . "/bd/productoConsulta.php";
5
6ejecutaServicio(function () {
7 $lista = productoConsulta();
8 $render = "";
9 foreach ($lista as $modelo) {
10 $prodId = htmlentities($modelo->prodId);
11 $prodNombre = htmlentities($modelo->prodNombre);
12 $archId = $modelo->archId === null
13 ? ""
14 : htmlentities($modelo->archId);
15 $render .=
16 "<div style='display: flex; flex-direction: row-reverse;
17 align-items: center; gap: 0.5rem'>
18 <dt style='flex: 1 1 0'>
19 <a href='modifica.html?id=$prodId'>$prodNombre</a>
20 </dt>
21 <dd style='flex: 1 1 0; margin: 0'>
22 <a href='modifica.html?id=$prodId'><img style='width: 100%'
23 alt='Imagen del producto' src='srv/srvArchivo.php?id=$archId'></a>
24 </dd>
25 </div>";
26 }
27 return ["lista" => ["innerHTML" => $render]];
28});
29

F. srv / srvProductoElimina.php

1<?php
2
3require_once __DIR__ . "/../lib/php/ejecutaServicio.php";
4require_once __DIR__ . "/../lib/php/JsonResponse.php";
5require_once __DIR__ . "/../lib/php/pdFaltaId.php";
6require_once __DIR__ . "/../lib/php/leeEntero.php";
7require_once __DIR__ . "/bd/productoElimina.php";
8
9ejecutaServicio(function () {
10 $id = leeEntero("id");
11 if ($id === null) throw pdFaltaId();
12 productoElimina($id);
13 return JsonResponse::noContent();
14});
15

G. srv / srvProductoModifica.php

1<?php
2
3require_once __DIR__ . "/../lib/php/ejecutaServicio.php";
4require_once __DIR__ . "/../lib/php/pdFaltaId.php";
5require_once __DIR__ . "/../lib/php/leeEntero.php";
6require_once __DIR__ . "/../lib/php/leeBytes.php";
7require_once __DIR__ . "/../lib/php/leeTexto.php";
8require_once __DIR__ . "/modelo/Archivo.php";
9require_once __DIR__ . "/modelo/Producto.php";
10require_once __DIR__ . "/bd/productoModifica.php";
11
12ejecutaServicio(function () {
13 $id = leeEntero("id");
14 if ($id === null) throw pdFaltaId();
15 $bytes = leeBytes("bytes");
16 $archivo = $bytes === "" ? null : new Archivo(bytes: $bytes);
17
18 $nombre = leeTexto("nombre");
19 $modelo = new Producto(
20 $nombre === null ? "" : trim($nombre),
21 archivo: $archivo,
22 id: $id
23 );
24
25 productoModifica($modelo);
26
27 // Los bytes se descargan con SrvArchivo; no desde aquí.
28 $archivo = $modelo->archivo;
29 return [
30 "id" => ["value" => $modelo->id],
31 "nombre" => ["value" => $modelo->nombre],
32 "imagen" => [
33 "src" => $archivo === null
34 ? ""
35 : "srv/srvArchivo.php?id=" . $archivo->id,
36 ]
37 ];
38});
39

H. Carpeta « srv / bd »

Versión para imprimir.

1. srv / bd / bdCrea.php

1<?php
2
3function bdCrea(PDO $con)
4{
5 $con->exec(
6 'CREATE TABLE IF NOT EXISTS ARCHIVO (
7 ARCH_ID INTEGER,
8 ARCH_BYTES BLOB NOT NULL,
9 CONSTRAINT ARCH_PK
10 PRIMARY KEY(ARCH_ID)
11 )'
12 );
13 $con->exec(
14 'CREATE TABLE IF NOT EXISTS PRODUCTO (
15 PROD_ID INTEGER,
16 PROD_NOMBRE TEXT NOT NULL,
17 ARCH_ID INTEGER NOT NULL,
18 CONSTRAINT PROD_PK
19 PRIMARY KEY(PROD_ID),
20 CONSTRAINT PROD_NOM_UNQ
21 UNIQUE(PROD_NOMBRE)
22 CONSTRAINT PROD_ARCH_FK
23 FOREIGN KEY (ARCH_ID) REFERENCES ARCHIVO(ARCH_ID)
24 )'
25 );
26}
27

2. srv / bd / Bd.php

1<?php
2
3require_once __DIR__ . "/bdCrea.php";
4
5class Bd
6{
7
8 private static ?PDO $conexion = null;
9
10 static function getConexion(): PDO
11 {
12 if (self::$conexion === null) {
13
14 self::$conexion = new PDO(
15 // cadena de conexión
16 "sqlite:srvarchivos.db",
17 // usuario
18 null,
19 // contraseña
20 null,
21 // Opciones: conexiones persistentes y lanza excepciones.
22 [PDO::ATTR_PERSISTENT => true, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
23 );
24
25 bdCrea(self::$conexion);
26 }
27
28 return self::$conexion;
29 }
30}
31

3. srv / bd / archivoAgrega.php

1<?php
2
3require_once __DIR__ . "/../modelo/Archivo.php";
4require_once __DIR__ . "/Bd.php";
5
6function archivoAgrega(Archivo $modelo)
7{
8 $modelo->valida();
9 $con = Bd::getConexion();
10 $stmt = $con->prepare(
11 "INSERT INTO ARCHIVO
12 (ARCH_BYTES)
13 VALUES
14 (:bytes)"
15 );
16 $stmt->execute([
17 ":bytes" => $modelo->bytes
18 ]);
19 /* Si usas una secuencia para generar el id,
20 * pasa como parámetro de lastInsertId el
21 * nombre de dicha secuencia, debes
22 * ejecutarlo antes del INSERT y pasarle el
23 * id generado al SQL. */
24 $modelo->id = $con->lastInsertId();
25}
26

4. srv / bd / archivoBusca.php

1<?php
2
3require_once __DIR__ . "/../modelo/Archivo.php";
4require_once __DIR__ . "/Bd.php";
5
6function archivoBusca(int $id): false|Archivo
7{
8 $con = Bd::getConexion();
9 $stmt = $con->prepare(
10 "SELECT
11 ARCH_ID AS id,
12 ARCH_BYTES AS bytes
13 FROM ARCHIVO
14 WHERE ARCH_ID = :id"
15 );
16 $stmt->execute([":id" => $id]);
17 $stmt->setFetchMode(
18 PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE,
19 Archivo::class
20 );
21 return $stmt->fetch();
22}
23

5. srv / bd / archivoElimina.php

1<?php
2
3require_once __DIR__ . "/Bd.php";
4
5function archivoElimina(int $id)
6{
7 $con = Bd::getConexion();
8 $stmt = $con->prepare(
9 "DELETE FROM ARCHIVO
10 WHERE ARCH_ID = :id"
11 );
12 $stmt->execute([":id" => $id]);
13}
14

6. srv / bd / archivoModifica.php

1<?php
2
3require_once __DIR__ . "/../modelo/Archivo.php";
4require_once __DIR__ . "/Bd.php";
5
6function archivoModifica(Archivo $modelo)
7{
8 $modelo->valida();
9 $con = Bd::getConexion();
10 $stmt = $con->prepare(
11 "UPDATE ARCHIVO
12 SET ARCH_BYTES = :bytes
13 WHERE ARCH_ID = :id"
14 );
15 $stmt->execute([
16 ":bytes" => $modelo->bytes,
17 ":id" => $modelo->id
18 ]);
19}
20

7. srv / bd / productoAgrega.php

1<?php
2
3require_once __DIR__ . "/../modelo/Archivo.php";
4require_once __DIR__ . "/Bd.php";
5require_once __DIR__ . "/archivoAgrega.php";
6
7function productoAgrega(Producto $modelo)
8{
9 $modelo->validaNuevo();
10 $con = Bd::getConexion();
11 $con->beginTransaction();
12 archivoAgrega($modelo->archivo);
13 $stmt = $con->prepare(
14 "INSERT INTO PRODUCTO
15 (PROD_NOMBRE, ARCH_ID)
16 VALUES
17 (:nombre, :archId)"
18 );
19 $stmt->execute([
20 ":nombre" => $modelo->nombre,
21 ":archId" => $modelo->archivo->id
22 ]);
23 /* Si usas una secuencia para generar el id,
24 * pasa como parámetro de lastInsertId el
25 * nombre de dicha secuencia, debes
26 * ejecutarlo antes del INSERT y pasarle el
27 * id generado al SQL. */
28 $modelo->id = $con->lastInsertId();
29 $con->commit();
30}
31

8. srv / bd / productoBusca.php

1<?php
2
3require_once __DIR__ . "/../modelo/Archivo.php";
4require_once __DIR__ . "/../modelo/Producto.php";
5require_once __DIR__ . "/Bd.php";
6
7function productoBusca(int $prodId)
8{
9 $con = Bd::getConexion();
10 $stmt = $con->prepare(
11 "SELECT
12 P.PROD_ID AS prodId,
13 P.PROD_NOMBRE AS prodNombre,
14 A.ARCH_ID AS archId
15 FROM PRODUCTO P
16 LEFT JOIN ARCHIVO A
17 ON P.ARCH_ID = A.ARCH_ID
18 WHERE P.PROD_ID = :prodId"
19 );
20 $stmt->execute([
21 ":prodId" => $prodId
22 ]);
23 $stmt->setFetchMode(PDO::FETCH_OBJ);
24 $obj = $stmt->fetch();
25 if ($obj === false) {
26 return false;
27 } else {
28 $id = $obj->prodId;
29 $nombre = $obj->prodNombre;
30 $archId = $obj->archId;
31 $archivo = $archId === null ? null : new Archivo(id: $archId);
32 $producto = new Producto(
33 id: $id,
34 nombre: $nombre,
35 archivo: $archivo
36 );
37 return $producto;
38 }
39}
40

9. srv / bd / productoConsulta.php

1<?php
2
3require_once __DIR__ . "/../../lib/php/recibeFetchAll.php";
4require_once __DIR__ . "/../modelo/Producto.php";
5require_once __DIR__ . "/Bd.php";
6
7function productoConsulta()
8{
9 $con = Bd::getConexion();
10 $stmt = $con->query(
11 "SELECT
12 P.PROD_ID AS prodId,
13 P.PROD_NOMBRE AS prodNombre,
14 A.ARCH_ID AS archId
15 FROM PRODUCTO P
16 LEFT JOIN ARCHIVO A
17 ON P.ARCH_ID = A.ARCH_ID
18 ORDER BY P.PROD_NOMBRE"
19 );
20 $resultado = $stmt->fetchAll(PDO::FETCH_OBJ);
21 return recibeFetchAll($resultado);
22}
23

10. srv / bd / productoElimina.php

1<?php
2
3require_once __DIR__ . "/Bd.php";
4require_once __DIR__ . "/archivoElimina.php";
5require_once __DIR__ . "/productoBusca.php";
6
7function productoElimina(int $id)
8{
9 $con = Bd::getConexion();
10 $con->beginTransaction();
11 $modelo = productoBusca($id);
12 if ($modelo === false) {
13 $con->rollBack();
14 } else {
15 archivoElimina($modelo->archivo->id);
16 $stmt = $con->prepare(
17 "DELETE FROM PRODUCTO
18 WHERE PROD_ID = :id"
19 );
20 $stmt->execute([":id" => $modelo->id]);
21 $con->commit();
22 }
23}
24

11. srv / bd / productoModifica.php

1<?php
2
3require_once __DIR__ . "/../modelo/Producto.php";
4require_once __DIR__ . "/Bd.php";
5require_once __DIR__ . "/productoBusca.php";
6require_once __DIR__ . "/archivoModifica.php";
7
8function productoModifica(Producto $modelo)
9{
10 $modelo->valida();
11 $con = Bd::getConexion();
12 $con->beginTransaction();
13 $archivo = $modelo->archivo;
14 $anterior = productoBusca($modelo->id);
15 if ($anterior === false) {
16 throw new Exception("Producto no encontrado.");
17 }
18 if ($anterior->archivo === null) {
19 throw new Exception("Falta el archivo anterior.");
20 }
21 if ($archivo === null) {
22 $archivo = $anterior->archivo;
23 $modelo->archivo = $archivo;
24 } else {
25 $archivo->id = $anterior->archivo->id;
26 archivoModifica($archivo);
27 }
28 $stmt = $con->prepare(
29 "UPDATE PRODUCTO
30 SET
31 PROD_NOMBRE = :nombre,
32 ARCH_ID = :archId
33 WHERE PROD_ID = :id"
34 );
35 $stmt->execute([
36 ":id" => $modelo->id,
37 ":nombre" => $modelo->nombre,
38 ":archId" => $archivo->id
39 ]);
40 $con->commit();
41}
42