APUNTES Laravel 10 v7
APUNTES Laravel 10 v7
APUNTES Laravel 10 v7
- Instalación de Laravel 10
Antes de comenzar hay que aclarar que para que funcione correctamente Laravel 10.0 es necesario que
tengamos instalado PHP v. 8.1 o superior.
Antes de nada, vamos a ver cómo podemos descargar y configurar el denominado “Esqueleto (estructura)
de Laravel 10” (basado en el FrameWork) en nuestro equipo.
https://git-scm.com
2- El siguiente paso sería tener instalado “Composer1”. Esto nos permitirá instalar todas las dependencias
necesarias para configurar nuestro proyecto. Para descargarlo, vamos a la página de composer y
buscamos la descarga para Windows (Windows Installer):
https://getcomposer.org/download/
La forma más sencilla y la que usaremos es mediante la opción “b”. Vamos a abrir el “cmd” y a situarnos
en la carpeta xampp/htdocs/, una vez ahí, vamos a escribir dicha línea de código2, sustituyendo la parte
nombre_proyecto por el nombre de la carpeta donde queramos crear el árbol de directorios. Para el
desarrollo de este manual, crearemos una aplicación que nos permita gestionar un foro, con lo cual vamos
a llamarle “forum”.
4- La carpeta “forum”, podríamos aprovecharla desde este momento como plantilla para todos los proyectos
que vamos a realizar a partir de ahora, evitándonos así volver a realizar todos los pasos anteriores.
1
En el caso de que Composer ya lo tuviésemos instalado, habría que actualizarlo (si acabamos de instalarlo en este
momento no es necesario):
2
Para poder ejecutar estos comandos es necesario incluir el Path de PHP en las variables de entorno de Windows.
Si has instalado XAMPP no es necesario que lo hagas.
-1-
2.- Configuración inicial de Laravel 10
Todos los ficheros de configuración de Laravel se encuentran dentro de la carpeta “config”. Antes de
continuar hay varios aspectos que podemos configurar en Laravel:
Por defecto, el NameSpace de nuestra aplicación es “App”, pero es posible modificarlo mediante el
comando “app:name” de Artisan. Para ejecutar el comando debes situarte dentro de la carpeta donde
creaste el proyecto “forum”).
NOTA: Puedes observar, antes y después de ejecutar el comando, en el archivo siguiente como cambia la
ruta del namespace:
Aplicación/app/Http/Controllers/Controller.php
2- Modo depuración
Es posible configurar el modo de depuración para nuestra aplicación como True o False. Esto se puede
configurar mediante la variable “APP_DEBUG” que se encuentra en el archivo “.env”
En versiones anteriores de Laravel era posible ver directamente los mensajes de Error (observa en las
siguientes figuras los mensajes que se muestran en caso de error en función del valor de dicha variable).
-2-
A partir de la versión 5.8, esto ya no es posible, debiendo recurrir a la instalación de paquetes para la
visualización de dichos errores. Existen 2 opciones para poder hacer esto:
Para ello tendremos que ejecutar el siguiente comando desde dentro de nuestro proyecto:
y modificar el código del método “render” del archivo “App\Exceptions\Handler.php” por este otro:
(Esta opción la veremos en el Punto 14 del manual “Optimización de Consultas con Eloquent”)
-3-
3- Archivo “config/app.php”
- timezone: es posible establecer la zona horaria de nuestra aplicación modificando la que aparece por
defecto “UTC”. Para ello, podemos visitar el siguiente enlace donde encontraremos las distintas zonas
horarias del mundo (la nuestra en concreto es “Europe/Madrid”):
http://php.net/manual/es/timezones.php
- locale: con este valor podemos modificar la localización (por defecto “en” – Inglés). Podemos
establecerlo a “es”. Además de modificar esta variable es necesario realizar otros cambios. Observa
la estructura de carpetas “resources/lang/en/”. Cada uno de los tres archivos contiene líneas de
lenguaje que establecen descripciones de mensajes en el idioma especificado (inglés). Para poder
cambiar la localización es necesario crear una nueva carpeta con el lenguaje seleccionado p.e.
“resources/lang/es/” y crear los tres archivos con sus contenidos escritos en dicho lenguaje. Puede
ayudarte para el contenido de los archivos el siguiente enlace (están dentro de la carpeta
“resources/lang/es”):
https://github.com/Laraveles/spanish/tree/master/resources/lang/es
NOTA: También se puede cambiar el idioma en tiempo de ejecución utilizando el método setLocale
de App. Este cambio no es permanente, en la siguiente ejecución se utilizará el valor de configuración
por defecto:
App::setLocale('es');
Para eliminar la palabra “public3” de las URL sigue los siguientes pasos:
3
NOTA: Para dar más seguridad a la aplicación una vez que esté en producción se suele recomendar renombrar la
carpeta “public” a otro nombre, modificando el valor tanto en la función anterior como en las dos apariciones que
tiene dicha carpeta en el archivo “index.php” del directorio raíz.
-4-
3.- MVC: Modelo-Vista-Controlador
1- CONTOLADOR:
2- MODELO:
El Modelo se encarga de la interacción de la aplicación con los datos, de tal manera que recibe peticiones
de datos, por parte del Controlador, y realiza las operaciones sobre la fuente de datos (BD) a la que esté
conectada. Además, ejecuta las Reglas de Negocio.
3- VISTA:
La Vista se encarga de interactuar con el usuario, de tal manera que recoge las acciones que el usuario
quiere ejecutar y devuelve los resultados que ha devuelto la aplicación.
-5-
Fig 2. Funcionamiento del MVC
-6-
4.- Rutas
Cuando se trabaja con recursos web, es necesario especificar una ruta de acceso a dichos recursos, ya sea
un archivo, una página, etc.
Como vimos en el apartado anterior una ruta es, básicamente, una petición por parte del usuario. Éste
hace peticiones a través del navegador, las cuales son enrutadas indicando al controlador dicha petición y el
Controlador procesa la petición decidiendo si es necesario llamar a un Modelo, porque se soliciten datos de una
base de datos (el Modelo le retornará dichos datos al Controlador para generar la Vista), o generar directamente
una Vista que le mostrará al usuario lo que ha solicitado.
Como podemos ver ya existen rutas establecidas y podremos crear nuevas rutas haciendo referencia a la
clase “Route” indicándole el verbo HTTP que vamos a usar (get, post, delete, put, …) y como argumentos le vamos
a indicar en primer lugar la “Ruta” que habrá que poner en el navegador y en segundo lugar, para este ejemplo,
crearemos una función que retornará un mensaje al usuario.
Route::get('/', function () {
return view('welcome');
});
-7-
En Laravel existen otros tipos de rutas además de las anteriores (básicas), como son las rutas con
parámetros (opcionales u opcionales con valores por defecto):
En este caso mediante llaves se indica el nombre del parámetro que vamos a recibir mediante la url
Route::get('prueba/{nombre}', function($nombre){
return "Hola " . $nombre;
});
Algo parecido sería para crear rutas básicas con valores por defecto. Para ello crearíamos la ruta poniendo
la interrogación detrás del parámetro e igualando el parámetro de la función al valor por defecto:
-8-
5.- Controladores
Los Controladores son los encargados de responder a las peticiones de los usuarios. En función de la
petición el Controlador invocará a un Modelo (si necesita acceder a datos de un origen de datos) o directamente
a la Vista para responder a la petición del usuario.
Como ejemplo vamos a crear un nuevo Controlador, para ello iremos a la carpeta de Controladores,
crearemos un nuevo fichero que llamaremos “PruebaController.php” y escribiremos el siguiente código
<?php
namespace Forum\Http\Controllers;
use App\Http\Controllers\PruebaController;
…
Route::controller(PruebaController::class)->group(function () {
Route::get('prueba2', 'index');
});
Route::controller(PruebaController::class)->group(function () {
Route::get('prueba3/{name}', 'nombre');
});
-9-
Todo esto se volvería incontrolable si tuviésemos diez Controladores y cada uno de ellos con 8 métodos.
Para ello existen los Controladores RESTful.
Para declarar un Controlador RESTful iríamos al archivo “web.php” y escribiríamos la siguiente línea de
código:
use App\Http\Controllers\PruebaController;
…
Route::resource('forum','PruebaController::class');
Pero mejor lo vamos a hacer mediante “Artisan”, que es la línea de comandos creada para trabajar con
Laravel desde PHP. Para ello nos iremos a la línea de comandos (en la carpeta del proyecto) y escribiremos lo
siguiente:
Esta declaración va a generar múltiples rutas para cada método del Controlador (index, create, store,
show, edit, update y destroy).
Si vamos ahora a nuestra carpeta de Controladores podemos ver como se ha creado nuestro Controlador
y tiene todos sus métodos ya definidos. Ahora ya podemos cambiar algunos métodos para probarlo. Por ejemplo
vamos a modificar el método index() y ponemos lo siguiente:
“localhost/forum/public/forum”
NOTA: Observa cómo, si no añadimos nada más en la ruta, se ejecuta el método “index”.
- 10 -
6.- Modelos (Eloquent ORM)
Un modelo no es otra cosa que la representación de los datos con los que la aplicación va a trabajar. Es
por tanto quien gestiona todos los accesos a dichos datos. Básicamente, el Modelo es la representación de una
tabla de nuestra Base de Datos.
Para generar un Modelo podemos hacerlo mediante código, directamente sobre un archivo php, o
podemos hacerlo mediante el siguiente comando Artisan:
NOTA: Para hacerlo de una forma más completa, vamos a hacerlo de la siguiente forma:
Con esto, además de crear el Modelo, estaremos creando una Migración, la Factoría y un Controlador
(aunque todos estos objetos podemos crearlo, de forma independiente, mediante Artisan).
Podemos ver cómo ha creado nuestro Modelo “Forum” (en la carpeta “app”), la Migración (en la carpeta
“database\migrations”), la Factoría (en “database\factories”) y el Controlador (en “app\Http\Controllers”).
NOTA: Si creamos antes el Controlador de forma unitaria no funcionarán después las factorías.
Eloquent se basa en lo que se denomina CodeFirst que consiste en crear las clases y sus relaciones y
olvidarse de la creación de la Base de Datos, aunque existe la posibilidad de trabajar con Bases de Datos existentes.
Una vez creado el Modelo debemos ir al código que ha generado y crearemos la línea desde donde
haremos referencia a nuestra Tabla. Para ello vamos a escribir:
<?php
namespace Forum;
use Illuminate\Database\Eloquent\Model;
- 11 -
7.- Migraciones
Una migración podríamos definirla como un tipo de control de versiones de nuestra Base de Datos,
permitiendo a un equipo modificar el esquema de la misma y estar al día en dichas modificaciones.
Antes de avanzar observa que, en el apartado anterior, cuando creamos el Modelo denominado “Forum”,
automáticamente se creó la migración para el mismo dentro de la ruta “database/migrations”.
Para crearlo mediante código tendremos que ver que columnas compondrán nuestra tabla y de qué tipo
son. Para ello iremos a la migración que se ha creado de nuestro modelo y modificaremos la parte del código que
sirve para crear el Esquema añadiendo aquellos campos que sean necesarios.
Los tipos de datos que podemos usar para crear los campos pueden verse en la documentación de Laravel:
https://laravel.com/docs/10.x/migrations#creating-columns
para permitir escribir Relaciones y Claves Foráneas vamos a modificar la línea 60 del archivo
“config/database.php”:
Para continuar debemos configurar el fichero “.env” con los valores de acceso de nuestra base de datos
(host, base_de_datos, usuario y contraseña) y debemos crear la base de datos (desde PHPMyAdmin).
- 12 -
Una vez hecho esto debemos ejecutar el comando Artisan para migrar la BD:
Esto creará (o modificará) las tablas con los parámetros establecidos en el fichero anterior.
También es posible realizar “RollBack” de la base de datos si hemos cometido algún error, para ello
escribimos:
NOTA: En el siguiente enlace se explica cómo hacer una migración de una base de datos existente:
https://styde.net/como-generar-migraciones-de-una-base-de-datos-existente/
- 13 -
8.- Factorías
Para poder poblar nuestras tablas vamos a utilizar los denominados “Factories” que se almacenan en la
carpeta “database”. En el apartado 6, con el comando “php artisan make:model Forum –mcf” ya creamos nuestra
factoría “ForumFactory.php”. Para poder utilizarla vamos a modificar el contenido de la misma, utilizando el Helper
“fake” que accede a la librería “FakerPHP”:
https://fakerphp.github.io/formatters/
factory(App\Forum::class, 50)->create();
- 14 -
2- Mediante el archivo “DatabaseSeeder.php” que se encuentra en la carpeta “database\seeds”,
modificando el código del método “run” como sigue:
Una vez hecho esto, hay que ejecutar el Seeder mediante Artisan:
Como podemos ver ha eliminado (Rollback) y vuelto a crear las tablas y ha creado 100 registros con datos
aleatorios
- 15 -
9.- Autenticación de usuarios
Laravel nos da la facilidad de crear, desde cero, un sistema de autenticación de usuarios sin tener que
escribir código.
Para ello sólo tenemos que acceder a la línea de comandos y ejecutar las siguientes instrucciones:
Con esto dispondremos de un sistema de autenticación completo, sin tener que hacer nada más. Como
podemos ver, en la parte superior derecha de la ventana nos aparecen ya los botones de “LOGIN” y “REGISTER”,
los cuales están ya totalmente validados.
Podemos ver también como en la carpeta de Vistas se ha creado una nueva carpeta llamada “auth” y
dentro de ella varias Vistas. Además, se ha creado otro Layout llamado “app.blade.php” que define la nueva barra
de navegación y permite incluir en él un contenido “content” que será el que incorpore las distintas Vistas que
hereden de este Layout.
- 16 -
10.- Vistas
Las “Vistas” son los archivos que contienen código Html que sirve nuestra aplicación y permiten separar
los controladores y la lógica de la aplicación de la lógica de presentación. Estos van a mostrar la información a los
usuarios finales. Además, van a permitir que los usuarios interactúen con la aplicación mediante el uso de
formularios.
Antes de crear las vistas, lo primero que debemos hacer es crear una ruta (que representa una petición
de un usuario), que en nuestro caso hará referencia a nuestro Controlador “ForumController”. Para crear la ruta
iremos al archivo “routes/web.php”, podemos ver como ya existe la siguiente ruta:
Route::get('/', function () {
return view('welcome');
});
NOTA: A partir de Laravel 5.8 es posible especificar una ruta de forma algo más abreviada, por ejemplo:
Route::view('/', 'welcome');
En nuestro ejemplo, para poder mostrar todos los foros en la ventana de inicio, lo que haremos será
modificar la ruta de más arriba (la que usa la función anónima) con el siguiente código:
Con esta ruta le estamos diciendo que debe llamar al método “index” del Controlador “ForumController”.
Por tanto, lo siguiente que debemos hacer es ir al Controlador y crear dicho método. El contenido del Controlador
debe ser el siguiente:
<?php
namespace App\Http\Controllers;
use App\Forum;
- 17 -
Con esto le estamos diciendo a Laravel que recupere todos los datos de los foros mediante el Modelo
“Forum”. Para poder ver dichos datos de una forma rápida podríamos usar la función “dd()” de la siguiente forma:
$forums = Forum::all();
dd($forums);
Si vamos al navegador y mostramos la página de inicio podemos ver cómo nos dice que tenemos una
colección de 100 registros devueltos.
Estos datos podríamos hacer que estuvieran ordenados y paginados de una forma muy sencilla,
simplemente tendríamos que modificar la línea donde accedemos al Modelo y escribir lo siguiente:
$forums = Forum::latest()->paginate(5);
Esto devolvería los registros ordenados desde el último que fue creado y paginados de 5 en 5. Veremos a
continuación que, además de esto, Laravel nos permite crear, de una forma muy sencilla los enlaces para poder
movernos por las distintas páginas (para ello sólo tendremos que llamar al método “links()”).
Para pasar dicha información a una Vista, en lugar de verlo con la función “dd()”, lo que haremos será
llamar al Helper “view” para devolver los datos a la vista compactándolos mediante la función “compact()”:
Con esto le estamos diciendo a Laravel que mande los datos compactados a la Vista “index.blade.php”
que crearemos dentro de una carpeta que llamaremos “forums” para tener las Vistas más ordenadas.
- 18 -
11.- Plantillas (Blade)
Blade es un motor de plantillas que ayuda a reducir las líneas de código que necesitamos para nuestra
aplicación. La documentación oficial podemos encontrarla en:
https://laravel.com/docs/10/blade
Lo primero que debemos tener en cuenta es que todas las plantillas deben tener la extensión “blade.php”.
Para definir una plantilla Blade se apoya en “secciones” y “herencia” (extends).
En la documentación podemos ver que primero hay que definir la estructura de una plantilla y luego la
usaremos desde otros archivos.
También podemos hacer una especie de “echo” de los datos si lo incluimos entre llaves: {{ $dato }}
En la carpeta “views” tenemos una carpeta denominada “layouts” y dentro de esta tenemos una plantilla
que se llama “app.blade.php” desde la que heredarán el resto de vistas (aunque podemos crear nuevas plantillas
y que hereden de estas otras).
El código de nuestra nueva Vista “index” que crearemos dentro de la nueva carpeta llamada “forums”
será el siguiente:
@extends('layouts.app')
@section('content')
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h1 class="text-center text-mute"> {{ __("Foros") }} </h1>
@forelse($forums as $forum)
<div class="panel panel-default">
<div class="panel-heading">
<a href="forums/{{ $forum->id }}"> {{ $forum->name }} </a>
</div>
<div class="panel-body">
{{ $forum->description }}
</div>
</div>
@empty
<div class="alert alert-danger">
{{ __("No hay ningún foro en este momento") }}
</div>
@endforelse
</div>
</div>
@endsection
NOTA: Las líneas del estilo “{{ __("Foros") }}” son traducciones JSON disponibles a partir de Laravel 5.4 y
que nos van a permitir traducir nuestras aplicaciones de una forma muy sencilla (lo veremos más adelante).
- 19 -
Para que nos quede un poco mejor, vamos a modificar la línea de la Plantilla “layout/app.blade.php”
donde creamos la herencia de la sección “content” de la siguiente forma:
<div class="container">
@yield('content')
</div>
Como podemos ver en el resultado, sólo nos está mostrando los 5 primeros registros. Para solucionar
esto, como dijimos anteriormente, llamaremos al método “links()” que nos mostrará los enlaces de paginación.
Simplemente tendremos que escribir el siguiente código después de la línea “@endforelse” de nuestra vista:
@endforelse
@if($forums->count())
{{ $forums->links() }}
@endif
</div>
</div>
@endsection
Para que funcione en Laravel 10, hay que hacer varios cambios.
2. En la lista que aparece tenemos que buscar en qué línea aparece “laravel-pagination”, y escribir el número
de dicha línea y pulsar Intro para que lo publique.
use Illuminate\Pagination\Paginator;
- 20 -
12.- Creación de los Posts de los Foros
Como sabemos, un foro está definido por muchos posts y cada post será creado por un usuario. Para crear
esta parte del programa vamos a desarrollar nuestra Migración, la Factoría, el Controlador y el Modelo para dichos
Posts.
Lo primero que haremos será modificar nuestro archivo de migración de la siguiente forma:
Con esas dos columnas que hemos creado (user_id y forum_id), estamos especificando quien será el
creador del “Post” y a que “Foro” pertenece.
Para poder poblar la nueva tabla vamos a modificar la nueva Factoría “PostFactory” como sigue:
Con la primera línea le estamos diciendo que coja un Usuario aleatoriamente, pero que exista en la tabla
Users y con la primera, lo mismo, pero de la tabla Forums.
- 21 -
Para poder usar la Factoría modificaremos el archivo “DatabaseSeeder.php” como sigue:
Una vez hecho esto, hay que ejecutar el Seeder mediante Artisan:
Para mostrar los datos creados de una forma sencilla, vamos a nuestro archivo “web.php” y vamos a crear
una nueva ruta:
use App\Http\Controllers\ForumController;
Route::resource('forums', ForumController::class);
public function show(Forum $forum) // Con esto estamos inyectando el Foro completo
{
dd($forum);
}
NOTA: Si quisiéramos crear un usuario concreto para que se creara cada vez que reiniciemos la base de
datos, podríamos poner, en la función “run()” la siguiente línea de código:
- 22 -
13.- Relaciones entre Modelos y carga dinámica
Para definir las relaciones entre el Modelo “User” y el Modelo “Forum” vamos a modificar el fichero
“Forum.php” de la siguiente forma:
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
protected $fillable = [
'name', 'description',
];
Con la función “posts”, le estamos diciendo a Laravel que un Foro puede tener muchos Posts. Ahora
tendremos que hacer la relación inversa (aunque aquí no la vamos a usar pero es posible que lo necesitemos en
alguna ocasión), para ello iremos al Modelo “Post.php” y lo modificaremos de la siguiente forma:
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
protected $fillable = [
'forum_id', 'user_id', 'title', 'description',
];
- 23 -
En este caso le estamos diciendo que el Post pertenece a un Foro y a un Usuario concreto (propietario).
NOTA: Cuando usamos “protected $hidden” en un Modelo, los campos incluidos dejarán de mostrarse
sólo si los estamos enviando en forma de Array, por ejemplo “$user->toArray()”.
Ahora tendremos que crear en el Modelo “user.php” la relación que existe entre un usuario y los post (un
usuario puede tener muchos posts):
Ahora tendremos que modificar la función “show” del Controlador “ForumController” para que muestre
dichos posts. Para ello cambiaremos la línea que hay dentro de “show” por la siguiente:
$posts = $forum->posts()->with(['owner'])->paginate(2);
Lo que le estamos diciendo es que llame al método “posts()” del Modelo “Forum” y mediante “with” le
decimos cuáles son las relaciones que queremos que use. Al ser un array podríamos indicar varias.
Si mostrásemos los Posts “dd($posts)” podríamos ver que ahora, además de mostrarnos los datos de los
Posts, nos mostraría los datos del “propietario” de dichos posts (owner).
Lo que haremos en lugar de eso será devolver dichos datos a una vista que llamaremos “detail” de la
siguiente forma:
@extends('layouts.app')
@section('content')
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h1 class="text-center text-mute"> {{ __("Posts") }} </h1>
@forelse($posts as $post)
- 24 -
<div class="panel-body">
{{ $post->description }}
</div>
</div>
@empty
<div class="alert alert-danger">
{{ __("No hay ningún post en este momento") }}
</div>
@endforelse
@if($posts->count())
{{ $posts->links() }}
@endif
</div>
</div>
@endsection
Podríamos mejorar la vista “index.blade.php” para que muestre, de cada Foro, cuántos Posts tienen. Para
ello añadimos el siguiente código a la vista:
- 25 -
14.- Optimización de Consultas con Eloquent
Para poder optimizar las consultas vamos a utilizar una herramienta que nos permitirá hacer debug tanto
de las consultas, como de las rutas, de las vistas, de las excepciones, de los emails, de las redirecciones y de los
requests en Laravel. Esta herramienta es “Laravel Debugbar” y está disponible en la siguiente dirección:
https://github.com/barryvdh/laravel-debugbar
NOTA : Para que funcione tiene que estar a “True” la variable “APP_DEBUG” del archivo “.env”.
Con esto nos aparecería la barra del Debugbar en la parte inferior del navegador
Y podríamos ver, por ejemplo que se están ejecutando 4 consultas en la página actual, que podríamos
verlas si pulsamos sobre el botón “Queries 4”
- 26 -
Si volvemos a la página de Foros y vemos las consultas que se están ejecutando, podemos ver que se está
ejecutando una consulta por cada uno de los Foros que estamos paginando simultáneamente.
Esto significa que si paginamos 10 foros por página nos haría 10 consultas para mostrarlos, lo cual no es
eficiente (puedes comprobarlo modificando el archivo “ForumController”).
Esto ocurre porque estamos añadiendo a la información de cada Foro el número de Post que contienen:
Puedes comprobar cómo, si quitamos dicha línea del archivo “index.blade.php”, no se ejecutarían dichas
consultas.
Para solucionar dicho problema, iremos al archivo “ForumController” y sustituiremos la línea siguiente:
$forums = Forum::latest()->paginate(5);
$forums = Forum::with(['posts'])->paginate(5);
Si volvemos a actualizar el navegador, podemos ver como ahora sólo ejecuta 1 consulta para mostrar
todos los foros:
Como puedes ver, lo que ha hecho es incluir todos los “forum_id” en una cláusula “in”. Esto lo
conseguimos, automáticamente, con el uso del método “with”.
- 27 -
15.- Desarrollando las respuestas de los Foros
Después de los Foros y los Posts, tenemos que desarrollar las Respuestas de los Posts. Para crear esta
parte del programa vamos a desarrollar nuestra Migración, la Factoría, el Controlador y el Modelo para dichas
Respuestas.
Lo primero que haremos será modificar nuestro archivo de migración de la siguiente forma:
Con esas dos columnas que hemos creado, estamos especificando quien será el creador de la “Respuesta”
y a que “Post” pertenece.
Para poder poblar la nueva tabla vamos a modificar la nueva Factoría “ReplyFactory” como sigue:
- 28 -
Para poder usar la Factoría modificaremos el archivo “DatabaseSeeder.php” como sigue:
Una vez hecho esto, hay que ejecutar el Seeder mediante Artisan:
Lo primero que vamos a hacer es, en la ventana donde mostramos los Foros, además de mostrar cuántos
Posts hay para cada Foro, vamos a indicar cuántas Respuestas hay. Para ello debemos acceder desde el Modelo
“Forum” al Modelo “Reply” a través del Modelo “Post”.
protected $fillable = [
'user_id', 'post_id', 'reply',
];
// Para poder acceder al Foro desde esta tabla crearemos un atributo extra
protected $appends = ['forum'];
- 29 -
Desde aquí parece fácil, porque podemos acceder al Post y desde el Modelo “Post” se puede acceder a su
Foro (mediante la función “forum”), pero realmente, si nos fijamos en el Modelo “Forum”, ¿cómo lo hacemos?,
¿accedemos a todos los Post y accedemos a un Foro cualquiera? o ¿tenemos que recorrer todos los Posts para
coger el Foro al que pertenece cada uno de ellos?
Para resolver esto, existe una solución muy sencilla que es definir en el Modelo “Forum” una función que
llamaremos “replies” la cuál indicará que tenemos una relación “a distancia”, especificando cuál será la Clase que
estará más lejos (“Reply”) y a través de qué clase vamos a enlazar con ella (“Post”):
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
…
public function posts(){
return $this->hasMany(Post::class);
}
Para mostrar dichos datos, vamos a modificar la Vista “index.blade.php” añadiendo lo siguiente:
$forums = Forum::with(['posts'])->paginate(5);
- 30 -
16.- Mostrando las Respuestas
Lo siguiente que haremos es ir al Modelo “Post” y definir la relación que tiene un Post con las posibles
Respuestas que puede tener. Para ello añadimos lo siguiente para indicar que un post puede tener muchas
respuestas:
use Illuminate\Database\Eloquent\Relations\HasMany;
…
public function owner(): BelongsTo {
return $this->belongsTo(User::class, 'user_id');
}
Ahora vamos a abrir el fichero “web.php” para crear la ruta que nos mostrará las Respuestas para un Post
concreto:
use App\Http\Controllers\PostController;
…
Route::resource('posts', PostController::class);
Lo siguiente que haremos es modificar el Controlador “PostController” para crear el método “show” al
que estamos llamando:
use App\Post;
- 31 -
Ahora tendremos que crear la Vista “posts/detail.blade.php” con el siguiente código:
@extends('layouts.app')
@section('content')
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h1 class="text-center text-mute"> {{ __("Respuestas al debate :name", ['name' => $post-
>title]) }} </h1>
@forelse($replies as $reply)
<div class="panel-body">
{{ $reply->reply }}
</div>
</div>
@empty
@endforelse
@if($replies->count())
{{ $replies->links() }}
@endif
</div>
</div>
@endsection
- 32 -
Para poder diferenciar un poco las distintas páginas (Foros, Posts y Respuestas) vamos a aplicar algunos
estilos a los headers de las Vistas. Primero vamos a ir a la siguiente URL https://www.cssportal.com/css-to-scss/
que nos permitirá transformar un archivo “.css” a uno “scss” (compilado) que es el que entiende Laravel.
.panel-default>.panel-heading-forum {
color: #fff;
background: #F4645F;
border-color: #ddd;
}
.panel-heading-forum a{
color: #fff;
text-decoration: underline;
}
.panel-default>.panel-heading-post {
color: #fff;
background: #1f648b;
border-color: #ddd;
}
.panel-heading-post a{
color: #fff;
text-decoration: underline;
}
.panel-default>.panel-heading-reply {
color: #fff;
background: #2F3133;
border-color: #111;
}
.panel-heading-reply a{
color: #fff;
text-decoration: underline;
}
Le damos al botón “Convert” y copiamos el código que ha generado. A continuación vamos a crear un
nuevo archivo en la carpeta “resources/sass” que llamaremos “styles.scss” y pegaremos el contenido copiado
anteriormente.
NOTA: Observa que al guardar los cambios se crean otros dos archivos “style.css” y “style.css.map”.
Ahora tenemos que añadir, en el layout principal “layouts/app.blade.php”, dicha hoja de estilos:
- 33 -
Y ahora tendremos que ir modificando tolas las vistas. Empezamos por “forums/index.blade.php”:
Vamos ahora a mostrar información adicional en la Vista de “Respuestas”. Para ello mostraremos quién
es el autor de la respuesta y añadiremos un enlace para volver a la Vista de Posts. Para ello vamos al archivo
“posts/detail.blade.php” y añadimos:
<div class="clearfix"></div>
<br/>
Y, por último, vamos a hacer lo mismo en la Vista de “Posts” para indicar a qué Foro pertenecen. Para ello
editamos el fichero “forums/detail.blade.php” y vamos a modificar la siguiente línea:
<div class="clearfix"></div>
<br/>
- 34 -
17.- Dar de Alta un Foro nuevo
Lo primero que vamos a hacer es modificar el número de foros que vamos a paginar cada vez (en lugar de
5 como está establecido vamos a paginar sólo 2 foros). Para ello vamos al Controlador “ForumController.php” y
modificamos la siguiente línea:
En la pantalla principal de los Foros, lo que vamos a hacer es, después de la lista de foros paginados, vamos
a mostrar un formulario que nos permita añadir nuevos Foros. Para hacer esto vamos a modificar el archivo
“forums/index.blade.php” añadiendo el siguiente código que muestra un formulario:
@if($forums->count())
{{ $forums->links() }}
@endif
</div>
</div>
- 35 -
Y ahora tendremos que crear el método “store()” en el Controlador “ForumController”:
Para mostrar la sesión flash que le estamos mandando, tendremos que ir al archivo “app.blade.php” y
añadir lo siguiente:
<div class="container">
@if(session('message'))
<div class="alert alert-{{ session('message')[0] }}">
{{ session('message')[1] }}
</div>
@endif
@yield('content')
</div>
- 36 -
- 37 -
18.- Validación de los Formularios
Para validar los datos de los formularios vamos a utilizar el método “validate()” de los Controladores, el
cual acepta como primer parámetro un “request” y como segundo un array de reglas de validación. Para ello, en
el método “store()” del Controlador “ForumController” vamos a añadir el siguiente código:
$this->validate(request(), [
'name' => 'required|max:100|unique:forums', // forums es la tabla dónde debe ser único
'description' => 'required|max:500',
]);
Forum::create(request()->all());
NOTA: Podemos ver todas las reglas de validación en la siguiente URL de la documentación de Laravel:
https://laravel.com/docs/10.x/validation#available-validation-rules
Para mostrar los posibles errores que puedan ocurrir en la validación vamos a crear una nueva Vista (en
una nueva carpeta dentro de resources/views) llamada “partials/errors.blade.php” donde comprobaremos si
existe algún error y crearemos una lista con los posibles errores. El código será el siguiente:
@if($errors->any())
<div class="alert alert-danger">
<ul>
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
La variable “errors” que estamos comprobando vendrá del archivo donde está el formulario
“forums/index.blade.php”, por eso será en este archivo donde tendremos que incluir el archivo creado
anteriormente “partials/errors.blade.php”. El código necesario es el siguiente:
@if($forums->count())
{{ $forums->links() }}
@endif
<hr />
@include('partials.errors')
- 38 -
Como podemos ver, si ahora enviamos el formulario vacío, nos mostrará los mensajes de error que han
ocurrido, pero si nos fijamos lo muestra en inglés:
- 39 -
Observa que, ni nombre como descripción aparecen directamente traducidos en el archivo
“es/validation.php”, así, tendríamos que ir al final del archivo y añadir tanto el campo como la traducción al idioma.
Si quisiéramos poner un mensaje personalizado para el error, distinto a “El campo nombre es obligatorio”
tendríamos que ir al Controlador y poner el siguiente código para el campo y error específicos:
$this->validate(request(), [
'name' => 'required|max:100|unique:forums', // forums es la tabla dónde debe ser único
'description' => 'required|max:500',
],
[
'name.required' => __("El campo NAME es requerido!!!")
]
);
- 40 -
19.- Usuarios autenticados
Aunque Laravel posee su propio módulo de Autenticación, para este ejercicio nosotros vamos a ver cómo
manejar esto mediante los “Providers”. En el archivo “app/Providers/AppServiceProvider.php” es dónde vamos a
registrar las directivas nuevas para “Blade” (ejemplos de directivas en Blade son @if..@endif,
@forelse…@endforelse, …).
Nosotros vamos a crear una directiva “if” específica para saber si un usuario está logueado. Dicha directiva
se llamará “Logged” y la crearemos en la función “register” de dicho archivo añadiendo el siguiente código:
Para usar la nueva directiva iremos a la Vista donde mostramos los detalles de los foros
“views/forums/detail.blade.php” y añadiremos el siguiente código:
@if($posts->count())
{{ $posts-links() }}
@endif
@Logged()
Está identificado
@else
@include('partials.login_link', ['message' => __("Inicia sesión para crear un post")])
@endLogged
NOTA: La línea del “@include” sirve para, si no está logueado, crear un enlace a un nuevo archivo llamado
“login_link.blade.php” que crearemos en la carpeta “partials” al cual pasaremos un mensaje.
<div class="text-center">
<a href="../login">{{ $message }}</a>
</div>
- 41 -
Para probarlo tendremos que registrar un usuario y entrar en los Posts que tiene un Foro cualquiera
después de haber iniciado sesión con el usuario y hacerlo sin estar logueado.
NOTA: Para que no nos lleve a la Vista “home” cuando nos registramos o iniciamos sesión hay que:
- Eliminar del archivo “web.php” la ruta que redirecciona a “/home”
- Modificar la constante HOME del archivo “App/Providers/RouteServiceProviders” que está
inicializada a '/home' por '/'
Lo siguiente que tenemos que hacer es crear el formulario que nos permitirá crear nuevos Posts para el
Foro en el que nos encontremos.
- 42 -
20.- Formulario para crear nuevos Posts
Lo primero que vamos a hacer es cambiar el mensaje del archivo “views/forums/detail.blade.php” que
dice “Está identificado” por otro más personalizado e incluir el archivo de errores por si ocurre alguno mostrar el
mensaje:
@Logged()
<h3 class="text-muted">{{ __("Añadir un nuevo post al foro :name", ['name' =>
$forum->name]) }}</h3>
@include('partials.errors')
@else
Para darle un poco de espacio al mensaje con la parte inferior de la ventana, vamos a ir al archivo
“app.blade.php” y vamos a modificar el siguiente código:
Vamos a seguir modificando el archivo “detail.blade.php” para incluir el nuevo formulario que nos
permitirá crear los nuevos Posts:
- 43 -
@include('partials.errors')
<div class="form-group">
<label for="title" class="col-md-12 control-label">{{ __("Título") }}</label>
<input id="title" class="form-control" name="title" value="{{ old('title') }}"/>
</div>
<div class="form-group">
<label for="description" class="col-md-12 control-label">{{ __("Descripción")
}}</label>
<textarea id="description" class="form-control"
name="description">{{ old('description') }}</textarea>
</div>
@else
- 44 -
Y a continuación lo que haremos será crear el método “store()” dentro del archivo “PostController.php”
que se encargará de guardar el nuevo Post creado en la base de datos.
En este caso vamos a usar un nuevo método de validación de formularios utilizando lo que se llaman los
FormRequests. De esta forma, el Controlador deja de tener la responsabilidad de validar los datos del formulario
pasando esta al FormRequest.
Esto creará un archive llamado “PostRequest.php” dentro de la ruta “Http\Requests”. Lo primero que
tendremos que cambiar del archivo es el método “authorize()” para que sólo lo puedan ejecutar los usuarios
autenticados. Con “auth()->check()” lo que hacemos es que si el usuario está autenticado devolverá “true” y se
ejecutará el Request, y si no lo está, devolverá “false” y no se ejecutará el Request:
Lo siguiente que haremos es modificar el método “rules()” para especificar las reglas de validación:
NOTA: Es posible usar otro método llamado “messages()” que nos permite devolver un array de mensajes
cuando existan errores de validación en el formulario (igual que hicimos cuando validamos en el método “store()”
del Controlador “ForumController”).
Para usar el FormRequest iremos al archivo “PostController.php” y codificaremos el método “store()” del
mismo de la siguiente forma:
use App\Http\Requests\PostRequest;
- 45 -
Tan sólo con estas líneas podemos ver como si enviamos el formulario se validará y mostrará los errores
que existan en el formulario.
Ahora sólo nos queda modificar el contenido del método “store()” para que inserte los datos del nuevo
Post en la base de datos y nos muestre un mensaje informando que se ha realizado correctamente:
Si enviamos los datos del nuevo Post, nos dará un error diciendo que el campo “user_id” no tiene ningún
valor:
- 46 -
Para solucionar este problema podemos hacerlo de varias formas:
1. Insertando en el PostRequest un nuevo par de clave-valor con el id del usuario que ha iniciado sesión
2. Otra forma más elegante de hacerlo es usando el método “boot()” dentro del Modelo “Post”. Este método
nos sirve para codificar los Eventos del Modelo, es decir, que haremos cuando se está creando, cuando
se ha creado y cuando se está borrando
protected $fillable = [
'forum_id', 'user_id', 'title', 'description',
];
static::creating(function($post) {
$post->user_id = auth()->id();
});
}
- 47 -
Todo esto sólo tiene un problema, que si vamos a la línea de comandos y ejecutamos lo siguiente:
se ejecutará también el método “boot()” y nos mostrará un error diciéndonos que la variable “user_id”
no puede ser Null, ya que no hemos iniciado sesión desde la línea de comandos:
Para solucionar este error, simplemente tendremos que indicar en el método “boot()” que haga eso si no
se está ejecutando desde la línea de comandos:
use Illuminate\Support\Facades\App;
static::creating(function($post) {
if( ! App::runningInConsole() ) {
$post->user_id = auth()->id();
}
});
- 48 -
21.- Formulario para crear nuevas Respuestas
(Mejorando las Respuestas)
Lo siguiente que vamos a hacer es crear, en la vista de respuestas de los Posts, un formulario para que,
los usuarios logueados, puedan crear nuevas Respuestas.
Lo primero que haremos es, en el archivo “posts/detail.blade.php” usaremos la nueva directiva “Logged”
que creamos anteriormente:
@Logged()
<h3 class="text-muted">{{ __("Añadir una nueva respuesta al post :name", ['name' => $post-
>name]) }}</h3>
@include('partials.errors')
<div class="form-group">
<label for="reply" class="col-md-12 control-label">{{ __("Respuesta") }}</label>
<textarea id="reply" class="form-control" name="reply">{{ old('reply') }}</textarea>
</div>
Podemos ver cómo, en caso de no estar logueado, nos muestra el mensaje de iniciar la sesión:
- 49 -
Pero si hemos iniciado sesión, nos mostraría el formulario para añadir una respuesta:
Igual que anteriormente, lo siguiente será definir la ruta a la que nos lleva el formulario:
Route::resource('replies', ReplyController::class);
Antes de crear el método “store()” en el Controlador, vamos a ver cómo usar un nuevo método de
Validación (disponible desde Laravel 5.8) que son las “Rules”. Para crear una nueva “Rule” tendremos que ir a la
línea de comandos y escribir:
Esto creará una nueva carpeta y dentro una Clase llamada “app/Rules/ValidReply.php”. Una vez creada,
modificaremos el método “passes( )” con el siguiente código:
- 50 -
Y posteriormente, tendremos que ir al Controlador “ReplyController” y escribir el código para el método
“store()” que utilice la validación que acabamos de crear:
use App\Rules\ValidReply;
Como podemos ver, el mensaje es un poco extraño (La reply debe tener…). Para poder modificar dicho
mensaje iremos al archivo “resources/lang/es/validation.php” y, en el array “atributes”, añadiremos un nuevo par
“clave-valor” que será:
- 51 -
Si enviamos de nuevo el formulario podemos ver que, ahora sí, el mensaje es correcto:
use App\Rules\ValidReply;
use App\Reply;
Reply::create(request()->input());
- 52 -
Si lo ejecutamos veremos que nos dará un mensaje de error, indicando que no existe el “user_id”. Para
solucionarlo podemos copiar el código del método “boot()” desde el Modelo “Post.php” y pegarlo modificando
“$post” por “$reply”:
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\App;
…
protected $appends = ['forum'];
static::creating(function($reply) {
if( ! App::runningInConsole() ) {
$reply->user_id = auth()->id();
}
});
}
Podemos ver como si ahora creamos una nueva respuesta nos mostrará el mensaje de que se ha creado
la respuesta correctamente:
- 53 -
22.- Urls amigables
Lo siguiente que vamos a hacer es modificar el código, para que, al acceder a un foro, post o respuesta
concreta, en lugar de hacerlo mediante su código (p.e. “/forums/1”) lo hagamos mediante “slugs”, haciendo las
URLs más amigables. Para ello tendremos que modificar, en primer lugar, las Migraciones para añadir a las tablas
un nuevo campo que llamaremos “slug”. Vamos a modificar primero el archivo de las migraciones de los Posts
añadiendo el siguiente código:
Observa que lo que hacemos es usar el método “table” del “Schema” para crear nuevos campos. Esto se
hace así cuando queremos mantener los datos anteriores que puedan existir en la Base de Datos creando un nuevo
archivo de migraciones con el contenido actual y el nuevo método “table”. En nuestro caso, como los datos no son
reales vamos a incluir el nuevo campo dentro del método “create” y volver a realizar la migración. Como vamos a
hacer búsquedas por este campo, también le diremos que se trata de un índice de la tabla:
- 54 -
Ahora tendríamos que hacer lo mismo en la migración de los Foros:
Lo siguiente será modificar las Factorías, tanto de los Foros como de los Posts. Empezaremos por la de los
Foros:
- 55 -
En los modelos de Posts y de Foros tendremos que decirles que hay un nuevo campo que se llama “slug”.
protected $fillable = [
'forum_id', 'user_id', 'title', 'description', 'slug',
];
protected $fillable = [
'name', 'description', 'slug',
];
Podemos ver en la Base de Datos como en las tablas de Posts y de Foros ha creado un nuevo campo
llamado “slug”. Por último tendremos que decirle a Laravel que la clave para las rutas de los Posts y de los Foros
no será el “id”, sino el “slug”. Para hacer esto, en cada uno de los modelos definiremos la función
“getRouteKeyName()”.
- 56 -
Si ahora volvemos a la aplicación e intentamos entrar en un foro, nos dará el error porque seguirá
intentando entrar mediante el “id” del Foro. Para solucionar este problema tendremos que cambiar, en todas las
vistas donde hagamos referencia al campo “id” y referenciar al campo “slug”:
Ahora sí, si volvemos a probar la aplicación entrando en los Foros y volviendo hacia atrás veremos que
funcionarán nuestras nuevas rutas “amigables”:
Hay que modificar el Modelo “Forum” para que no de error al crear uno nuevo:
use Illuminate\Support\Facades\App;
class Forum extends Model
{
…
protected static function boot() {
parent::boot();
static::creating(function($forum) {
if( ! App::runningInConsole() ) {
$forum->slug = str_slug($forum->name , "-");
}
});
}
- 57 -
Tenemos que hacer lo mismo en el Modelo Post para que no de error al crear el slug:
static::creating(function($post) {
if(!App::runningInConsole()){
$post->user_id = auth()->id();
$post->slug = str_slug($post->title,'-');
}
});
}
- 58 -
27.- Eliminar Posts, Respuestas y archivos adjuntos
Vamos a crear ahora la funcionalidad que nos va a permitir eliminar Posts, Respuestas y los archivos
(imágenes) adjuntos que pueda tener, pero controlando que un usuario sólo pueda eliminar los que sean de su
propiedad.
Lo primero que tendremos que hacer entonces, antes de mostrar el botón de eliminar, es comprobar si el
Post pertenece al usuario que ha iniciado sesión. Para ello vamos a crear un nuevo método en el Modelo “Post”
que llamaremos “isOwner()” con el siguiente código:
Esto nos devolverá “True” o “False” indicando si el propietario del Post es el mismo que el usuario que ha
iniciado sesión.
<div class="panel-body">
{{ $post->description }}
@if($post->attachment)
<img src="{{ $post->pathAttachment() }}" class="img-responsive img-
rounded" />
@endif
</div>
@if($post->isOwner())
<div class="panel-footer">
<form method="POST" action="../posts/{{ $post->slug }}">
// Es necesario enmascarar el método Delete en Laravel
{{ method_field('DELETE') }}
{{ csrf_field() }}
<button type="submit" name="deletePost" class="btn btn-danger">
{{ __("Eliminar post") }}
</button>
</form>
</div>
@endif
- 59 -
Como podemos ver, si nosotros somos los autores del Post, nos aparece el botón para eliminar dicho Post:
Lo siguiente será crear la nueva ruta que nos permita llamar al método del Controlador:
Route::post('/posts', 'PostController@store');
Route::delete('/posts/{post}', 'PostController@destroy');
Ahora tendremos que crear el método “destroy()” dentro del Controlador “PostController”:
$post->delete();
return back()->with('message', ['success', __('Post y respuestas eliminados
correctamente')]);
}
- 60 -
Pero este código sólo eliminaría el Post si no tiene Respuestas asociadas. Para que elimine el Post y las
Respuestas asociadas, podríamos hacerlo desde el mismo Controlador, pero es mejor dejarlo más “limpio” y
hacerlo desde el Modelo “Post”. Para ello vamos a modificar la función “boot()” añadiendo un método “static” con
el siguiente código:
use Illuminate\Support\Facades\Storage;
…
static::deleting(function($post) {
if( ! App()->runningInConsole() ) {
if($post->replies()->count()) {
foreach($post->replies as $reply) {
if($reply->attachment) {
Storage::delete('replies/' . $reply->attachment);
}
}
$post->replies()->delete();
}
if($post->attachment) {
Storage::delete('posts/' . $post->attachment);
}
}
});
- 61 -
28.- Eliminar Respuestas y adjuntos si somos Autores
Vamos a hacer lo mismo que hemos hecho con los Posts pero ahora con las Respuestas. Para ello vamos
a ir al fichero “posts/detail.blade.php” y vamos a añadir un botón para borrar pero comprobando antes si el usuario
que está logueado es el autor de dicha Respuesta. Para saber si es el autor, primero iremos al Modelo “Reply.php”
y crearemos un nuevo método:
Una vez hecho esto, iremos a la vista “detail” y justo debajo del “panel-body” vamos a escribir el siguiente
código:
<div class="panel-body">
{{ $reply->reply }}
@if($post->attachment)
<img src="{{ $reply->pathAttachment() }}" class="img-responsive img-rounded" />
@endif
</div>
@if($reply->isAuthor())
<div class="panel-footer">
<form method="POST" action="{{ route('replies.delete', [$reply->id]) }}">
{{ method_field('DELETE') }}
{{ csrf_field() }}
<button type="submit" name="deleteReply" class="btn btn-danger">
{{ __("Eliminar respuesta") }}
</button>
</form>
</div>
@endif
Nota que en esta ocasión, al llamar al “action” del formulario estamos llamando a una ruta
“replies.delete” enviándole un parámetro “$reply->id”. Para declarar dicha ruta iremos al archivo “web.php” y
añadiremos el siguiente código:
Route::post('/replies', 'ReplyController@store');
Route::delete('/replies/{reply}', 'ReplyController@destroy')->name('replies.delete');
- 62 -
Observa como ahora si nos aparece el botón de “Eliminar Respuesta” en las respuestas que somos
propietarios:
Nos queda aún crear el método “destroy()” dentro del Controlador “ReplyController”:
Y en el Modelo “Reply” tenemos que crear también el Evento “deleting()” justo debajo del Evento
“creating()”:
use Illuminate\Support\Facades\Storage;
…
static::deleting(function($reply) {
if( ! App::runningInConsole() ) {
if($reply->attachment) {
Storage::delete('replies/' . $reply->attachment);
}
}
});
Si pulsamos el botón eliminar podemos ver como elimina la Respuesta mostrándonos el mensaje
“Respuesta eliminada correctamente”.
- 63 -
29.- Base de Datos de la Aplicación Forum
- 64 -