L. Carpeta « lib »

Versión para imprimir.

A. Carpeta « lib / js »

Versión para imprimir.

1. lib / js / consumeJson.js

1import { exportaAHtml } from "./exportaAHtml.js"
2import { ProblemDetails } from "./ProblemDetails.js"
3
4/**
5 * Espera a que la promesa de un fetch termine. Si
6 * hay error, lanza una excepción. Si no hay error,
7 * interpreta la respuesta del servidor como JSON y
8 * la convierte en una literal de objeto.
9 *
10 * @param { string | Promise<Response> } servicio
11 */
12export async function consumeJson(servicio) {
13
14 if (typeof servicio === "string") {
15 servicio = fetch(servicio, {
16 headers: { "Accept": "application/json, application/problem+json" }
17 })
18 } else if (!(servicio instanceof Promise)) {
19 throw new Error("Servicio de tipo incorrecto.")
20 }
21
22 const respuesta = await servicio
23
24 const headers = respuesta.headers
25
26 if (respuesta.ok) {
27 // Aparentemente el servidor tuvo éxito.
28
29 if (respuesta.status === 204) {
30 // No contiene texto de respuesta.
31
32 return { headers, body: {} }
33
34 } else {
35
36 const texto = await respuesta.text()
37
38 try {
39
40 return { headers, body: JSON.parse(texto) }
41
42 } catch (error) {
43
44 // El contenido no es JSON. Probablemente sea texto de un error.
45 throw new ProblemDetails(respuesta.status, headers, texto,
46 "/error/errorinterno.html")
47
48 }
49
50 }
51
52 } else {
53 // Hay un error.
54
55 const texto = await respuesta.text()
56
57 if (texto === "") {
58
59 // No hay texto. Se usa el texto predeterminado.
60 throw new ProblemDetails(respuesta.status, headers, respuesta.statusText)
61
62 } else {
63 // Debiera se un ProblemDetails en JSON.
64
65 try {
66
67 const { title, type, detail } = JSON.parse(texto)
68
69 throw new ProblemDetails(respuesta.status, headers,
70 typeof title === "string" ? title : respuesta.statusText,
71 typeof type === "string" ? type : undefined,
72 typeof detail === "string" ? detail : undefined)
73
74 } catch (error) {
75
76 if (error instanceof ProblemDetails) {
77 // El error si era un ProblemDetails
78
79 throw error
80
81 } else {
82
83 throw new ProblemDetails(respuesta.status, headers, respuesta.statusText,
84 undefined, texto)
85
86 }
87
88 }
89
90 }
91
92 }
93
94}
95
96exportaAHtml(consumeJson)

2. lib / js / exportaAHtml.js

1/**
2 * Permite que los eventos de html usen la función.
3 * @param {function} functionInstance
4 */
5export function exportaAHtml(functionInstance) {
6 window[nombreDeFuncionParaHtml(functionInstance)] = functionInstance
7}
8
9/**
10 * @param {function} valor
11 */
12export function nombreDeFuncionParaHtml(valor) {
13 const names = valor.name.split(/\s+/g)
14 return names[names.length - 1]
15}

3. lib / js / muestraError.js

1import { exportaAHtml } from "./exportaAHtml.js"
2import { ProblemDetails } from "./ProblemDetails.js"
3
4/**
5 * Muestra un error en la consola y en un cuadro de
6 * alerta el mensaje de una excepción.
7 * @param { ProblemDetails | Error | null } error descripción del error.
8 */
9export function muestraError(error) {
10
11 if (error === null) {
12
13 console.error("Error")
14 alert("Error")
15
16 } else if (error instanceof ProblemDetails) {
17
18 let mensaje = error.title
19 if (error.detail) {
20 mensaje += `\n\n${error.detail}`
21 }
22 mensaje += `\n\nCódigo: ${error.status}`
23 if (error.type) {
24 mensaje += ` ${error.type}`
25 }
26
27 console.error(mensaje)
28 console.error(error)
29 console.error("Headers:")
30 error.headers.forEach((valor, llave) => console.error(llave, "=", valor))
31 alert(mensaje)
32
33 } else {
34
35 console.error(error)
36 alert(error.message)
37
38 }
39
40}
41
42exportaAHtml(muestraError)

