En esta lección se presenta un ejemplo que muestra los datos devueltos por un servicio.
Puedes probar el ejemplo en https://replit.com/@GilbertoPachec5/srvmuestra?v=1. Hazle fork al proyecto y córrelo.
Revisa el proyecto en Replit con la URL https://replit.com/@GilbertoPachec5/srvmuestra?v=1. Hazle fork al proyecto y córrelo. En el ambiente de desarrollo tienes la opción de descargar el proyecto en un zip.
Crea un proyecto PHP Web Server en Replit y edita o sube los archivos de este proyecto.
Haz clic en los triángulos para expandir las carpetas
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | <meta charset="UTF-8"> |
6 | <meta name="viewport" content="width=device-width"> |
7 | <title>Muestra datos</title> |
8 | </head> |
9 | |
10 | <body onload="descargaDatos()"> |
11 | |
12 | <h1>Muestra datos</h1> |
13 | |
14 | <!-- Al mostrar un dato, se usan los atributos "id", "name" o "data-name" |
15 | para buscar la propiedad a desplegar en la página. --> |
16 | |
17 | <p> |
18 | <label> |
19 | Nombre |
20 | <!-- Muestra la propiedad nombre del objeto descargado, usando la propiedad |
21 | value del output. --> |
22 | <output name="nombre"></output> |
23 | </label> |
24 | </p> |
25 | |
26 | <p> |
27 | <label> |
28 | Apellido |
29 | <!-- Muestra la propiedad apellido del objeto descargado, usando la propiedad |
30 | value del input. --> |
31 | <input name="apellido" type="text"> |
32 | </label> |
33 | </p> |
34 | |
35 | <p> |
36 | <label> |
37 | Género |
38 | <!-- Muestra la propiedad genero del objeto descargado, usando la propiedad |
39 | value del select. --> |
40 | <select name="genero"> |
41 | <option value="">Sin selección</option> |
42 | <option value="pop">Pop</option> |
43 | <option value="reg">Reguetón</option> |
44 | </select> |
45 | </label> |
46 | </p> |
47 | |
48 | <p> |
49 | <label> |
50 | Géneración |
51 | <!-- Muestra la propiedad generacion del objeto descargado, usando la |
52 | propiedad value del select. --> |
53 | <select name="generacion"> |
54 | <option value="boom">Baby Boom</option> |
55 | <option value="X">X</option> |
56 | <option value="">Sin selección</option> |
57 | <option value="Y">Millenoals</option> |
58 | <option value="Z">Z</option> |
59 | <option value="alfa">Alfa</option> |
60 | </select> |
61 | </label> |
62 | </p> |
63 | |
64 | <p> |
65 | <label> |
66 | Edad |
67 | <!-- Muestra la propiedad edad del objeto descargado, usando la propiedad |
68 | valueAsNumber del input. --> |
69 | <input data-name="edad" type="number"> |
70 | </label> |
71 | </p> |
72 | |
73 | <p> |
74 | <label> |
75 | Número de la suerte |
76 | <!-- Muestra la propiedad numero del objeto descargado, usando la propiedad |
77 | value del output. --> |
78 | <output id="numero"></output> |
79 | </label> |
80 | </p> |
81 | |
82 | <p> |
83 | <label> |
84 | Avance |
85 | <!-- Muestra la propiedad avance del objeto descargado, usando la propiedad |
86 | value del progress. --> |
87 | <progress id="avance" max="100"></progress> |
88 | </label> |
89 | </p> |
90 | |
91 | <p> |
92 | <label> |
93 | Capacidad |
94 | <!-- Muestra la propiedad capacidad del objeto descargado, usando la |
95 | propiedad value del meter. --> |
96 | <meter id="capacidad" min="50" max="80"></meter> |
97 | </label> |
98 | </p> |
99 | |
100 | <p> |
101 | <label> |
102 | Temperatura |
103 | <!-- Muestra la propiedad temperatura del objeto descargado, usando la |
104 | propiedad valueAsNumber del meter. --> |
105 | <input id="temperatura" type="range" min="0" max="50"> |
106 | </label> |
107 | </p> |
108 | |
109 | <p> |
110 | <label> |
111 | <!-- Muestra la propiedad aprobado del objeto descargado, usando la propiedad |
112 | checked del input, porque el tipo del dato es boolean y el input tiene |
113 | type="checkbox". --> |
114 | <input id="aprobado" type="checkbox"> |
115 | Aprobado |
116 | </label> |
117 | </p> |
118 | |
119 | <p> |
120 | <label> |
121 | Gracioso |
122 | <!-- Muestra la propiedad gracioso del objeto descargado, usando la propiedad |
123 | value del output. --> |
124 | <output id="gracioso"></output> |
125 | </label> |
126 | </p> |
127 | |
128 | <p> |
129 | <label> |
130 | Emplacado |
131 | <!-- Muestra la propiedad emplacado del objeto descargado, usando la |
132 | propiedad value del select. --> |
133 | <select name="emplacado"> |
134 | <option value="">Sin selección</option> |
135 | <option value="true</span>">Si</option> |
136 | <option value="false</span>">No</option> |
137 | </select> |
138 | </label> |
139 | </p> |
140 | |
141 | <label>Dirección</label> |
142 | <!-- Muestra la propiedad direccion del objeto descargado, usando la propiedad |
143 | textContent del pre. --> |
144 | <pre id="direccion"></pre> |
145 | |
146 | <p> |
147 | <label> |
148 | Encabezado |
149 | <!-- Muestra la propiedad encabezado del objeto descargado, usando la |
150 | propiedad innerHTML del span. --> |
151 | <span id="encabezado"></span> |
152 | </label> |
153 | </p> |
154 | |
155 | <p> |
156 | <label> |
157 | Nacimiento |
158 | <!-- Muestra la propiedad nacimiento del objeto descargado, usando la |
159 | propiedad value del input. Revisa la especificación de input para |
160 | los distintos formatos de fecha que se pueden usar. --> |
161 | <input id="nacimiento" type="date"> |
162 | </label> |
163 | </p> |
164 | |
165 | <figure> |
166 | <!-- Muestra la propiedad imagen1 del objeto descargado, usando la propiedad |
167 | src del img. --> |
168 | <img id="imagen1" alt="Imagen 2"> |
169 | <figcaption>Imagen 1</figcaption> |
170 | </figure> |
171 | |
172 | <figure> |
173 | <!-- Muestra la propiedad imagen2 del objeto descargado, usando la propiedad |
174 | src del img. Como el valor es "", se oculta el img usando hidden = true. --> |
175 | <img id="imagen2" alt="Imagen 2"> |
176 | <figcaption>Imagen 2</figcaption> |
177 | </figure> |
178 | |
179 | <!-- Muestra la propiedad pasatiempos[] del objeto descargado; como es un |
180 | array, usa los elementos con name="pasatiempos[]" y les pone la propiedad |
181 | checked en true si su value está en el array; de lo contrario se las pone en |
182 | false. Los corchetes([]), le indican a PHP que la propiedad es un array |
183 | que puede llegar a tener 0, 1 o más elementos. --> |
184 | <fieldset> |
185 | <legend>Pasatiempos</legend> |
186 | <label> |
187 | <input type="checkbox" name="pasatiempos[]" value="fut">Futbol |
188 | </label> |
189 | <label> |
190 | <input type="checkbox" name="pasatiempos[]" value="chess">Ajedrez |
191 | </label> |
192 | <label> |
193 | <input type="checkbox" data-name="pasatiempos[]" value="basket">Basketbol |
194 | </label> |
195 | </fieldset> |
196 | |
197 | <!-- Muestra la propiedad madrugador del objeto descargado; como es un |
198 | array, usa los elementos con name="madrugador" y les pone la propiedad |
199 | checked en true si su value está en el array; de lo contrario se las pone en |
200 | false. Como el name no tiene [], el array solo tiene 0 o un elemento. --> |
201 | <fieldset> |
202 | <legend>Madrugador</legend> |
203 | <label><input type="radio" name="madrugador" value="si">Si</label> |
204 | <label><input type="radio" name="madrugador" value="no">No</label> |
205 | </fieldset> |
206 | |
207 | <!-- Muestra la propiedad patos del objeto descargado; como es un |
208 | array, usa los las opciones del select con name="patos[]" y les pone la |
209 | propiedad selected en true si su value está en el array; de lo contrario se |
210 | las pone en false. Los corchetes([]), le indican a PHP que la propiedad es |
211 | un array que puede llegar a tener 0, 1 o más elementos. --> |
212 | <select name="patos[]" multiple size="3"> |
213 | <option value="hugo">Hugo</option> |
214 | <option value="paco">Paco</option> |
215 | <option value="luis">Luis</option> |
216 | </select> |
217 | |
218 | </body> |
219 | |
220 | <script type="module"> |
221 | |
222 | import { invocaServicio } from "./lib/js/invocaServicio.js" |
223 | import { muestraObjeto } from "./lib/js/muestraObjeto.js" |
224 | import { muestraError } from "./lib/js/muestraError.js" |
225 | |
226 | async function descargaDatos() { |
227 | try { |
228 | const respuesta = await invocaServicio("srv/datos.php") |
229 | await muestraObjeto(document, respuesta.body) |
230 | } catch (error) { |
231 | muestraError(error) |
232 | } |
233 | } |
234 | // Permite que los eventos de html usen la función. |
235 | window["descargaDatos"] = descargaDatos |
236 | |
237 | </script> |
238 | |
239 | </html> |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../lib/php/ejecutaServicio.php"; |
4 | |
5 | ejecutaServicio(function () { |
6 | return [ |
7 | "nombre" => ["value" => "pp"], |
8 | "apellido" => ["value" => "tkt"], |
9 | "genero" => ["value" => "pop"], |
10 | "generacion" => ["value" => ""], |
11 | "edad" => ["valueAsNumber" => 18], |
12 | "numero" => ["value" => 5], |
13 | "avance" => ["value" => 70], |
14 | "capacidad" => ["value" => 60], |
15 | "temperatura" => ["valueAsNumber" => 40], |
16 | "aprobado" => ["checked" => true], |
17 | "gracioso" => ["value" => false], |
18 | "emplacado" => ["value" => false], |
19 | "direccion" => ["textContent" => "Girasoles 23\ncolonia Rosales"], |
20 | "encabezado" => ["innerHTML" => "<em>Hola, soy <strong>pp</strong>"], |
21 | "nacimiento" => ["value" => "2000-07-04"], |
22 | "imagen1" => [ |
23 | "src" => "https://gilpgawoas.github.io/img/icono/maskable_icon_x48.png" |
24 | ], |
25 | "imagen2" => ["src" => ""], |
26 | "pasatiempos[]" => ["fut", "basket"], |
27 | "madrugador" => ["no"], |
28 | "patos[]" => ["paco", "luis"], |
29 | ]; |
30 | }); |
31 |
1 | import { |
2 | JsonResponse, JsonResponse_Created, JsonResponse_NoContent, JsonResponse_OK |
3 | } from "./JsonResponse.js" |
4 | import { |
5 | ProblemDetails, ProblemDetails_InternalServerError |
6 | } from "./ProblemDetails.js" |
7 | |
8 | /** |
9 | * Espera a que la promesa de un fetch termine. Si |
10 | * hay error, lanza una excepción. Si no hay error, |
11 | * interpreta la respuesta del servidor como JSON y |
12 | * la convierte en una literal de objeto. |
13 | * @param { string | Promise<Response> } servicio |
14 | */ |
15 | export async function invocaServicio(servicio) { |
16 | let f = servicio |
17 | if (typeof servicio === "string") { |
18 | f = fetch(servicio, { |
19 | headers: { "Accept": "application/json, application/problem+json" } |
20 | }) |
21 | } else if (!(f instanceof Promise)) { |
22 | throw new Error("Servicio de tipo incorrecto.") |
23 | } |
24 | const respuesta = await f |
25 | if (respuesta.ok) { |
26 | if (respuesta.status === JsonResponse_NoContent) { |
27 | return new JsonResponse(JsonResponse_NoContent) |
28 | } |
29 | const texto = await respuesta.text() |
30 | try { |
31 | const body = JSON.parse(texto) |
32 | if (respuesta.status === JsonResponse_Created) { |
33 | const location = respuesta.headers.get("location") |
34 | return new JsonResponse(JsonResponse_Created, body, |
35 | location === null ? undefined : location) |
36 | } else { |
37 | return new JsonResponse(JsonResponse_OK, body) |
38 | } |
39 | } catch (error) { |
40 | // El contenido no es JSON. Probablemente sea texto. |
41 | throw new ProblemDetails(ProblemDetails_InternalServerError, |
42 | "Problema interno en el servidor.", texto) |
43 | } |
44 | } else { |
45 | const texto = await respuesta.text() |
46 | try { |
47 | const { type, title, detail } = JSON.parse(texto) |
48 | throw new ProblemDetails(respuesta.status, |
49 | typeof title === "string" ? title : "", |
50 | typeof detail === "string" ? detail : undefined, |
51 | typeof type === "string" ? type : undefined) |
52 | } catch (error) { |
53 | if (error instanceof ProblemDetails) { |
54 | throw error |
55 | } else { |
56 | // El contenido no es JSON. Probablemente sea texto. |
57 | throw new ProblemDetails(respuesta.status, respuesta.statusText, texto) |
58 | } |
59 | } |
60 | } |
61 | } |
62 | |
63 | // Permite que los eventos de html usen la función. |
64 | window["invocaServicio"] = invocaServicio |
1 | export const JsonResponse_OK = 200 |
2 | export const JsonResponse_Created = 201 |
3 | export const JsonResponse_NoContent = 204 |
4 | |
5 | export class JsonResponse { |
6 | |
7 | /** |
8 | * @param {number} status |
9 | * @param {any} [body] |
10 | * @param {string} [location] |
11 | */ |
12 | constructor(status, body, location) { |
13 | /** @readonly */ |
14 | this.status = status |
15 | /** @readonly */ |
16 | this.body = body |
17 | /** @readonly */ |
18 | this.location = location |
19 | } |
20 | |
21 | } |
1 | import { ProblemDetails } from "./ProblemDetails.js" |
2 | |
3 | /** |
4 | * Muestra un error en la consola y en un cuadro de |
5 | * alerta el mensaje de una excepción. |
6 | * @param { ProblemDetails | Error | null } error descripción del error. |
7 | */ |
8 | export function muestraError(error) { |
9 | if (error === null) { |
10 | console.log("Error") |
11 | alert("Error") |
12 | } else if (error instanceof ProblemDetails) { |
13 | let mensaje = error.title |
14 | if (error.detail) { |
15 | mensaje += `\n\n${error.detail}` |
16 | } |
17 | mensaje += `\n\nCódigo: ${error.status}` |
18 | if (error.type) { |
19 | mensaje += ` ${error.type}` |
20 | } |
21 | console.error(mensaje) |
22 | console.error(error) |
23 | alert(mensaje) |
24 | } else { |
25 | console.error(error) |
26 | alert(error.message) |
27 | } |
28 | } |
29 | |
30 | // Permite que los eventos de html usen la función. |
31 | window["muestraError"] = muestraError |
1 | /** |
2 | * @param { Document | HTMLElement } raizHtml |
3 | * @param { any } objeto |
4 | */ |
5 | export async function muestraObjeto(raizHtml, objeto) { |
6 | for (const [nombre, definiciones] of Object.entries(objeto)) { |
7 | if (Array.isArray(definiciones)) { |
8 | muestraArray(raizHtml, nombre, definiciones) |
9 | } else if (definiciones !== undefined && definiciones !== null) { |
10 | const elementoHtml = buscaElementoHtml(raizHtml, nombre) |
11 | if (elementoHtml instanceof HTMLImageElement) { |
12 | await muestraImagen(raizHtml, elementoHtml, definiciones) |
13 | } else if (elementoHtml !== null) { |
14 | for (const [atributo, valor] of Object.entries(definiciones)) { |
15 | if (atributo in elementoHtml) { |
16 | elementoHtml[atributo] = valor |
17 | } |
18 | } |
19 | } |
20 | } |
21 | } |
22 | } |
23 | // Permite que los eventos de html usen la función. |
24 | window["muestraObjeto"] = muestraObjeto |
25 | |
26 | /** |
27 | * @param { Document | HTMLElement } raizHtml |
28 | * @param { string } nombre |
29 | */ |
30 | export function buscaElementoHtml(raizHtml, nombre) { |
31 | return raizHtml.querySelector( |
32 | `#${nombre},[name="${nombre}"],[data-name="${nombre}"]`) |
33 | } |
34 | |
35 | /** |
36 | * @param { Document | HTMLElement } raizHtml |
37 | * @param { string } propiedad |
38 | * @param {any[]} valores |
39 | */ |
40 | function muestraArray(raizHtml, propiedad, valores) { |
41 | const conjunto = new Set(valores) |
42 | const elementos = |
43 | raizHtml.querySelectorAll(`[name="${propiedad}"],[data-name="${propiedad}"]`) |
44 | if (elementos.length === 1) { |
45 | const elemento = elementos[0] |
46 | if (elemento instanceof HTMLSelectElement) { |
47 | const options = elemento.options |
48 | for (let i = 0, len = options.length; i < len; i++) { |
49 | const option = options[i] |
50 | option.selected = conjunto.has(option.value) |
51 | } |
52 | return |
53 | } |
54 | } |
55 | for (let i = 0, len = elementos.length; i < len; i++) { |
56 | const elemento = elementos[i] |
57 | if (elemento instanceof HTMLInputElement) { |
58 | elemento.checked = conjunto.has(elemento.value) |
59 | } |
60 | } |
61 | } |
62 | |
63 | /** |
64 | * @param { Document | HTMLElement } raizHtml |
65 | * @param { HTMLImageElement } img |
66 | * @param { any } definiciones |
67 | */ |
68 | async function muestraImagen(raizHtml, img, definiciones) { |
69 | const input = getInputParaElementoHtml(raizHtml, img) |
70 | const src = definiciones.src |
71 | if (src !== undefined) { |
72 | img.dataset.inicial = src |
73 | if (input === null) { |
74 | img.src = src |
75 | if (src === "") { |
76 | img.hidden = true |
77 | } else { |
78 | img.hidden = false |
79 | } |
80 | } else { |
81 | const dataUrl = await getDataUrlDeSeleccion(input) |
82 | if (dataUrl !== "") { |
83 | img.hidden = false |
84 | img.src = dataUrl |
85 | } else if (src === "") { |
86 | img.src = "" |
87 | img.hidden = true |
88 | } else { |
89 | img.src = src |
90 | img.hidden = false |
91 | } |
92 | } |
93 | } |
94 | for (const [atributo, valor] of Object.entries(definiciones)) { |
95 | if (atributo !== "src" && atributo in img) { |
96 | img[atributo] = valor |
97 | } |
98 | } |
99 | } |
100 | |
101 | /** |
102 | * @param { HTMLInputElement } input |
103 | */ |
104 | export function getArchivoSeleccionado(input) { |
105 | const seleccion = input.files |
106 | if (seleccion === null || seleccion.length === 0) { |
107 | return null |
108 | } else { |
109 | return seleccion.item(0) |
110 | } |
111 | } |
112 | // Permite que los eventos de html usen la función. |
113 | window["getArchivoSeleccionado"] = getArchivoSeleccionado |
114 | |
115 | |
116 | /** |
117 | * @param {HTMLInputElement} input |
118 | * @returns {Promise<string>} |
119 | */ |
120 | export function getDataUrlDeSeleccion(input) { |
121 | return new Promise((resolve, reject) => { |
122 | try { |
123 | const seleccion = getArchivoSeleccionado(input) |
124 | if (seleccion === null) { |
125 | resolve("") |
126 | } else { |
127 | const reader = new FileReader() |
128 | reader.onload = () => { |
129 | const dataUrl = reader.result |
130 | if (typeof dataUrl === "string") { |
131 | resolve(dataUrl) |
132 | } else { |
133 | resolve("") |
134 | } |
135 | } |
136 | reader.onerror = () => reject(reader.error) |
137 | reader.readAsDataURL(seleccion) |
138 | } |
139 | } catch (error) { |
140 | resolve(error) |
141 | } |
142 | }) |
143 | } |
144 | // Permite que los eventos de html usen la función. |
145 | window["getDataUrlDeSeleccion"] = getDataUrlDeSeleccion |
146 | |
147 | /** |
148 | * @param { Document | HTMLElement } raizHtml |
149 | * @param { HTMLElement } elementoHtml |
150 | */ |
151 | export function getInputParaElementoHtml(raizHtml, elementoHtml) { |
152 | const inputId = elementoHtml.getAttribute("data-input") |
153 | if (inputId === null) { |
154 | return null |
155 | } else { |
156 | const input = buscaElementoHtml(raizHtml, inputId) |
157 | if (input instanceof HTMLInputElement) { |
158 | return input |
159 | } else { |
160 | return null |
161 | } |
162 | } |
163 | } |
1 | export const ProblemDetails_BadRequest = 400 |
2 | export const ProblemDetails_NotFound = 404 |
3 | export const ProblemDetails_InternalServerError = 500 |
4 | |
5 | export class ProblemDetails extends Error { |
6 | |
7 | /** |
8 | * @param {number} status |
9 | * @param {string} title |
10 | * @param {string} [detail] |
11 | * @param {string} [type] |
12 | */ |
13 | constructor(status, title, detail, type) { |
14 | super(title) |
15 | /** @readonly */ |
16 | this.status = status |
17 | /** @readonly */ |
18 | this.type = type |
19 | /** @readonly */ |
20 | this.title = title |
21 | /** @readonly */ |
22 | this.detail = detail |
23 | } |
24 | |
25 | } |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/JsonResponse.php"; |
4 | require_once __DIR__ . "/ProblemDetails.php"; |
5 | |
6 | /** |
7 | * Ejecuta una funcion que implementa un servicio. |
8 | */ |
9 | function ejecutaServicio($servicio) |
10 | { |
11 | try { |
12 | $resultado = $servicio(); |
13 | if (!($resultado instanceof JsonResponse)) { |
14 | $resultado = JsonResponse::ok($resultado); |
15 | } |
16 | procesa_json_response($resultado); |
17 | } catch (ProblemDetails $details) { |
18 | procesa_problem_details($details); |
19 | } catch (Throwable $throwable) { |
20 | procesa_problem_details(new ProblemDetails( |
21 | status: ProblemDetails::InternalServerError, |
22 | type: "/error/errorinterno.html", |
23 | title: "Error interno del servidor.", |
24 | detail: $throwable->getMessage() |
25 | )); |
26 | } |
27 | } |
28 | |
29 | function procesa_json_response(JsonResponse $response) |
30 | { |
31 | $json = ""; |
32 | $body = $response->body; |
33 | if ($response->status !== JsonResponse_NoContent) { |
34 | $json = json_encode($body); |
35 | if ($json === false) { |
36 | no_puede_generar_json(); |
37 | return; |
38 | } |
39 | } |
40 | http_response_code($response->status); |
41 | if ($response->location !== null) { |
42 | header("Location: {$response->location}"); |
43 | } |
44 | if ($response->status !== JsonResponse_NoContent) { |
45 | header("Content-Type: application/json"); |
46 | echo $json; |
47 | } |
48 | } |
49 | |
50 | function procesa_problem_details(ProblemDetails $details) |
51 | { |
52 | $body = ["title" => $details->title]; |
53 | if ($details->type !== null) { |
54 | $body["type"] = $details->type; |
55 | } |
56 | if ($details->detail !== null) { |
57 | $body["detail"] = $details->detail; |
58 | } |
59 | $json = json_encode($body); |
60 | if ($json === false) { |
61 | no_puede_generar_json(); |
62 | } else { |
63 | http_response_code($details->status); |
64 | header("Content-Type: application/problem+json"); |
65 | echo $json; |
66 | } |
67 | } |
68 | |
69 | function no_puede_generar_json() |
70 | { |
71 | http_response_code(ProblemDetails::InternalServerError); |
72 | header("Content-Type: application/problem+json"); |
73 | echo '{"type":"/error/nojson.html"' |
74 | . ',"title":"El valor devuelto no puede representarse como JSON."}'; |
75 | } |
76 |
1 | <?php |
2 | |
3 | const JsonResponse_OK = 200; |
4 | const JsonResponse_Created = 201; |
5 | const JsonResponse_NoContent = 204; |
6 | |
7 | class JsonResponse |
8 | { |
9 | |
10 | public int $status; |
11 | public $body; |
12 | public ?string $location; |
13 | |
14 | public function __construct( |
15 | int $status = JsonResponse_OK, |
16 | $body = null, |
17 | ?string $location = null |
18 | ) { |
19 | $this->status = $status; |
20 | $this->body = $body; |
21 | $this->location = $location; |
22 | } |
23 | |
24 | public static function ok($body) |
25 | { |
26 | return new JsonResponse(body: $body); |
27 | } |
28 | |
29 | public static function created(string $location, $body) |
30 | { |
31 | return new JsonResponse(JsonResponse_Created, $body, $location); |
32 | } |
33 | |
34 | public static function noContent() |
35 | { |
36 | return new JsonResponse(JsonResponse_NoContent, null); |
37 | } |
38 | } |
39 |
1 | <?php |
2 | |
3 | class ProblemDetails extends Exception |
4 | { |
5 | |
6 | public const BadRequest = 400; |
7 | public const NotFound = 404; |
8 | public const InternalServerError = 500; |
9 | |
10 | public int $status; |
11 | public string $title; |
12 | public ?string $type; |
13 | public ?string $detail; |
14 | |
15 | public function __construct( |
16 | int $status, |
17 | string $title, |
18 | ?string $type = null, |
19 | ?string $detail = null, |
20 | Throwable $previous = null |
21 | ) { |
22 | parent::__construct($title, $status, $previous); |
23 | $this->status = $status; |
24 | $this->type = $type; |
25 | $this->title = $title; |
26 | $this->detail = $detail; |
27 | } |
28 | } |
29 |
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>Error interno del servidor</title> |
10 | |
11 | </head> |
12 | |
13 | <body> |
14 | |
15 | <h1>Error interno del servidor</h1> |
16 | |
17 | <p>Se presentó de forma inesperada un error interno del servidor.</p> |
18 | |
19 | </body> |
20 | |
21 | </html> |
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>El valor devuelto no puede representarse como JSON</title> |
10 | |
11 | </head> |
12 | |
13 | <body> |
14 | |
15 | <h1>El valor devuelto no puede representarse como JSON</h1> |
16 | |
17 | <p> |
18 | Debido a un error interno del servidor, la respuesta generada, no se puede |
19 | recuperar. |
20 | </p> |
21 | |
22 | </body> |
23 | |
24 | </html> |
Este archivo ayuda a detectar errores en los archivos del proyecto.
Lo utiliza principalmente Visual Studio Code.
No se explica aquí su estructura, pero puede encontrarse la explicación de todo en la documentación del sitio de Visual Studio Code.
1 | { |
2 | "compilerOptions": { |
3 | "checkJs": true, |
4 | "strictNullChecks": true, |
5 | "target": "ES6", |
6 | "module": "ES6", |
7 | "moduleResolution": "classic", |
8 | "lib": [ |
9 | "ES2017", |
10 | "WebWorker", |
11 | "DOM" |
12 | ] |
13 | } |
14 | } |
En esta lección se indica como mostrar en el cliente los datos devueltos por un servicio.