Cómo crear una aplicación de citas inspiradoras con AdonisJs y MySQL

El autor seleccionó el Tech Education Fund para recibir una donación como parte del programa Write for DOnations.
Introducción
AdonisJs es un framework web Node.js escrito en JavaScript simple que se ejecuta en todos los principales sistemas operativos. Utiliza el popular patrón de diseño MVC (Modelo-Vista-Controlador) y ofrece un ecosistema estable para escribir aplicaciones web del lado del servidor. El framework cuenta con autenticación sin inconvenientes, ORM SQL (mapeo relacional de objetos), migraciones y propagación de bases de datos. AdonisJs tiene una arquitectura similar al framework de aplicaciones web PHP Laravel, que incluye la misma estructura de carpetas y varios conceptos de configuración compartida.
De forma predeterminada, AdonisJs utiliza el motor de plantillas Edge, que está diseñado para un uso intuitivo. Al igual que Laravel, AdonisJs se entrega con un ORM llamado Lucid que sirve como interfaz para la comunicación entre los modelos de una aplicación y la base de datos. Con AdonisJs, los desarrolladores pueden crear una aplicación full-stack donde el servidor back-end será responsable de aplicar la lógica empresarial, el enrutamiento y la representación de todas las páginas de la aplicación. También es posible crear una API de servicio web para devolver respuestas JSON desde un controlador; estos servicios web pueden consumirse luego utilizando marcos front-end como Vue.js, React y Angular.
En este tutorial, creará una aplicación con AdonisJs utilizando su CLI. Creará rutas, controladores, modelos y vistas dentro de su aplicación y realizará validaciones de formularios. El ejemplo de este tutorial será una aplicación de citas inspiradoras en la que un usuario puede registrarse e iniciar sesión para crear una cita inspiradora. Esta aplicación de demostración le brindará la oportunidad de realizar operaciones CRUD (Crear, Leer, Actualizar y Eliminar).
Prerrequisitos
Antes de comenzar esta guía, necesitará lo siguiente:
- Una instalación local de Node.js (al menos v8) y npm (al menos v3.0). Node.js es un entorno de ejecución de JavaScript que te permite ejecutar tu código fuera del navegador. Viene con un administrador de paquetes preinstalado llamado npm, que te permite instalar y actualizar paquetes. Para instalarlos en macOS o Ubuntu 18.04, sigue los pasos de Cómo instalar Node.js y crear un entorno de desarrollo local en macOS o la sección Instalación mediante un PPA de Cómo instalar Node.js en Ubuntu 18.04.
- MySQL instalado en su equipo. Siga las instrucciones que se indican aquí para descargarlo e instalarlo en el sistema operativo que elija. Para instalar MySQL correctamente, puede hacerlo utilizando Homebrew en Mac o, para Ubuntu 18.04, siga nuestro tutorial Cómo instalar MySQL en Ubuntu 18.04.
- Un conocimiento básico de JavaScript; consulte nuestra serie Cómo codificar en JavaScript.
- Un editor de texto instalado, como Visual Studio Code, Atom o Sublime Text.
Nota: Este tutorial utiliza una máquina macOS para el desarrollo. Si utiliza otro sistema operativo, es posible que deba utilizar los comandos sudo
for npm
en los primeros pasos.
Paso 1: Instalación de Adonis CLI
En esta sección, instalará Adonis CLI y todos los paquetes necesarios en su máquina local. La CLI le permitirá crear el andamiaje de un nuevo proyecto de AdonisJs, así como crear y generar código repetitivo para controladores, middlewares y modelos en su aplicación. También creará su base de datos para el proyecto.
Ejecute el siguiente comando para instalar la CLI de AdonisJs globalmente en su máquina a través de npm
:
- npm i -g @adonisjs/cli
Una vez completado el proceso de instalación, escriba el siguiente comando en la terminal para confirmar la instalación de AdonisJs y ver la versión actual:
- adonis --version
Verá un resultado que muestra la versión actual de AdonisJs:
Output4.1.0
Con la instalación exitosa de la CLI de AdonisJs, ahora tiene acceso y puede usar el adonis
comando para crear nuevas instalaciones de un proyecto de AdonisJs, administrar su proyecto y generar archivos relevantes como controladores, modelos, etc.
Ahora, puedes proceder a crear un nuevo proyecto AdonisJs usando el adonis
comando que se muestra aquí:
- adonis new adonis-quotes-app
El comando anterior creará una aplicación nombrada adonis-quotes-app
en un nuevo directorio con el mismo nombre en su directorio de proyecto local con la estructura AdonisJs MVC relevante.
Muévete a la nueva carpeta de la aplicación:
- cd adonis-quotes-app
A continuación, inicie su aplicación ejecutando:
- adonis serve --dev
Esto iniciará el servidor de desarrollo en el puerto predeterminado 3333
, tal como se especifica en el archivo raíz .env
de su aplicación. Navegue a http://localhost:3333 para ver la página de bienvenida de AdonisJs.
Ahora completará la configuración de su base de datos. Aquí, instalará el mysql
controlador para conectarse a su servidor MySQL desde su aplicación Node.js a través de npm
. Para comenzar, regrese a su terminal donde se está ejecutando actualmente la aplicación, detenga el proceso con CTRL + C
y ejecute el siguiente comando:
- npm i mysql
Ahora que ha instalado correctamente el controlador MySQL Node.js para esta aplicación, necesita crear la base de datos de la aplicación y configurar la conexión adecuada a ella.
La última versión de MySQL que ha instalado a partir del tutorial de requisitos previos utiliza un complemento de autenticación predeterminado llamado caching_sha2_password
. Actualmente, los controladores de Node.js para MySQL no lo admiten. Para evitar problemas de conexión a la base de datos desde su aplicación, deberá crear un nuevo usuario MySQL y utilizar el complemento de autenticación compatible actualmente mysql_native_password
: .
Para comenzar, acceda al cliente MySQL utilizando la cuenta raíz :
- mysql -u root -p
Se le pedirá que ingrese la contraseña de su cuenta raíz configurada durante la instalación de MySQL.
A continuación, crea el usuario y la contraseña mediante el mysql_native_password
complemento:
- CREATE USER 'sammy'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';
Verá el siguiente resultado:
OutputQuery OK, 0 rows affected (0.02 sec)
A continuación, cree una base de datos para la aplicación con:
- CREATE DATABASE adonis;
Verá el siguiente resultado:
OutputQuery OK, 1 row affected (0.03 sec)
Ahora ha creado correctamente la base de datos para esta aplicación.
Ahora, habilite el acceso a la base de datos creada para el nuevo usuario MySQL. Ejecute el siguiente comando para otorgarle todos los privilegios al usuario en la base de datos:
- GRANT ALL PRIVILEGES ON adonis.* TO 'sammy'@'localhost';
Vuelva a cargar las tablas de concesiones ejecutando el siguiente comando para aplicar los cambios que acaba de realizar:
- FLUSH PRIVILEGES;
Verá el siguiente resultado:
OutputQuery OK, 0 rows affected (0.00 sec)
Salga del cliente MySQL con:
- quit;
Ha instalado correctamente la CLI de AdonisJs, ha creado un nuevo proyecto de AdonisJs y lo ha instalado mysql
mediante npm
. También ha creado la base de datos para esta aplicación y ha configurado un usuario MySQL con los privilegios adecuados. Esta es la configuración básica para su aplicación y en la siguiente sección comenzará a crear las vistas necesarias para su aplicación.
Paso 2: uso del motor de plantillas Edge
AdonisJs se entrega con su propio motor de plantillas llamado Edge. Te permite crear una plantilla HTML reutilizable y permite la introducción de lógica de interfaz en tu aplicación con un código mínimo. Edge proporciona a los desarrolladores de JavaScript las herramientas necesarias para desarrollar una aplicación, crear un diseño basado en componentes, escribir condicionales, usar iteraciones y crear capas de vista para contener la lógica. Todos los archivos de plantilla terminan con la .edge
extensión y se almacenan en el resources/views
directorio.
Las siguientes son las vistas que su aplicación necesitará para funcionar correctamente:
- Diseño maestro : con Edge, puede crear una página que contendrá el CSS, los archivos JavaScript comunes, jQuery y las partes comunes de la interfaz de usuario que permanecerán iguales en toda la aplicación, por ejemplo, la barra de navegación, el logotipo, el encabezado, etc. Una vez que haya establecido la página de Diseño maestro, otras vistas (páginas) en su aplicación la heredarán.
- Vista de índice : esta página utilizará el diseño maestro para heredar archivos comunes y también representará contenidos para la página de inicio de la aplicación.
- Página de inicio de sesión : esta página también utilizará el diseño maestro y mostrará el formulario con los campos de entrada para el nombre de usuario y la contraseña para que los usuarios inicien sesión.
- Página de registro : aquí, los usuarios verán un formulario para registrarse y tener sus datos almacenados en la base de datos.
- Crear página de citas : los usuarios utilizarán esta página para crear una cita inspiradora.
- Página de editar cita : los usuarios utilizarán esta página para editar una cita.
- Ver página de citas : los usuarios utilizarán esta página para ver una cita en particular.
Para comenzar, utilice el adonis
comando para crear la página de diseño maestra ejecutando el siguiente comando:
- adonis make:view layouts/master
Verá un resultado similar al siguiente:
Output✔ create resources/views/layouts/master.edge
Este comando creará automáticamente un master.edge
archivo en su resources/views/layouts
carpeta. Abra el nuevo archivo:
- nano resources/views/layouts/master.edge
Añade el siguiente código:
/recursos/vistas/layouts/master.edge
!DOCTYPE htmlhtmlhead meta charset="UTF-8" meta name="viewport" content="width=device-width, initial-scale=1.0" meta http-equiv="X-UA-Compatible" content="ie=edge" titleadonis-quotes-app/title {{ style('https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css') }} {{ style('style') }} {{ script('https://code.jquery.com/jquery-3.3.1.slim.min.js') }}/headbody div @include('navbar') @!section('content') /div {{ script('https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js') }}/body/html
En este archivo, incluye los archivos CDN para Bootstrap CSS, Bootstrap JavaScript y jQuery. Agrega un nombre de archivo CSS global de style.css
y dentro de div
incluye un archivo parcial llamado navbar
. Para reutilizar fragmentos de código HTML que necesita en varias páginas de su aplicación, como nav
o footer
, puede incorporar archivos parciales. Estos son archivos más pequeños que contienen el código repetido, lo que hace que sea más rápido actualizar el código para estos elementos en un solo lugar en lugar de cada vez que ocurre. navbar
Contiene marcado para botones de inicio de sesión y registro , un logotipo y un enlace de inicio.
Con esto en su lugar, todas las páginas posteriores que se crearán para esta aplicación pueden extender el diseño maestro y tenerlo navbar
renderizado sin necesidad de escribir el contenido nuevamente. Creará este navbar
archivo más adelante en el tutorial.
Por último, se define una etiqueta de sección @!section()
para incluir contenido de otras páginas y hacer que el diseño maestro las represente. Para que esto funcione como se espera, todas las páginas nuevas que extenderán el diseño maestro también deben definir una etiqueta de sección con el mismo nombre (es decir, @section(``'``content``'``)
).
Guarde y salga del archivo una vez que haya terminado de editarlo.
A continuación, utilizará el adonis
comando para crear la barra de navegación:
- adonis make:view navbar
Verá un resultado similar a este:
Output✔ create resources/views/navbar.edge
Abra el archivo recién creado:
- nano resources/views/navbar.edge
Luego agregale el siguiente código:
/recursos/vistas/navbar.edge
nav a LOGO/a button type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation" span/span /button div ul li a href="/"Home/a /li /ul /div div @loggedIn ul li div a href="{{route('create.quote')}}"Create Quote/a /div /li li a href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" {{ auth.user.username}} /a div aria-labelledby="navbarDropdownMenuLink" form method="POST" action="{{route('logout')}}" {{ csrfField() }} button type="submit" href=""logout/button /form /div /li /ul @else ul li a href="{{route('login.create')}}" login /a /li li a href="{{route('register.create')}}" Register /a /li /ul @endloggedIn /div/nav
Además de definir los enlaces a la página de inicio y un botón para registrarse e iniciar sesión, se agrega una @loggedIn
etiqueta. Con esto en su lugar, puede escribir una declaración condicional en torno al usuario autenticado y mostrar el contenido apropiado donde sea necesario. Para un usuario autenticado, la aplicación mostrará su nombre de usuario y un botón para crear una nueva cotización. Si un usuario no ha iniciado sesión, su aplicación mostrará un botón para iniciar sesión o registrarse. Esta página se incluirá como parte de cada página, como se hizo anteriormente en el diseño maestro de esta aplicación.
Guardar y salir del archivo.
Ahora, creará la página de índice que utilizará como página de inicio de la aplicación. Representará y mostrará la lista de todas las citas inspiradoras que escriben los usuarios:
- adonis make:view index
Verá un resultado similar al siguiente:
Output✔ create resources/views/index.edge
El archivo creado aquí se ubicará en resources/views/index.edge
. Abra el archivo:
- nano resources/views/index.edge
Luego agrega el siguiente código:
/recursos/vistas/index.edge
@layout('layouts/master')@section('content')div div @if(flashMessage('successmessage')) span{{ flashMessage('successmessage') }}/span @endif /div div @each(quote in quotes) div a href="/view-quote/{{quote.id}}" div div blockquote p{{quote.body}}/p footer cite {{quote.username}}/cite /footer /blockquote @if(auth.user.id == quote.user_id) div a href="/edit-quote/{{quote.id}}"edit/a a href="/delete-quote/{{quote.id}}"delete/a /div @endif /div /div /a /div @else div pNo inspirational quote has been created/p /div @endeach /div/div@endsection
Aquí, indica que esta vista utilizará el master
diseño extendiéndolo. Esta página ahora puede tener acceso a todas las bibliotecas, hojas de estilo y las navbar
incluidas en el master
diseño. A continuación, itera sobre una matriz de utilizando la etiqueta quotes
incorporada . La matriz se pasará a esta vista desde la que creará más adelante en este tutorial. Si no hay comillas, se mostrará un mensaje correspondiente.@each
quotes
QuoteController
Guarde y salga de este archivo.
Ahora, para crear la página de inicio de sesión, ejecute el siguiente comando desde la terminal:
- adonis make:view auth/login
Verá un resultado similar a:
Output✔ create resources/views/auth/login.edge
Esto creará automáticamente una auth
carpeta resources/views
y también un login.edge
archivo dentro de ella. Abra el login.edge
archivo:
- nano resources/views/auth/login.edge
Añade el siguiente contenido:
/recursos/vistas/auth/login.edge
@layout('layouts/master')@section('content') div div div form method="POST" action="{{route('login.store')}}" {{ csrfField() }} div @if(flashMessage('successmessage')) span{{ flashMessage('successmessage') }}/span @endif /div div label for="email"Email address/label input type="email" name="email" value="{{old('email','')}}" placeholder="Enter email" {{ elIf('span class=text-danger$self/span', getErrorFor('email'), hasErrorFor('email')) }} /div div label for="pasword"Password/label input type="password" name="password" value="{{old('password','')}}" placeholder="Password" {{ elIf('span class=text-danger$self/span', getErrorFor('password'), hasErrorFor('password')) }} /div div button type="submit"Submit/button /div /form /div /div /div@endsection
Este archivo contiene un formulario que contiene elementos de entrada que utilizará para recopilar el nombre de usuario y la contraseña de un usuario registrado antes de que pueda autenticarse correctamente y comenzar a crear cotizaciones. Otro elemento importante a tener en cuenta en esta página es el {{ csrfField() }}
. Es una variable global que AdonisJs utilizará para pasar el token de acceso CSRF al enviar una solicitud POST
, PUT
y DELETE
desde su aplicación.
Esto se implementó para proteger su aplicación de ataques de falsificación de solicitud entre sitios (CSRF). Funciona generando un secreto CSRF único para cada usuario que visita su sitio web y, una vez que sus usuarios envían una solicitud HTTP desde el frontend, se genera un token correspondiente para este secreto y se transmite junto con la solicitud. Esto permitirá que el middleware creado para esta solicitud dentro de AdonisJs verifique que tanto el token como el secreto CSRF sean válidos y pertenezcan al usuario autenticado actualmente.
Guarde y salga del archivo una vez que haya terminado.
A continuación, creará la página de registro con este comando:
- adonis make:view auth/register
Verá un resultado similar a este:
Output✔ create resources/views/auth/register.edge
Localice y abra el archivo recién creado en resources/views/auth/register.edge
:
- nano resources/views/auth/register.edge
Añade el siguiente código:
recursos/vistas/auth/register.edge
@layout('layouts/master')@section('content') div div div form method="POST" action="{{route('register.store')}}" {{ csrfField() }} div label for="name"Fullname/label input type="text" name="name" value="{{old('name','')}}" placeholder="Enter Fullname" {{ elIf('span class=text-danger$self/span', getErrorFor('name'), hasErrorFor('name')) }} /div div label for="email"Email address/label input type="email" name="email" value="{{old('email','')}}" placeholder="Enter email" {{ elIf('span class=text-danger$self/span', getErrorFor('email'), hasErrorFor('email')) }} /div div label for="pasword"Password/label input type="password" name="password" placeholder="Password" {{ elIf('span class=text-danger$self/span', getErrorFor('password'), hasErrorFor('password')) }} /div div button type="submit"Submit/button /div /form /div /div /div@endsection
De manera similar a lo que tiene en la página de inicio de sesión, este archivo contiene un formulario HTML con campos de entrada para recopilar los datos de acceso name
, de la email
cuenta y password
de un usuario durante el proceso de registro. También se incluye el {{ csrfField() }}
que se requiere para cada solicitud de publicación para una aplicación AdonisJs.
Guardar y salir del archivo.
Ahora, generará un nuevo archivo para crear una cita inspiradora ejecutando el siguiente comando desde la terminal:
- adonis make:view quotes/create-quote
Verá un resultado como el siguiente:
Output✔ create resources/views/quotes/create-quote.edge
Abierto resources/views/quotes/create-quote.edge
:
- nano resources/views/quotes/create-quote.edge
Y añadele el siguiente contenido:
/recursos/vistas/quotes/create-quote.edge
@layout('layouts/master')@section('content')div div div/div div div a href="/"back/a /div br div/div form method="POST" action="{{route('store.quote')}}" {{ csrfField() }} div label for="quote"Create Quote/label textarea type="text" rows="5" name='body' placeholder="Write an inspirational quote"/textarea /div div button type="submit"Submit/button /div /form /div /div div/div /div/div@endsection
Esta página extiende el diseño maestro y contiene un formulario HTML con un elemento de área de texto que permite al usuario ingresar texto en varias filas antes de publicarlo y manejarlo mediante la ruta adecuada.
Guarde y salga del archivo una vez que haya terminado.
A continuación, creará una página para editar una cita en particular. Ejecute el siguiente comando desde la terminal:
- adonis make:view quotes/edit-quote
Verá el siguiente resultado:
Output✔ create resources/views/quotes/edit-quote.edge
Abra el archivo con:
- nano resources/views/quotes/edit-quote.edge
Añade el siguiente contenido a resources/views/quotes/edit-quote
:
/recursos/vistas/citas/edit-quote.edge
@layout('layouts/master')@section('content')div div div div a href="/"back/a /div br div/div form method="POST" action="/update-quote/{{quote.id}}" {{ csrfField() }} div label for="pasword"Edit Quote/label textarea type="text" rows="5" name='body' placeholder="write the inspirational quote"{{quote.body}}/textarea /div div button type="submit"Update/button /div /form /div /div/div@endsection
Esta página tiene contenido similar al create-quote.edge
archivo; la diferencia es que contiene los detalles de una cita particular que necesita ser editada form method="POST" action="/update-quote/{{quote.id}}"
.
Guardar y salir del archivo.
Por último, genere una página para ver una sola cita inspiradora:
- adonis make:view quotes/quote
Verá un resultado similar a este:
Output✔ create resources/views/quotes/quote.edge
Abra el archivo con:
- nano resources/views/quotes/quote.edge
Añade el siguiente código:
/recursos/vistas/quotes/quote.edge
@layout('layouts/master')@section('content')div div div div div div a href="/"back/a /div br div/div blockquote p{{quote.body}}/p footer cite{{quote.username}}/cite /footer /blockquote /div /div /div /div/div@endsection
Esta página muestra los detalles de una cita en particular, que incluye el cuerpo de la cita, quote.body
, y el autor que la creó, quote.username
.
Una vez que haya terminado con el archivo, guárdelo y salga.
Ha creado todas las páginas necesarias para su aplicación mediante el motor de plantillas Edge. A continuación, configurará y creará una conexión a la base de datos de su aplicación.
Paso 3: Creación de un esquema de base de datos
Si ejecuta su aplicación ahora, se generará un error, ya que aún no ha conectado la aplicación a una base de datos. En esta sección, configurará una conexión a la base de datos y luego usará el adonis
comando para generar un archivo de migración que se utilizará para crear las tablas correspondientes.
AdonisJs se entrega con un ORM llamado Lucid ORM, que proporciona una implementación de registros activos para trabajar con su base de datos. Elimina la molestia de escribir consultas SQL que recuperan datos de la base de datos en tiempo real. Esto es especialmente útil cuando se trabaja en una aplicación compleja que requiere muchas consultas. Por ejemplo, puede recuperar todas las citas de su aplicación escribiendo lo siguiente:
const quotes = await Quote.all()
Para continuar con la configuración adecuada para la base de datos de su aplicación, asegúrese de que todavía está dentro del directorio raíz de su aplicación y cree un .env
archivo:
- nano .env
Abra el archivo recién creado y agregue el siguiente contenido:
.env
HOST=127.0.0.1PORT=3333NODE_ENV=developmentAPP_URL=http://${HOST}:${PORT}CACHE_VIEWS=falseAPP_KEY=bTVOEgUvmTCfkvgrK8gEBC3Qxt1xSYr0DB_CONNECTION=mysqlDB_HOST=127.0.0.1DB_PORT=3306DB_USER=sammyDB_PASSWORD=passwordDB_DATABASE=adonisSESSION_DRIVER=cookieHASH_DRIVER=bcrypt
De manera predeterminada, la conexión de base de datos para una aplicación AdonisJs es SQLite, que actualizará a MySQL aquí. También debe especificar la PORT
aplicación, el entorno de la aplicación y las credenciales de su base de datos. Asegúrese de reemplazar los marcadores de posición DB_USER
, DB_PASSWORD
y DB_DATABASE
con sus credenciales.
A continuación, creará el modelo y un archivo de migración para Quote
utilizar la CLI de Adonis. Para ello, ejecute el siguiente comando:
- adonis make:model Quote --migration
Verá un resultado similar al siguiente:
Output✔ create app/Models/Quote.js✔ create database/migrations/1568209992854_quote_schema.js
Este comando creará un modelo Quote
en la app/Models
carpeta y un archivo de esquema en la database/migrations
carpeta. El archivo de esquema recién creado tendrá como prefijo la marca de tiempo actual. Abra el archivo de esquema con:
- nano database/migrations/1568209992854_quote_schema.js
Actualice su contenido con el siguiente código:
base de datos/migraciones/…quote_schema.js
'use strict'/** @type {import('@adonisjs/lucid/src/Schema')} */const Schema = use('Schema')class QuoteSchema extends Schema { up () { this.create('quotes', (table) = { table.increments() table.integer('user_id').notNullable() table.string('username', 80).notNullable() table.string('body').notNullable() table.timestamps() }) } down () { this.drop('quotes') }}module.exports = QuoteSchema
Un archivo de esquema en AdonisJs requiere dos métodos diferentes, que son:
- arriba : se utiliza para crear una nueva tabla o alterar una existente.
- abajo : se utiliza para revertir los cambios aplicados en el
up
método.
Además de los campos timestamps()
y increments()
, se actualiza el contenido del archivo de esquema con los atributos de campo user_id
, username
y de body
una cotización que se creará. Los campos user_id
y username
hacen referencia a los detalles del usuario que crea una cotización en particular. Esto define una relación de uno a muchos y significa que un usuario puede poseer una cantidad infinita de cotizaciones, mientras que una sola cotización solo puede pertenecer a un usuario.
Guardar y salir del archivo.
AdonisJs viene instalado con un User
modelo y su archivo de migración por defecto, el cual requiere solo una pequeña modificación para establecer la relación entre el modelo User
y Quote
el modelo.
Abra el User
modelo en app/Models/User.js
:
- app/Models/User.js
Agregue este método inmediatamente después del tokens()
método:<
Deja una respuesta