4. lib / js / muestraObjeto.js

1import { exportaAHtml } from "./exportaAHtml.js"
2
3/**
4 * @param { Document | HTMLElement } raizHtml
5 * @param { any } objeto
6 */
7export function muestraObjeto(raizHtml, objeto) {
8
9 for (const [nombre, definiciones] of Object.entries(objeto)) {
10
11 if (Array.isArray(definiciones)) {
12
13 muestraArray(raizHtml, nombre, definiciones)
14
15 } else if (definiciones !== undefined && definiciones !== null) {
16
17 const elementoHtml = buscaElementoHtml(raizHtml, nombre)
18
19 if (elementoHtml instanceof HTMLInputElement) {
20
21 muestraInput(raizHtml, elementoHtml, definiciones)
22
23 } else if (elementoHtml !== null) {
24
25 for (const [atributo, valor] of Object.entries(definiciones)) {
26 if (atributo in elementoHtml) {
27 elementoHtml[atributo] = valor
28 }
29 }
30
31 }
32
33 }
34
35 }
36
37}
38exportaAHtml(muestraObjeto)
39
40/**
41 * @param { Document | HTMLElement } raizHtml
42 * @param { string } nombre
43 */
44export function buscaElementoHtml(raizHtml, nombre) {
45 return raizHtml.querySelector(
46 `#${nombre},[name="${nombre}"],[data-name="${nombre}"]`)
47}
48
49/**
50 * @param { Document | HTMLElement } raizHtml
51 * @param { string } propiedad
52 * @param {any[]} valores
53 */
54function muestraArray(raizHtml, propiedad, valores) {
55
56 const conjunto = new Set(valores)
57 const elementos =
58 raizHtml.querySelectorAll(`[name="${propiedad}"],[data-name="${propiedad}"]`)
59
60 if (elementos.length === 1) {
61 const elemento = elementos[0]
62
63 if (elemento instanceof HTMLSelectElement) {
64 const options = elemento.options
65 for (let i = 0, len = options.length; i < len; i++) {
66 const option = options[i]
67 option.selected = conjunto.has(option.value)
68 }
69 return
70 }
71
72 }
73
74 for (let i = 0, len = elementos.length; i < len; i++) {
75 const elemento = elementos[i]
76 if (elemento instanceof HTMLInputElement) {
77 elemento.checked = conjunto.has(elemento.value)
78 }
79 }
80
81}
82
83/**
84 * @param { Document | HTMLElement } raizHtml
85 * @param { HTMLInputElement } input
86 * @param { any } definiciones
87 */
88function muestraInput(raizHtml, input, definiciones) {
89
90 for (const [atributo, valor] of Object.entries(definiciones)) {
91
92 if (atributo == "data-file") {
93
94 const img = getImgParaElementoHtml(raizHtml, input)
95 if (img !== null) {
96 input.dataset.file = valor
97 input.value = ""
98 if (valor === "") {
99 img.src = ""
100 img.hidden = true
101 } else {
102 img.src = valor
103 img.hidden = false
104 }
105 }
106
107 } else if (atributo in input) {
108
109 input[atributo] = valor
110
111 }
112 }
113
114}
115
116/**
117 * @param { Document | HTMLElement } raizHtml
118 * @param { HTMLElement } elementoHtml
119 */
120export function getImgParaElementoHtml(raizHtml, elementoHtml) {
121 const imgId = elementoHtml.getAttribute("data-img")
122 if (imgId === null) {
123 return null
124 } else {
125 const input = buscaElementoHtml(raizHtml, imgId)
126 if (input instanceof HTMLImageElement) {
127 return input
128 } else {
129 return null
130 }
131 }
132}

5. lib / js / ProblemDetails.js

1/**
2 * Detalle de los errores devueltos por un servicio.
3 */
4export class ProblemDetails extends Error {
5
6 /**
7 * @param {number} status
8 * @param {Headers} headers
9 * @param {string} title
10 * @param {string} [type]
11 * @param {string} [detail]
12 */
13 constructor(status, headers, title, type, detail) {
14 super(title)
15 /**
16 * @readonly
17 */
18 this.status = status
19 /**
20 * @readonly
21 */
22 this.headers = headers
23 /**
24 * @readonly
25 */
26 this.type = type
27 /**
28 * @readonly
29 */
30 this.detail = detail
31 /**
32 * @readonly
33 */
34 this.title = title
35 }
36
37}

