Open INESEM
Investigación
Yaaseen Oman: desarrollo y puesta en producción de una aplicación web
Introducción
El presente proyecto trata sobre el desarrollo y la puesta en producción de una aplicación web hecha en Symfony con Bootstrap para abarcar las necesidades particulares de una microempresa del Sultanato de Omán.
Este proyecto surge tras haber experimentado la carencia de aplicaciones web profesionales entre pequeños empresarios en dicho país. Muchos de estos empresarios y artesanos tienen difusión en las redes sociales, pero no disponen de una aplicación web para promover sus creaciones o productos artesanales; en pocos casos tienen una página web estática pero de aspecto y funcionalidad rudimentario, sin posibilidad de modificación de sus contenidos por cuenta propia. Dado que los costos, tanto a respeto de actualización como de manutención, de un content management system (CMS) tradicional pueden ser considerables para una microempresa en su fase inicial, es el desempeño de este proyecto crear una aplicación web que funcione como un CMS, utilizando solamente recursos y componentes gratuitos, y de acceso libre.
Específicamente, el presente proyecto trata de construir un CMS simple hecho en Symfony, cuyo enfoque principal es mostrar cómo utilizar Twig para gestionar su contenido y mostrarlo en su apartado público de la misma aplicación.
Marco teórico
Como el proyecto consiste en desarrollar una aplicación web, cuya funcionalidad es la de un CMS, y que está hecha a medida para acomodar las necesidades particulares de un cliente, los primeros pasos son:
- Definir exactamente que es una aplicación web.
- Definir a que se refiere con content management system (CMS).
- Identificar las necesidades particulares de la cliente.
Primero, al usar el término aplicación web se refiere a que, deja al usuario interactuar con el servidor, y guardar información en el servidor. (Web Application, s.f)
En el caso del presente proyecto esa interacción ocurre de dos formas:
- El usuario anónimo puede interactuar con la aplicación al enviar un correo al servidor usando el formulario de contacto.
- El usuario con acceso al apartado Admin puede subir, borrar, actualizar y ver contenido de la base de datos que se presenta en su aplicación web.
Eso nos lleva al segundo punto, la definición de un Content Management System (CMS). Un CMS es esencialmente una aplicación web que permite usuarios administrativos de subir y actualizar su propio material sin tener que lidiar con el código o recurrir a la asistencia de un desarrollador web (Morris, 2022).
Dado que la necesidades particulares del cliente haya sido desarrollar una aplicación web que podría ser auto gestionable, pero sin encajarse en la rigidez de un CMS tradicional que suele tener complementos o plugins de cobro, se ha inclinado hacía usar el framework de Symfony.
Se ha optado por implementar el proyecto con la Standard Edition de Symfony, que tiene todos los componentes requeridos en un proyecto de Symfony totalmente funcional.
Otra razón por usar Symfony, es que funciona perfectamente con el patrón Model – View – Controller, (MVC), una estructura de arquitectura diseño web que separa lo que es el modelo, la vista del usuario, y el controlador, para facilitar la escalabilidad de la aplicación (Shukla, 2022).
1.1. Modelo
En esta arquitectura, los modelos son los objetos que contienen información del acceso a la base de datos: o el lógico de negocio o sus validaciones (Walther, 2022). En nuestro caso, una importante parte del modelo son las entidades, o entities, que contienen los objetos de la base de datos.
Esas entidades vienen de Doctrine, un componente instalado para facilitar las operaciones con la base de datos, o el acceso a la base de datos, otro componente importante del modelo. Las entidades contienen los getters y los setters.
Además, las entidades tienen referencia a un repositorio, que ayuda a recoger los valores de las mismas en la base de datos a través de Doctrine. Hay un repositorio correspondiente a cada entidad.
1.2. Controlador
En el diseño MVC, el controlador recupera la información del modelo y la muestra a la vista del usuario. Es decir, el controlador es responsable por vigilar la manera en que el usuario interactúa con la aplicación (Walther, 2022). Eso pasa en las acciones dentro del controlador. Una acción siempre tiene el mismo propósito, recibir información de un HTTP request, y crear y devolver un HTTP objeto de tipo response (El objeto Response, s.f.).
El controlador busca a través del repositorio los valores de condicionados en las entidades para pintarlos en el fichero principal, el index.html.twig.
1.3. Vista
El último aspecto del diseño MVC es la vista que es lo que ve el usuario dentro de la aplicación. Es una plantilla y presenta los aspectos provenientes de la base de datos. Normalmente el rendimiento de la vista preparado en el controlador es un documento HTML, con sus hojas de estilo y funciones de JavaScript (Walther, 2022). En nuestro caso, todos los documentos son de tipo Twig, con extensión html.twig. Twig es un componente muy poderoso de Symfony que permite escribir código de PHP entre las etiquetas del HTML, de ese modo facilitando pintar la vista del patrón MVC con el contenido del modelo a través de una acción del controlador.
Para su funcionalidad, Twig tiene tres diferentes tipos de etiquetas:
1. {#...#} para comentarios
2. {% … %} para ejecutar el contenido
3. {{ … }} para imprimir el contenido
1.4. Base
En nuestra estructura tenemos, además del fichero index.html.twig que pinta todo el contenido de disponibilidad pública de la aplicación, el fichero base.html.twig, que contiene la cabecera y pie, trasmitible para todos los ficheros de la aplicación, incluso el index.
El fichero base se distingue en ser el único que contiene la portada tradicional de un documento html con las etiquetas de apertura “<html>” y clausura de “</html>" (Gómez, 2018a):
<!DOCTYPE html>
<html lang="en">
<head>
Incluyendo título y links para las hojas de estilo.
</head>
<body>
<header>
Dentro de la etiqueta <header> encontramos el menú desplegable. Se han excluido las clases de Bootstrap y la hoja de estilo de CSS, para, en una manera simple, poder enfocarnos en la comprensión de la función de Twig dentro del HTML.
Entre las etiquetas Twig {% … %} utilizamos el bucle ‘for’ que ejecuta el contenido proveniente de todos los campos de la tabla Product en el Modelo a través de la búsqueda efectuado con Doctrine y su repositorio correspondiente. Estos datos utilizamos en una lista de menú no organizada <ul>:
<ul >
{% for product in products %}
Imprimimos primero el enlace producto.gallerylink que abre un apartado en el fichero index.html.twig, donde se ubica las descripciones de los varios productos. También imprimimos el título product.title, ambos valores entre las etiquetas { … }.
<li><a href={{"/#" ~ product.gallerylink}} >{{product.title}}</a></li>
Finalmente, cerramos la ejecución del bucle ‘for’ con {% endfor %} y la lista del menú con la etiqueta </ul>, igual que el apartado del </header>.
{% endfor %}
</ul>
</header>
En el resto del documento tenemos el apartado body que se abre y cierre con etiquetas Twig, no HTML.
{% block body %}
{% endblock %}
El apartado body está vacío, dado que su contenido se ubica en el fichero index.html.twig entre etiquetas HTML de “<body>” y “</body>”.
Finalmente hay la etiqueta “<footer>” en el fichero base, contiene entre etiquetas HTML el contenido y estilo de Bootstrap, y las etiquetas de cierre del “</body>” y “</HTML>”.
1.5. Index
En el fichero principal, index.html.twig, comenzamos por hacer referencia al fichero base.html.twig:
{% extends 'base.html.twig' %}
De esa manera obtenemos el contenido de la base, el menú, la cabecera y el pie de la página, los cuales utilizamos para pintar de manera genérica en todos los ficheros.
Luego hacemos referencia a una plantilla de un formulario, utilizado en el apartado contacto para que el usuario puede escribir mensajes, (Gómez, 2018b):
{% form_theme form 'form/productform.html.twig' %}
Después abrimos el HTML con un bloque de body:
{% block body %}
En el bloque del body, encontramos el contenido de todos los modelos provenientes de la base de datos, o en el caso del formulario, un modelo que inserta información en la base de datos.
Cerramos con el cierre del body:
{% endblock %}
2. Estructura detallada de la aplicación
Ahora vamos a ver un poco más en detalle la estructura de la aplicación. Siguiendo el modelo MVC, vamos a comenzar con los modelos, luego su relación con el controlador para entender cómo funciona Twig en la parte visual al final. Vamos a comenzar con la parte más complicada, la Admin, pero nos favorece para entender cómo al final pintamos la parte pública de la aplicación, el ‘index’, y el fichero ‘base’.
2.1. Parte Admin
Para lograr el objetivo principal de que la aplicación funcione como un CMS, es requerimiento crear una sección privada desde donde el usuario con acceso pueda subir su propio material.
Esta sección, llamada Admin, se construye detrás del Firewall, con su propio controlador, y tres tipos de ficheros:
1. los que muestran los registros de la base de datos.
2. los que crean registros nuevos.
3. los que actualizan los registros guardados.
Estos tres tipos de ficheros existen para todas las entidades y sus tablas, seis en total: Carousel, Gallery, Product, Contact, Questions y Users.
Antes que todo, para poder modificar la base de datos, se tiene que acceder al área detrás del Firewall, Admin. Eso se hace con el formulario ‘login’ que registra el username y password.
Ilustración 1. Formulario del Login para acceder a la parte Admin
2.1.1. Crear productos
El primer paso para el administrador es crear productos para su aplicación. Eso se hace en el fichero newproduct.html.twig, utilizando la plantilla de su formulario que se guarda en un fichero PHP llamado productType.php. Este fichero contiene todos los campos de su entidad. Por lo tanto, el fichero newproduct.html.twig, no necesita tener campos, sino solo hacer referencia al modelo productform:
{% form_theme form 'form/productform.html.twig' %}
y luego pintar el modelo del formulario en Twig:
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
El formulario ‘productform’ también contiene solamente un modelo genérico, por lo cual los campos específicos se recogen en el controlador, desde el fichero productType y recogiendo sus datos en el variable $newproducts:
$form = $this->createForm(ProductType::class, $newproducts);
En el formulario creado, se llenan los campos para guardar el producto en la base de datos.
Ilustración 2. Formulario para crear un producto nuevo en el apartado Admin
Al pulsar en “Create new product”, se actualiza el contenido, la página se recarga y se redirige al fichero de la presentación de la misma, en este caso al fichero product.html.twig, pero no antes que llamar a una función de alerta escrita en JavaScript confirmando la actualización, con la ayuda de Sweet Alert.
Dado que se recarga la página al momento que queremos que funcione el fichero de JavaScript, programación perteneciente al frontend, hay que escribir esa programación en un comando de ‘echo’ de PHP, para que funcione al momento de actualizar el DOM. Esa parte se escribe en el controlador, que pinta la página con la información obtenida de la base de datos, con el aviso de alerta de Sweet Alert, “swal” (Fernando, 2020):
echo "<script type='text/javascript'>
document.addEventListener('DOMContentLoaded', function(event) {
swal({
title: 'Product updated!',
text: 'Your product has been updated successfully!',
type: 'success'
}).then(function() {
window.location = '/admin/products';
}); });
</script>";
Ilustración 3. Mensaje de Sweet Alert de confirmación al crear un producto nuevo
Con el nuevo producto se crea un enlace llamado Gallerylink, con opción de utilizar para redirigir el usuario desde el carrusel de la portada, o incluso desde la galería, para ver más descripción del producto. Además, el enlace Gallerylink se posiciona automáticamente en el menú desplegable de los productos, como vimos en la descripción del fichero base.
Ilustración 4. El menú desplegable con los productos provenientes de la tabla Product
La visualización del menú se ve en todos los ficheros, dado que se presenta en el fichero base que se extiende en todos los otros ficheros con la ejecución de este enlace:
{% extends 'base.html.twig' %}
2.1.2. Presentación de productos
En la imagen abajo se puede ver la presentación del contenido de la tabla Carousel, que contiene las imágenes y videos para la portada del Index. Desde los ficheros de presentaciones hay los opciones de actualizar (edit) o borrar (delete) el contenido.
Ilustración 5. Presentación del contenido de la tabla Carousel
El fichero products.html.twig funciona de la misma manera. Al pulsar en ‘Edit’ el usuario estará redirigido al fichero editproduct.html.twig, que sirve para actualizar el contenido, esta vez del apartado de productos.
Eso funciona a través del ‘id’ del producto de la base de datos, que se lleva como una variable en la URL, como por ejemplo: …/editproduct/1 (Gómez, 2018d):
<th scope="row"><a href="{{path ('editproduct',{'id':product.id})}}" >
<i class="fa-solid fa-pencil" ></i></a></th>
Esa id se usa, para que Twig pueda pintar el contenido existente en el formulario que se quiere actualizar. Incluso, en este apartado, se ha hecho una extensión al documento PHP que pinta el formulario en cuestión para que pueda rellenar el contenido a actualizar con la imagen existente también.
Primero, en el fichero services.yml se define la nueva propiedad para que se le pueda utilizar de manera genérica:
AppBundle\Form\Extension\ImageTypeExtension:
tags:
- { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\FileType }
Luego, se extiende el componente FileType en el fichero ImageTypeExtension.php:
public function getExtendedType()
{ return FileType::class; }
Y se agrega la definición ‘image_property’:
public function configureOptions(OptionsResolver $resolver)
{ $resolver->setDefined(['image_property']);}
Para que se puede recoger y filtrar los datos a través de la función getdata():
$parentData = $form->getParent()->getData();
Sin embargo, para poder filtrar los datos guardados en el variable $parentData y encontrar sólo la imagen, tenemos que utilizar el componente propertyAccess (Symfony : ):
if (null !== $parentData) {
$accessor = PropertyAccess::createPropertyAccessor();
$imageUrl = $accessor->getValue($parentData, $options['image_property']);
$curPageName = substr($_SERVER["REQUEST_URI"],7,7);
}
Si hay datos, los podemos extraer y, luego, buscar el URL para insertar la imagen en dicho formulario perteneciente al URL. En este caso, es el URL del fichero editproduct/id.
2.1.3. Borrar Imágenes
Es importante describir otro aspecto de las imágenes, porque igual que los videos, estas no se guardan en la base de datos sino en una carpeta ubicada en el alojamiento, o hosting, de la aplicación. Lo que se guarda en la base de datos es únicamente el enlace con un nombre único generado para cada registro con la función generateUniqueFileName() y la extensión guessExtension().
Si se rompe o cambia el enlace, es obviamente importante poder identificar la imagen o video antiguo para poder borrarlo. Por eso, al actualizar o borrar imágenes o videos, es importante incluir una parte en el código del controlador que borra cada imagen o video que ya no nos sirve, así no vamos a acumular imágenes de basura en su carpeta de la aplicación.
Eso se hace a través de la función unlink del PHP, escrito en el controlador. Añadiendo la ‘@ ‘, es decir: ‘@unlink’ la función no genera un error en caso de que no se encuentra la imagen o video a borrar (File System Functions, 2022):
@unlink("../web/img/productimg/". $oldImageFile);
Visualización del formulario con la imagen existente del apartado de galería.
Ilustración 6. Visualización del apartado de galería con la imagen existente a actualizar
2.2 Público
En la imagen posterior, se puede ver el carrusel de la portada con el enlace que abre el apartado del producto.
Ilustración 7. El carrusel de la portada del Index, con el botón "Learn more" de enlace para la sección Productos.
2.2.1. Galería
El apartado de la galería tiene otra función interesante. En el formType que pinta el formulario para la galería, se pueden escoger los enlaces de categoría proveniente de la tabla producto. Aquí se puede filtrar la galería mostrando solamente las imágenes perteneciente a esta misma categoría de productos.
Ilutración 8. Galería mostrando imágenes de los productos seleccionados. En este caso todos, "all"
En el index.html.twig, en el apartado de la galería, esa funcíon del Twig se ve así, utilizando el category.link para su selección:
<li data-filter="*" class="filter-active">All | الكل</li>
{% for category in categories %}
<li data-filter={{".filter-" ~ category.link}}>{{category.link | raw}}</li>
{% endfor %}
2.2.2 Productos
Al hacer clic en el botón con el ícono ‘i’ por “more information” debajo de la imagen, lleva al usuario al apartado donde se encuentra el producto.
<a href="{{'../#' ~ gallery.link}}" class="details-link" title="More information | معلومات أكثر"><i class="fa-solid fa-circle-info"></i></a>
Ilustración 9. Presentación de un producto con la posibilidad de ver imágenes o realizar un pedido
En el apartado del producto hay otra función donde se puede hacer un pedido. Al hacer clic en ‘Place order’ lleva al orderform.html.twig, donde se puede registrar sus datos para efectuar un pedido.
2.2.3. Order Form
Ilustración 10. Formulario de pedido (order form) rellenado con los datos e imagen del producto seleccionado
El fichero orderform se ha construido como una página separada para sacar provecho de la funcionalidad de los ficheros ‘edit’. En este sentido, el archivo orderform guarda el ‘id’ de un producto seleccionado y lo pone como extensión al URL para pintar un formulario que se ha rellenado con los datos del mismo producto. De esta manera, el cliente no tiene que rellenar su pedido sino sólo poner su dirección y detalles de contacto para efectuarlo. Al final, para alertar al cliente al efectuar su pedido, recibe un mensaje de Sweet Alert.
El formulario del orderform es realmente el mismo formulario para el contacto, pero con una condición que le pinta con información de tipo de producto (generado automáticamente a través del ‘id’ del URL), cantidad y un botón con “place order” en vez del “send message”.
Por causa de una función que compruebe parte del URL, el controlador sabe cuál formulario pintar (How to Create a Form Type Extension, 2022):
$contactPageName = substr($_SERVER["REQUEST_URI"],1,5);
Si el $contactPageName es igual a ‘order’, se pinta el formulario para hacer pedidos de productos y, si no, se pinta el formulario para editar o rellenar el contacto. Eso se hace porque el administrador también tiene la posibilidad de actualizar o editar los mensajes enviados por usuarios o clientes potenciales desde el formulario de contacto. Una segunda condición pinta el formulario en blanco con el mensaje “send message” si no existe datos en el formulario a rellenar:
if (!$contact || null === $contact->getId()) {
$form->add('send', SubmitType::class, ['label' => 'Send message | ارسال']);
2.3. Plantilla de Formularios
Todos los ficheros que crean o actualizan registros en la base de datos se han construido usando formularios, tanto los de la parte admin como público. En la parte público hay el formulario de contacto y el de hacer un pedido.
En la parte Admin, se puede crear y actualizar los productos en los apartados de carrusel, galería o producto.
3. CONCLUSIONES
Symfony funciona muy bien para este tipo de proyecto porque su biblioteca de documentación y componentes dejan al desarrollador con amplios recursos para poder crear una aplicación interesante de acuerdo con las necesidades de cualquier tipo. También funciona perfectamente para integrar Bootstrap, incluso dejando Twig interactuar y poniendo variables dentro de las clases de Bootstrap, como vimos en el ejemplo de la galería y, además, en el menú de navegación. Symfony ofrece una forma muy útil para crear un proyecto sin tener que acudir a pagar por extensiones y plugins al querer escalarlo.
Una nota importante es que el Standard Edition ha sido descartado porque no escala bien dado que es difícil cambiar sus componentes integrados. (Potencier, 2018) Por lo tanto, un desarrollador experimentado que quiere construir un proyecto con facilidad de escalabilidad, debería optar por una versión más reciente de Symfony.
Referencias
Fernando, A. (2020). ¿Cómo puedo usar un Sweet Alert en un echo? Stack Overflow. https://es.stackoverflow.com/questions/402445/como-puedo-usar-un-sweet-alert-en-un-echo.
File System Functions. (2022). PHP Manual. https://www.php.net/manual/en/function.unlink.php.
Gómez, P. (2018). App con Symfony 3 – 08 – Layout. Vistas. Aplicación desde 0 con Symfony 3. https://www.youtube.com/watch?v=ZuEb6zAhOiA&list=PLm0WEBt1zBgJyj8ETsWFaQWo0YwViX37u&index=10.
App con Symfony 3 – 35 – Formularios. Diseño con themes. Aplicación desde 0 con Symfony 3. (2018). https://www.youtube.com/watch?v=ovajEWI07AY&list=PLm0WEBt1zBgJyj8ETsWFaQWo0YwViX37u&index=37.
App con Symfony 3 – 50 – Login. Aplicación desde 0 con Symfony 3. (2018). https://www.youtube.com/watch?v=F3TTqSklTnA&list=PLm0WEBt1zBgJyj8ETsWFaQWo0YwViX37u&index=52.
App con Symfony 3 – 54 – Reservas. Aplicación desde 0 con Symfony 3. (2018). .https://www.youtube.com/watch?v=SU0iQCtmCSY&list=PLm0WEBt1zBgJyj8ETsWFaQWo0YwViX37u&index=56.
How to Create a Form Type Extension. (2022). Symfony. https://symfony.com/doc/3.4/form/create_form_type_extension.html.
How to Dynamically Modify Forms Using Form Events. (2022). Symfony, https://symfony.com/doc/current/form/dynamic_form_modification.html.
Morris, C. (2022). What Is a Content Management System (CMS)? Search Engine Journal. https://www.searchenginejournal.com/what-is-a-content-management-system-cms/468136/#close.
El objeto Response. (s.f.) Uniwebsidad. https://uniwebsidad.com/libros/symfony-2-4/capitulo-5/el-objeto-response.
Potencier, F. (2018) The End of the Symfony Standard Edition. Symfony.https://symfony.com/blog/the-end-of-the-symfony-standard-edition.
Shukla, S. (2022). What Makes Symfony Framework a Great Choice for PHP Development? Net Solutions. https://www.netsolutions.com/insights/symfony-framework-features/.
Walther, S. (2022). Understanding Models, Views, and Controllers. Learn Microsoft.https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/overview/understanding-models-views-and-controllers-cs.
Web Application (Web App). (s.f.) Tech Target Contributor. https://www.techtarget.com/searchsoftwarequality/definition/Web-application-Web-app.