6. lib / js / submitForm.js

1import { consumeJson } from "./consumeJson.js"
2import { exportaAHtml } from "./exportaAHtml.js"
3
4/**
5 * Envía los datos de la forma a la url usando la codificación
6 * multipart/form-data.
7 * @param {string} url
8 * @param {Event} event
9 * @param { "GET" | "POST"| "PUT" | "PATCH" | "DELETE" | "TRACE" | "OPTIONS"
10 * | "CONNECT" | "HEAD" } metodoHttp
11 */
12export function submitForm(url, event, metodoHttp = "POST") {
13
14 event.preventDefault()
15
16 const form = event.target
17
18 if (!(form instanceof HTMLFormElement))
19 throw new Error("event.target no es un elemento de tipo form.")
20
21 return consumeJson(fetch(url, {
22 method: metodoHttp,
23 headers: { "Accept": "application/json, application/problem+json" },
24 body: new FormData(form)
25 }))
26
27}
28
29exportaAHtml(submitForm)

B. Carpeta « lib / php »

Versión para imprimir.

1. lib / php / BAD_REQUEST.php

1<?php
2
3const BAD_REQUEST = 400;
4

2. lib / php / calculaArregloDeParametros.php

1<?php
2
3function calculaArregloDeParametros(array $arreglo)
4{
5 $parametros = [];
6 foreach ($arreglo as $llave => $valor) {
7 $parametros[":$llave"] = $valor;
8 }
9 return $parametros;
10}
11

3. lib / php / calculaSqlDeAsignaciones.php

1<?php
2
3function calculaSqlDeAsignaciones(string $separador, array $arreglo)
4{
5 $primerElemento = true;
6 $sqlDeAsignacion = "";
7 foreach ($arreglo as $llave => $valor) {
8 $sqlDeAsignacion .=
9 ($primerElemento === true ? "" : $separador) . "$llave=:$llave";
10 $primerElemento = false;
11 }
12 return $sqlDeAsignacion;
13}
14

4. lib / php / calculaSqlDeCamposDeInsert.php

1<?php
2
3function calculaSqlDeCamposDeInsert(array $values)
4{
5 $primerCampo = true;
6 $sqlDeCampos = "";
7 foreach ($values as $nombreDeValue => $valorDeValue) {
8 $sqlDeCampos .= ($primerCampo === true ? "" : ",") . "$nombreDeValue";
9 $primerCampo = false;
10 }
11 return $sqlDeCampos;
12}
13

5. lib / php / calculaSqlDeValues.php

1<?php
2
3function calculaSqlDeValues(array $values)
4{
5 $primerValue = true;
6 $sqlDeValues = "";
7 foreach ($values as $nombreDeValue => $valorDeValue) {
8 $sqlDeValues .= ($primerValue === true ? "" : ",") . ":$nombreDeValue";
9 $primerValue = false;
10 }
11 return $sqlDeValues;
12}
13

6. lib / php / delete.php

1<?php
2
3require_once __DIR__ . "/calculaArregloDeParametros.php";
4require_once __DIR__ . "/calculaSqlDeAsignaciones.php";
5
6function delete(PDO $pdo, string $from, array $where)
7{
8 $sql = "DELETE FROM $from";
9
10 if (sizeof($where) === 0) {
11 $pdo->exec($sql);
12 } else {
13 $sqlDeWhere = calculaSqlDeAsignaciones(" AND ", $where);
14 $sql .= " WHERE $sqlDeWhere";
15
16 $statement = $pdo->prepare($sql);
17 $parametros = calculaArregloDeParametros($where);
18 $statement->execute($parametros);
19 }
20}
21

7. lib / php / devuelveCreated.php

1<?php
2
3require_once __DIR__ . "/devuelveResultadoNoJson.php";
4
5function devuelveCreated($urlDelNuevo, $resultado)
6{
7
8 $json = json_encode($resultado);
9
10 if ($json === false) {
11
12 devuelveResultadoNoJson();
13 } else {
14
15 http_response_code(201);
16 header("Location: {$urlDelNuevo}");
17 header("Content-Type: application/json");
18 echo $json;
19 }
20}
21

8. lib / php / devuelveErrorInterno.php

1<?php
2
3require_once __DIR__ . "/INTERNAL_SERVER_ERROR.php";
4require_once __DIR__ . "/devuelveProblemDetails.php";
5require_once __DIR__ . "/devuelveProblemDetails.php";
6
7function devuelveErrorInterno(Throwable $error)
8{
9 devuelveProblemDetails(new ProblemDetails(
10 status: INTERNAL_SERVER_ERROR,
11 title: $error->getMessage(),
12 type: "/error/errorinterno.html"
13 ));
14}
15

9. lib / php / devuelveJson.php

1<?php
2
3require_once __DIR__ . "/devuelveResultadoNoJson.php";
4
5function devuelveJson($resultado)
6{
7
8 $json = json_encode($resultado);
9
10 if ($json === false) {
11
12 devuelveResultadoNoJson();
13 } else {
14
15 http_response_code(200);
16 header("Content-Type: application/json");
17 echo $json;
18 }
19}
20

10. lib / php / devuelveNoContent.php

1<?php
2
3function devuelveNoContent()
4{
5 http_response_code(204);
6}
7

11. lib / php / devuelveProblemDetails.php

1<?php
2
3require_once __DIR__ . "/devuelveResultadoNoJson.php";
4require_once __DIR__ . "/ProblemDetails.php";
5
6function devuelveProblemDetails(ProblemDetails $details)
7{
8
9 $body = ["title" => $details->title];
10 if ($details->type !== null) {
11 $body["type"] = $details->type;
12 }
13 if ($details->detail !== null) {
14 $body["detail"] = $details->detail;
15 }
16
17 $json = json_encode($body);
18
19 if ($json === false) {
20
21 devuelveResultadoNoJson();
22 } else {
23
24 http_response_code($details->status);
25 header("Content-Type: application/problem+json");
26 echo $json;
27 }
28}
29

12. lib / php / devuelveResultadoNoJson.php

1<?php
2
3require_once __DIR__ . "/INTERNAL_SERVER_ERROR.php";
4
5function devuelveResultadoNoJson()
6{
7
8 http_response_code(INTERNAL_SERVER_ERROR);
9 header("Content-Type: application/problem+json");
10 echo '{' .
11 '"title": "El resultado no puede representarse como JSON."' .
12 '"type": "/error/resultadonojson.html"' .
13 '}';
14}
15

13. lib / php / ejecutaServicio.php

1<?php
2
3require_once __DIR__ . "/ProblemDetails.php";
4require_once __DIR__ . "/devuelveProblemDetails.php";
5require_once __DIR__ . "/devuelveErrorInterno.php";
6
7function ejecutaServicio(callable $codigo)
8{
9 try {
10 $codigo();
11 } catch (ProblemDetails $details) {
12 devuelveProblemDetails($details);
13 } catch (Throwable $error) {
14 devuelveErrorInterno($error);
15 }
16}
17

14. lib / php / fetch.php

1<?php
2
3function fetch(
4 PDOStatement|false $statement,
5 $parametros = [],
6 int $mode = PDO::FETCH_ASSOC,
7 $opcional = null
8) {
9
10 if ($statement === false) {
11
12 return false;
13 } else {
14
15 if (sizeof($parametros) > 0) {
16 $statement->execute($parametros);
17 }
18
19 if ($opcional === null) {
20 return $statement->fetch($mode);
21 } else {
22 $statement->setFetchMode($mode, $opcional);
23 return $statement->fetch();
24 }
25 }
26}
27

15. lib / php / fetchAll.php

1<?php
2
3function fetchAll(
4 PDOStatement|false $statement,
5 $parametros = [],
6 int $mode = PDO::FETCH_ASSOC,
7 $opcional = null
8): array {
9
10 if ($statement === false) {
11
12 return [];
13 } else {
14
15 if (sizeof($parametros) > 0) {
16 $statement->execute($parametros);
17 }
18
19 $resultado = $opcional === null
20 ? $statement->fetchAll($mode)
21 : $statement->fetchAll($mode, $opcional);
22
23 if ($resultado === false) {
24 return [];
25 } else {
26 return $resultado;
27 }
28 }
29}
30

16. lib / php / insert.php

1<?php
2
3require_once __DIR__ . "/calculaSqlDeCamposDeInsert.php";
4require_once __DIR__ . "/calculaSqlDeValues.php";
5require_once __DIR__ . "/calculaArregloDeParametros.php";
6
7function insert(PDO $pdo, string $into, array $values)
8{
9 $sqlDeCampos = calculaSqlDeCamposDeInsert($values);
10 $sqlDeValues = calculaSqlDeValues($values);
11 $sql = "INSERT INTO $into ($sqlDeCampos) VALUES ($sqlDeValues)";
12 $parametros = calculaArregloDeParametros($values);
13 $pdo->prepare($sql)->execute($parametros);
14}
15

17. lib / php / INTERNAL_SERVER_ERROR.php

1<?php
2
3const INTERNAL_SERVER_ERROR = 500;

18. lib / php / NOT_FOUND.php

1<?php
2
3const NOT_FOUND = 404;
4

19. lib / php / ProblemDetails.php

1<?php
2
3/** Detalle de los errores devueltos por un servicio. */
4class ProblemDetails extends Exception
5{
6
7 public int $status;
8 public string $title;
9 public ?string $type;
10 public ?string $detail;
11
12 public function __construct(
13 int $status,
14 string $title,
15 ?string $type = null,
16 ?string $detail = null,
17 Throwable $previous = null
18 ) {
19 parent::__construct($title, $status, $previous);
20 $this->status = $status;
21 $this->type = $type;
22 $this->title = $title;
23 $this->detail = $detail;
24 }
25}
26

20. lib / php / recuperaArray.php

1<?php
2
3/**
4 * Recupera los valores asociados a un
5 * parámetro multivaluado; por ejemplo, un
6 * grupo de checkbox, recibido en el servidor
7 * por medio de GET, POST o cookie. Si no se
8 * recibe el parámetro, devuelve []. Si el
9 * valor recibido no es un arreglo, lo coloca
10 * dentro de uno.
11 */
12function recuperaArray(string $parametro)
13{
14 if (isset($_REQUEST[$parametro])) {
15 $valor = $_REQUEST[$parametro];
16 return is_array($valor)
17 ? $valor
18 : [$valor];
19 } else {
20 return [];
21 }
22}
23

21. lib / php / recuperaDecimal.php

1<?php
2
3require_once __DIR__ . "/recuperaTexto.php";
4
5/**
6 * Recupera el valor decimal de un parámetro (que
7 * puede tener fracciones) enviado al servidor por
8 * medio de GET, POST o cookie.
9 *
10 * Si el parámetro no se recibe, devuekve false
11 *
12 * Si se recibe una cadena vacía, se devuelve null.
13 *
14 * Si parámetro no se puede convertir a entero,
15 * devuelve 0.
16 */
17function recuperaDecimal(string $parametro): false|null|float
18{
19 $valor = recuperaTexto($parametro);
20 if ($valor === false) {
21 return false;
22 } elseif ($valor === "") {
23 return null;
24 } else {
25 return (float) trim($valor);
26 }
27 return $valor === null|| $valor === ""
28 ? null
29 : trim($valor);
30}
31

22. lib / php / recuperaEntero.php

1<?php
2
3require_once __DIR__ . "/recuperaTexto.php";
4
5/**
6 * Devuelve el valor entero de un parámetro recibido en el
7 * servidor por medio de GET, POST o cookie.
8 *
9 * Si el parámetro no se recibe, devuekve false
10 *
11 * Si se recibe una cadena vacía, se devuelve null.
12 *
13 * Si parámetro no se puede convertir a entero, se genera
14 * un error.
15 */
16function recuperaEntero(string $parametro): false|null|int
17{
18 $valor = recuperaTexto($parametro);
19 if ($valor === false) {
20 return false;
21 } elseif ($valor === "") {
22 return null;
23 } else {
24 return (int) trim($valor);
25 }
26}
27

23. lib / php / recuperaIdEntero.php

1<?php
2
3require_once __DIR__ . "/BAD_REQUEST.php";
4require_once __DIR__ . "/recuperaEntero.php";
5require_once __DIR__ . "/ProblemDetails.php";
6
7function recuperaIdEntero(string $parametro): int
8{
9
10 $id = recuperaEntero($parametro);
11
12 if ($id === false)
13 throw new ProblemDetails(
14 status: BAD_REQUEST,
15 title: "Falta el id.",
16 type: "/error/faltaid.html",
17 detail: "La solicitud no tiene el valor de id.",
18 );
19
20 if ($id === null)
21 throw new ProblemDetails(
22 status: BAD_REQUEST,
23 title: "Id en blanco.",
24 type: "/error/idenblanco.html",
25 );
26
27 return $id;
28}
29

24. lib / php / recuperaTexto.php

1<?php
2
3/**
4 * Recupera el texto de un parámetro enviado al
5 * servidor por medio de GET, POST o cookie.
6 *
7 * Si el parámetro no se recibe, devuelve false.
8 */
9function recuperaTexto(string $parametro): false|string
10{
11 /* Si el parámetro está asignado en $_REQUEST,
12 * devuelve su valor; de lo contrario, devuelve false.
13 */
14 $valor = isset($_REQUEST[$parametro])
15 ? $_REQUEST[$parametro]
16 : false;
17 return $valor;
18}
19

25. lib / php / select.php

1<?php
2
3require_once __DIR__ . "/fetchAll.php";
4require_once __DIR__ . "/calculaSqlDeAsignaciones.php";
5
6function select(
7 PDO $pdo,
8 string $from,
9 array $where = [],
10 string $orderBy = "",
11 int $mode = PDO::FETCH_ASSOC,
12 $opcional = null
13) {
14 $sql = "SELECT * FROM $from";
15
16 if (sizeof($where) > 0) {
17 $sqlDeWhere = calculaSqlDeAsignaciones(" AND ", $where);
18 $sql .= " WHERE $sqlDeWhere";
19 }
20
21 if ($orderBy !== "") {
22 $sql .= " ORDER BY $orderBy";
23 }
24
25 if (sizeof($where) === 0) {
26 $statement = $pdo->query($sql);
27 return fetchAll($statement, [], $mode, $opcional);
28 } else {
29 $statement = $pdo->prepare($sql);
30 $parametros = calculaArregloDeParametros($where);
31 return fetchAll($statement, $parametros, $mode, $opcional);
32 }
33}
34

26. lib / php / selectFirst.php

1<?php
2
3require_once __DIR__ . "/fetch.php";
4require_once __DIR__ . "/calculaArregloDeParametros.php";
5require_once __DIR__ . "/calculaSqlDeAsignaciones.php";
6
7function selectFirst(
8 PDO $pdo,
9 string $from,
10 array $where = [],
11 string $orderBy = "",
12 int $mode = PDO::FETCH_ASSOC,
13 $opcional = null
14) {
15 $sql = "SELECT * FROM $from";
16
17 if (sizeof($where) > 0) {
18 $sqlDeWhere = calculaSqlDeAsignaciones(" AND ", $where);
19 $sql .= " WHERE $sqlDeWhere";
20 }
21
22 if ($orderBy !== "") {
23 $sql .= " ORDER BY $orderBy";
24 }
25
26 if (sizeof($where) === 0) {
27 $statement = $pdo->query($sql);
28 return fetch($statement, [], $mode, $opcional);
29 } else {
30 $statement = $pdo->prepare($sql);
31 $parametros = calculaArregloDeParametros($where);
32 return fetch($statement, $parametros, $mode, $opcional);
33 }
34}
35

27. lib / php / update.php

1<?php
2
3require_once __DIR__ . "/calculaArregloDeParametros.php";
4require_once __DIR__ . "/calculaSqlDeAsignaciones.php";
5
6
7function update(PDO $pdo, string $table, array $set, array $where)
8{
9 $sqlDeSet = calculaSqlDeAsignaciones(",", $set);
10 $sqlDeWhere = calculaSqlDeAsignaciones(" AND ", $where);
11 $sql = "UPDATE $table SET $sqlDeSet WHERE $sqlDeWhere";
12
13 $parametros = calculaArregloDeParametros($set);
14 foreach ($where as $nombreDeWhere => $valorDeWhere) {
15 $parametros[":$nombreDeWhere"] = $valorDeWhere;
16 }
17 $statement = $pdo->prepare($sql);
18 $statement->execute($parametros);
19}
20