Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Versión 5.n
Por
Carlos Cuesta Iglesias
Instalación
MySQL login*:
user: root
password:cd..
Importante:
Las siguientes rutas deberían quedar referenciadas en las variables de entorno a nivel de
sistema:
● X:\laragon\bin\composer
● X:\laragon\bin\php\php-7.2.11-Win32-VC15-x64
● Y:\Users\Cuesta\AppData\Roaming\Composer\vendor\bin
Siendo X la unidad donde se instaló Laragón y Y la unidad donde el sistema
operativo referencia las aplicaciones.
Si es usuario de Mac, se recomienda instalar Laravel Valet, pero si tiene ya instalado MAMP
o XAMPP puede instalar sobre dichos servidores. Tenga en cuenta que también se requiere
Composer, PHP y un gestor de base de datos.
Se recomienda mirar:
● https://brew.sh/index_es.
Luego de instalar:
● Mostrar el readme
○ Document Root c:\laragon\www
○ Abrir terminal: Ctrl+Alt+T
○ Si se requiere MySQL, el usuario es root y el password (nada)
El botón Iniciar todo, carga los servicios de Apache (p80) y MySQL (p.3306)
Si se utiliza Wampp o XAMPP, apagar los puertos para evitar conflictos.
Terminada la descargar, use el comando ls para comprobar la creación www de la nueva
aplicación.
En conclusión, como en wamp, las aplicaciones se crean en la carpeta www.
Importante:
● En caso de presentarse errores de este tipo:
composer dumpautoload
● En esta parte, tenga cuidado con proporcional los permisos necesarios para
evitar la denegación de acceso del sistema operativo al servidor web.
● Cada vez que ejecute php artisan debe hacerse dentro de la carpeta del
proyecto.
Vista General de Estructura de Carpetas
Carpeta Descripción
● En web.php se definen las rutas de la aplicación web (aquellas que consultan los
usuarios desde el navegador). Inicialmente nos centraremos en este tipo.
Note que el objeto Route proporciona un método get cuyo primer argumento hace referencia
en este caso a la ruta raíz de la aplicación y un segundo argumento, una función anónima o
clousure que retorna una vista disponible en la carpeta ../resources/views/
Route::get('/', f
unction () {
return 'Hola mundo';
});
También es posible utilizar otros tipos de peticiones como post, put, patch y delete, las 3
últimas no soportadas por los navegadores pero que se pueden emular en Laravel. Más
adelante se verá cómo.
Paso de parámetros a las rutas
Se pueden pasar parámetros desde la barra de direcciones. Estos se pasan entre llaves y
por cada parámetro que se indique, el clousure debe recibe el argumento respectivo.
Ejemplo:
appx.test/usuario/Carlos
Hasta el momento, el uso de la URL fallará si no se pasan parámetros porque estos son
obligatorios. Esto se puede corregir fácilmente mediante el uso de parámetros por defecto:
Suponga que se tiene la siguiente “sección productos” a la que se accede desde diferentes
enlaces (ver app4 / app5):
Los enlaces desde los los que se accede se pueden definir de la siguiente manera:
Así, al visitar appx.test/ deben verse los cinco enlaces que llevan a la página de
“Productos”.
Para efectos de pensar en nuevas versiones, en donde posiblemente haya que reemplazar
nombres de rutas, esta no es la manera óptima de definir los enlaces. En el siguiente
ejemplo, se tiene una ruta “articulos” a la cual le hemos dado el alias o nombre productos.
Pruebe ahora a cambiar la ruta ‘articulos’ por cualquier otra ruta (inventario, almacen, etc) y
al pulsar clic sobre los enlaces, notará que la aplicación se direcciona correctamente al
enlace indicado.
Rutas de vistas
Recuerde que las vistas están en ../resources/views/ y que por defecto hay una vista en
dicho paquete, llamada welcome.blade.php. La forma de utilizar dicha vista sería mediante
esde appX/routes/web.php para
la inclusión de una instrucción que llame la función view d
que reciba como argumento el nombre de la vista:
Creación de vistas
Supongamos que la vista que acaba de crear (ver app6) tiene el siguiente cuerpo en el que
se ha incluido una porción de código PHP para mostrar el dato recibido $nombre:
<body>
<h1>Tienda online</h1>
Bienvenid@ <?php e cho $nombre ?? 'Visitante anónimo' ?>
</body>
Una de las formas de enviar el dato desde web.php a la vista sería el siguiente:
Una tercera forma de enviar los datos a la vista, es enviar un array asociativo como
segundo argumento del método view:
Route::get('/', function ( ) {
$nombre = 'Carlos';
return view('inicio', [ 'nombre' => $nombre]);
})->name('inicio');
Route::get('/', function (
) {
$nombre = 'Carlos';
return view('inicio', c ompact('nombre'));
})->name('inicio');
Nota: por cada variable, compact() busca una variable con ese nombre en la tabla de
símbolos actual y las añade al array de salida de modo que el nombre de la variable se
convierte en la clave y el contenido de la variable se convierte en el valor para esa clave. En
pocas palabras, hace lo contrario que extract().
$nombre = 'Carlos';
Route::view('/', 'inicio', ['nombre' => $nombre]);
Route::view('/', 'inicio')->name('inicio');
Route::view('/acercade', 'acercade')->name('acercade');
Route::view('/catalogo', 'catalogo')->name('catalogo');
Route::view('/contacto', 'contacto')->name('contacto');
<nav>
<ul>
<li><a href="/">Inicio</a></li>
<li><a href="/acercade">Acerca de...</a></li>
<li><a href="/catalogo">Catálogo de productos</a></li>
<li><a href="/contacto">Contacto</a></li>
</ul>
</nav>
<h1>Tienda online</h1>
Bienvenid@ <?php echo $nombre ?? 'Visitante anónimo' ?>
Pruebe que la aplicación y sus enlaces se ejecuta correctamente, pero que para las páginas
acerca de, catálogo y contacto la navegación se pierde y no se quiere repetir el código de
navegación para cada una de estas páginas. Ahora sí observe a solución utilizando blade
(ver app6):
1. Cree una copia del archivo inicio.blade.php y renombre la copia con el nombre
plantilla.blade.php o cualquier otro nombre que le recuerde que es una plantilla.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
...
<title>@yield('titulo', 'Tienda')</title>
</head>
<body>
<nav>
<ul>
<li><a href="/">Inicio</a></li>
<li><a href="/acercade">Acerca de...</a></li>
<li><a href="/catalogo">Catálogo de productos</a></li>
<li><a href="/contacto">Contacto</a></li>
</ul>
</nav>
@yield('contenido')
</body>
</html>
Todo lo que hay que saber por ahora de una plantilla Blade es que las directivas
@yield permiten indicar secciones como argumentos, en las que luego se colocará
contenido específico. Opcionalmente, estas directivas admiten un segundo
parámetro para indicar un valor por defecto. Veamos un ejemplo de su uso:
@extends('plantilla')
@section('titulo')
Tienda Online
@endsection
@section('contenido')
<h1>Página de inicio</h1>
Bienvenid@ {{ $nombre ?? 'Invitado' }}
@endsection
La ventaja de esta notación es que evita ataques de tipo XSS. Ensaye por
ejemplo a enviar un echo de un alert y luego intente enviar el mismo alert
mediante doble llave. La segunda forma no funcionará.
<?php echo e($nombre ?? 'Invitado'); ?> // ver función e() de PHP
$catalogo = [
['producto' => 'Harina de trigo'],
['producto' => 'Arroz parvorizado'],
['producto' => 'Chocolate en polvo'],
['producto' => 'Café liofilizado'],
['producto' => 'Queso campesino']
];
Route::view('/', 'inicio', ['nombre' => 'Carlos'])->name('inicio');
Route::view('/acercade', 'acercade')->name('acercade');
Route::view('/catalogo', 'catalogo',
compact('catalogo')
)->name('catalogo');
Route::view('/contacto', 'contacto')->name('contacto');
También se ha agregado a catalogo.blade.php el siguiente código para mostrar los
elementos del array en una lista:
@extends('plantilla')
@section('titulo')
Catálogo
@endsection
@section('contenido')
<h1>Catálogo de productos</h1>
<ul>
<?php
foreach ($catalogo as $itemCatalogo) {
echo "<li>" . $itemCatalogo['producto'] . "</li>";
}
?>
</ul>
@endsection
Si ejecuta el código anterior, como es de esperarse, el contenido del array se muestra ahora
en una lista no ordenada.
En el código anterior, el código PHP que lleva a cabo la iteración, se puede reemplazar por
el siguiente código que mezcla Blade y PHP:
Comente ahora el contenido del array para ejemplificar el uso condiciones, inicialmente para
saber qué hacer si el array está vacío:
@if ($catalogo)
@foreach ($catalogo as $itemCatalogo)
<li> {{ $itemCatalogo['producto'] }} </li>
@endforeach
@else
<li>No hay productos para mostrar</li>
@endif
Y finalmente para condicionar el caso en que la variable del array no ha sido definida:
<ul>
@isset ($catalogo)
@if ($catalogo)
@foreach ($catalogo as $itemCatalogo)
<li> {{ $itemCatalogo['producto'] }} </li>
@endforeach
@else
<li>No hay proyectos para mostrar</li>
@endif
@else
<li>Catálogo no definido</li>
@endisset
</ul>
<ul>
@isset ($catalogo)
@forelse($catalogo as $itemCatalogo)
<li> {{ $itemCatalogo['producto'] }} </li>
@empty
<li>No hay proyectos para mostrar</li>
@endforelse
@else
<li>Catálogo no definido</li>
@endisset
</ul>
Existe una variable $loop disponible dentro de cada bucle @foreach. Esta variable es de
tipo stdClass y proporciona metainformación sobre el bucle en el que se encuentra
actualmente. Para echar un vistazo al tema, cambie la parte resaltada del código anterior
por lo siguiente:
El resultado devuelto por var_dump o print_r, será similar al siguiente, para la primera
iteración:
object(stdClass)#214 (10) {
["iteration"] => int(1)
["index"] => int(0)
["remaining"] => int(4)
["count"] => int(5)
["first"] => bool(true)
["last"] => bool(false)
["odd"] => bool(true)
["even"] => bool(false)
["depth"] => int(1) // nivel de profundidad
["parent"]=> NULL
}
La importancia de la información proporcionada por este objeto, radica en que podría, por
ejemplo, formater de manera diferente el primero o último elemento de una lista o cebrarla
fácilmente. Ejemplo, usando el operador condicional:
$loop→last ? ...
$loop→first ? ...
Controladores
Nota: si usa Visual Studio Code (en adelante VSC) active la terminal (menú Terminal >
Nueva Terminal o Ctrl+Mayúsculas+ñ) , la necesitaremos:
Del listado que se visualiza, vale la pena resaltar que en la columna “Action”, se muestra el
controlador ViewController.php que se ejecuta cuando se utiliza el método view en las
rutas, el cual se encuentra en ../vendor/laravel/framework/src/Illuminate/Routing.
Tenga en cuenta que éste y todos los componentes de Laravel están bajo el namespace
Illuminate.
namespace App\Http\Controllers;
Esto ayuda a mantener el principio de responsabilidad única de los patrones SOLID, ya que
si una clase implementa el método __invoke() está declarando de forma implícita cual es la
acción, funcionalidad o responsabilidad de dicha clase.
Volviendo a nuestro controlador recien creado, vemos que ahora quedó de la siguiente
manera:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
3. Pruebe la aplicación y los resultados deberán ser los mismos que obtuvo en
anteriores implementaciones.
Vamos a crear un controlador que provea un grupo de rutas de recursos con peticiones de
tipo index, create, show, edit, store, update y destroy. Estas peticiones son ampliamente
usadas y para no tener que crear una ruta para cada método, es que Laravel agrupa todos
éstos en un controlador de tipo resource. El significado de estos métodos es el siguiente:
En los siguientes pasos se mostrarán brevemente otros aspectos sobre los que se
profundizará luego.
Route::resource('catalogo', 'CatalogoController');
6. Guarde y ejecute:
Observe que se generan las 7 rutas documentadas al inicio (index, create, ...).
7. Con el siguiente comando se generan sólo las rutas indicadas:
Route::resource('catalogo', 'CatalogoController')->only([
'index', 'show'
]);
Route::resource('catalogo', 'CatalogoController')->except([
'index', 'show'
]);
Route::apiResource('catalogo', 'CatalogoController');
Note que efectivamente se obtienen los mismos resultados. Por supuesto también se
tienen a disposición los métodos only y except.
Route::resource('catalogo', 'CatalogoController');
use Illuminate\Support\Facades\Route;
Como excusa para continuar con los temas fundamentales, enseguida se plantea un
ejemplo trivial de cómo activar un enlace de navegación e indicar la ruta de navegación. No
se preocupe si se empieza a embeber código dentro de HTML, luego se indicará como
mantener debidamente separado el código CSS, HTML, JavaScript y PHP.
<head>
...
<style>
.activo a {
color: red;
text-decoration: none;
}
</style>
</head>
<body>
<nav>
<ul>
<li class="activo"><a href="/">Inicio</a></li>
<li><a h
ref="/acercade">Acerca de...</a></li>
<li><a h ref="/catalogo">Catálogo</a></li>
<li><a h ref="/contacto">Contacto</a></li>
</ul>
</nav>
@yield('contenido')
</body>
</html>
2. HTTP define un conjunto de métodos de petición (request) para indicar la acción que
se desea realizar para un recurso determinado.Esto se maneja en Laravel mediante
una instancia de la clase Request. Eseguida vamos a usar la función request() que
ara ver algunas
devuelve una nueva instancia de Illumitate/Http/Request, p
características de dicho objeto:
<body>
<nav>
<pre> {{ request() }} </pre>
<ul>
<li class="activo"><a href="/">Inicio</a></li>
<li><a h
ref="/acercade">Acerca de...</a></li>
<li><a h ref="/catalogo">Catálogo</a></li>
<li><a h ref="/contacto">Contacto</a></li>
</ul>
</nav>
@yield('contenido')
</body>
3. Pruebe ahora el resultado que devuelve el método url() del objeto Request:
{{ dump(request()->url()) }}
"http://appxx.test/contacto"
4. Pruebe también el método path( ) para ver sólo la carpeta actual y no la ruta
completa:
{{ dump(request()->path()) }}
5. Y uno más, el método routeIs() que devuelve t rue si el argumento dado corresponde
a una ruta. Con este método es posible resaltar la ruta seleccionada actualmente.
Veamos:
Pruebe que efectivamente esta lógica funciona para pasar a optimizar dicho código,
mediante el uso de una función.
<?php
function seleccionado($nombreEnlace) {
return request()->routeIs($nombreEnlace) ? 'activo' : '';
}
8. Ahora es necesario indicar a Composer que además de las clases, también se debe
cargar el archivo del paso 6 para poder utilizarlo en cualquier ruta. Así que modifique
el elemento “autoload” del archivo ../composer.json de tal manera que se agregue
la entrada que se resalta enseguida:
"autoload": {
"psr-4": {
"App\\": "app/"
},
"classmap": [
"database/seeds",
"database/factories"
],
"files": ["app/helpers.php"]
}
composer dumpautoload
Como la navegación de una aplicación web puede ser algo que cambie
constantemente, de ahora en adelante, vamos a mantenerla separada.
<body>
@include('partials/nav')
@yield('contenido')
</body>
Envío de formularios
Hasta aquí se tiene una aplicación en la que se ha trabajado con la siguiente estructura:
● Appxx
○ Http
■ Controllers
● CatalogoController.php
■ helpers.php
○ resources
■ views
● partials
○ nav.blade.php
● acercade.blade.php
● catalogo.blade.php
● contacto.blade.php
● inicio.blade.php
● plantilla.blade.php
○ routes
■ web.php
○ composer.json
@extends('plantilla')
@section('titulo', 'Contacto')
@section('contenido')
<h1>Contacto</h1>
<form>
<input name="nombre" placeholder="Nombre..."><br>
<input type="email" name="correo" placeholder="Correo..."><br>
<input name="asunto" placeholder="Asunto..."><br>
<textarea name="contenido" placeholder="Mensaje..."></textarea><br>
<button>Enviar</button>
</form>
@endsection
Si prueba a usar un formulario como el que se le pide implementar, notará que los datos
envíados se muestran en la barra de dirección, lo que indica que por defecto se están
enviando por GET. Cambie para enviar los datos por POST:
<form method="post">
Si hecho este cambio, intenta enviar de nuevo los datos, obtendrá el “error The POST
method is not supported for this route. Supported methods: GET, HEAD”, esto sucede
porque en web.php, para el formulario de contacto sólo se ha definido la ruta:
Route::view('/contacto', 'contacto')->name('contacto');
Y view, registra rutas de tipo GET. Así que proceda con los siguientes pasos para adecuar
este formulario para que funcione mediente el método POST:
2. Agregue en ../routes/web.php una nueva ruta para indicar que se utilizará un nuevo
controlador que haga persistibles los datos:
Route::post('contacto', 'MensajesController@store');
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class MensajesController extends Controller {
}
5. Intente ejecutar de nuevo. Obtendrá un error 419 “Page Expired”. Esto sucede
porque Laravel protege automáticamente contra ataques XSS, un tipo de ataque de
suplantación de identidad que inyectta código malicioso para su posterior ejecución.
Esto se corrige agregando siempre la directiva @csrf que agrega un campo oculto
con el token de usuario y que Laravel verifica automáticamente:
Ahora veamos las formas de acceder a los datos que el usuario ingresó en el
formulario:
6. Modifique el método del paso 4 para devolver un objeto JSON con los datos del
formulario:
{
"_token": "xawl6QZrFHmAJHtkFdjKglkT19aOzXPyNFSjBuBa",
"nombre": "Carlos",
"correo": "carlos.cuesta@ucaldas.edu.co",
"asunto": "Prueba de Request",
"contenido": "Devolviendo datos correctamente"
}
Importante: tenga en cuenta que el atributo name es requerido en los formularios ya
que si no se incluye, los datos no serán enviados.
7. Si sólo requiere el dato de un campo, use el método get del objeto Request.
Ejemplo:
<?php
namespace App\Http\Controllers;
class MensajesController extends Controller {
Este curso no incluye validación de datos del lado del cliente usando HTML y JavasScript,
pero si aborda el tema de las validaciones datos del lado del servidor.
<?php
namespace App\Http\Controllers;
}
Pruebe este cambio, notará que cuando no se ingresa el nombre o el correo, se regresa al
formulario y que en caso contrario, se muestra el mensaje “datos válidos”.
Laravel proporciona la variable $error que se puede utilizar para conocer el estado de los
errores. Para ver su funcionamiento, inclúyala en el formulario de contacto,
../resources/views/contacto.blade.php, justo en la parte que se indica en la siguiente
porción de código:
@section('contenido')
<h1>Contacto</h1>
{{ $errors }}
También se puede verificar mediante $errors->any() si existe o no existe algún error.
Ejemplo:
@section('contenido')
<h1>Contacto</h1>
{{ var_dump($errors->any()) }}
O también, con una variación del caso anterior, mostrar errores si los hay:
@section('contenido')
<h1>Contacto</h1>
@if($errors->any())
{{ var_dump($errors->all()) }}
@endif
<form method="post" action={{ route('contacto') }}>
...
</form>
@endsection
@if($errors->any())
@foreach($errors->all() as $error)
<p> {{ $error }} </p>
@endforeach
@endif
@extends('plantilla')
@section('titulo', 'Contacto')
@section('contenido')
<h1>Contacto</h1>
Incluso puede experimentar a dar formato a cada uno de los mensajes, enviando como
segundo parámetro una estructura HTML:
<form method="post" action={{ route('contacto') }}>
@csrf
<input name="nombre" placeholder="Nombre..."><br>
{!! $errors->first('nombre', '<small>:message</small>') !!} <br>
<input type="email" name="correo" placeholder="Correo..."><br>
{!! $errors->first('correo', '<small>:message</small>') !!} <br>
<input name="asunto" placeholder="Asunto..."><br>
{!! $errors->first('asunto', '<small>:message</small>') !!} <br>
<textarea name="contenido" placeholder="Mensaje..."></textarea><br>
{!! $errors->first('contenido', '<small>:message</small>') !!} <br>
<button>Enviar</button>
</form>
Cuando las validaciones son muy complejas, puede ser conveniente usar una clase
especial de Laravel llamada Form Request que permiten separar la lógica de validación de
datos (validación, mensajes de errores, autorización de usuarios y redirección en caso de
fallar) de la lógica del controlador. Esta clase intercepta la solicitud o request y valida los
datos que vienen de una petición HTTP antes de pasar al controlador.
Producto::create($campos);
return redirect()->route('productos.index');
}
2. Suponga también que se quiere mostrar los errores de validación al usuario. Una
forma rápida de hacerlo, sería incluir la validación en la plantilla que muestra el
formulario: ..\resources\views\productos\create.blade.php
@extends('plantilla')
@section('titulo', 'Agregar producto')
@section('contenido')
<h1>Agregar nuevo producto</h1>
@if($errors->any())
<ul>
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endif
@endsection
Form Request nos provee otra manera de hacer las validaciones. Veamos:
Esta instrucción creará un form request con el nombre dado como argumento, en
reateProductoRequest.php.
este caso ..\app\Http\Requests\C
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
/**
* Determina si el usuario está autorizado para hacer el request
* Si las reglas incluidas aquí se producirá un error 403,
* indicando que no hay autorización para realizar la acción.
* @ return bool
*/
public function authorize() {
return true; // <-- OJO cualquier usuario está autorizado
}
/**
* D efina aquí las reglas de validación que se aplican al request.
* @ return array
*/
public function rules() {
return [
'nombre' => 'required',
'precio' => 'required',
'iva' => 'required',
'cantidad_disponible' => 'required'
...
];
}
<?php
namespace App\Http\Controllers;
pp\Producto;
use A
use I lluminate\Http\Request;
use A pp\Http\Requests\CreateProductoRequest;
Pruebe que todo sigue funcionando como antes pero utilizando ahora el form
request.
Importante:
● Verifique que sucede si en vez de {!! … !!}, utiliza {{ … }} y consulte qué relación
tienen estas dos notaciones con la inyección de código.
● También realice una mejora para el usuario, haga que los datos se conserven
cuando el formulario no pase la validación, para evitar que el usuario tenga que
volverlos a llenar todos:
<button>Enviar</button>
</form>
En esta sección se estudiarán algunos temas complementarios a lo visto hasta aquí. Para
ello se recomienda que cree una copia de la aplicación y sobre la copia, a su vez cree un
nuevo controlador de pruebas:
1. Estando en la carpeta de la aplicación utilice el siguiente comando:
2. De manera general un helper es una función de ayuda que realiza una tarea o
procedimiento específico y que su uso puede repetirse a lo largo de nuestro
proyecto. Laravel ofrece algunos helpers que son de gran utilidad en nuestras
aplicaciones. Algunos de ellos están definidos en el archivo
..\vendor\laravel\framework\src\Illuminate\Foundation\helpers.php. De este archivo
nos interesa la función response() cuya cabecera es la siguiente:
Route::get('/', 'OtrasPruebasController@inicio')->name('inicio');
4. Observe los resultados y compruebe que puesto que el tercer argumento es de tipo
array, puede enviar varias cabeceras.
5. Una sintaxis alternativa y mucho más clara para el envío de cabeceras y de cookies
es la que se indica en el siguiente ejemplo:
Dele una mirada a la información oficial del tema y compruebe que tanto las dos
cabeceras como el cookie son recibidos por el navegador (http://appxx.test/).
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
}
Con el nuevo método lo que se pretende es ver la respuesta que el servidor da
cuando se ejecuta la aplicación (appxx.test/contacto) y se pulsa clic en enviar. por lo
tanto haga el cambio que se indica a continuación:
<?php
Route::get('/', 'OtrasPruebasController@inicio')->name('inicio');
Route::view('/acercade', 'acercade')->name('acercade');
Route::get('/catalogo', 'CatalogoController@index')->name('catalogo');
contacto')->name('contacto');
Route::view('contacto', '
Route::post('contacto', ' OtrasPruebasController@respuestaContacto');
Esta función retorna una instancia de la clase Redirector con una respuesta y un
estado 302, que significa redirección.
@extends('plantilla')
Contacto')
@section('titulo', '
@section('contenido')
<h1>Contacto</h1>
@if(session()->has('info'))
<h3> {{ session('info') }} </h3>
@endif
@endsection
Laravel tiene un middleware para la autenticacion, para quitar los espacios en blanco, para
verificar si tenemos el token csrf, para encriptar las cookies y otros más. Con esto nos
podemos dar cuenta de lo importante que puede ser usar meiddlewares… (ver más...)
Para mayor claridad antes de continuar, es muy importante lea la información de los
vínculos que se le proporcionan en los enlaces de los párrafos anteriores y que además
observe cómo están implementados los middlewares que se proporcionan en la carpeta
..\vendor\laravel\framework\src\Illuminate\Foundation\Http\Middleware\.
Como pudo ver casi todos los Middlewares tiene un método llamado handle. Observe por
ejemplo el de la clase VerifyCsrfToken que es el encargado de verificar el token csrf
agregado al formulario de contacto mediante la directiva @csrf.
2. Modifique el método handle del middleware creado en el paso 1, para que quede
así:
5. Una de las formas de usar un middleware es en los controladores, así que abra el
archivo ..\app\Http\Controllers\OtrasPruebasController.php y agregue al inicio de la
clase el siguiente constructor con la instrucción necesaria para ejecutar el
middleware:
Tenga en cuenta que de esta manera, el middleware “ejemplo” se aplica a todos los
métodos del controlador OtrasPruebasController.php.
request()->validate([
'nombre' => 'required',
'correo' => 'required|email', // también soporta format array
'asunto' => 'required',
'contenido' => ['required', 'min:3'],
], [
'nombre.required' => 'No acepto anónimos',
'contenido.min' => 'Sea breve en los mensajes pero no tanto',
]);
<?php
App::setLocale('es');
Route::post('contacto', 'MensajesController@store');
Envío de correos
En esta sección se utilizará el formulario de contacto para hacer que nos llegue un correo
cada vez que el usuario envíe los datos del formulario.
1. Iniciamos con la creación de una clase Mailable, un tipo de clase que se utiliza para
representar cada tipo de correo enviado:
Ejecute el comando php artisan y observe que dentro del namespace make, está
el comando make:mail (Create a new email class). Utilice dicho comando
para crear una clase para representar cada tipo de email enviado por la aplicación.
Ejemplo:
2. En la carpeta emails, cree también una vista para el contenido del correo, llamada
mensajes-recibidos.
Para este paso debe crear una carpeta ../resources\views\emails y dentro de ella
crear el archivo mensajes-recibidos.blade.php con cualquier contenido de prueba.
<?php
namespace App\Http\Controllers;
request()->validate([
'nombre' => 'required',
'correo' => 'required|email',
'asunto' => 'required',
'contenido' => ['required', 'min:3'],
], [
'nombre.required' => 'No acepto anónimos',
'contenido.min' => 'Sea breve pero no tanto',
]);
Mail::to('cc.1591570@ucaldas.edu.co')
6. Como en el paso anterior se indica que el driver es log, el correo en vez de enviarse
se guardará en ../storage/logs/laravel-xxxx.log.
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'cc@misitio.com'),
'name' => env('MAIL_FROM_NAME', 'Carlos Cuesta'),
],
MAIL_DRIVER=log
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=cc@misitio.com
MAIL_FROM_NAME='Carlos Cuesta I.'
<?php
...
9. Para acceder a los mensajes, hay que realizar algunas adiciones. Empiece por los
cambios que se resaltan en ../Http/Controllers/MensajesController.php:
class MensajesController extends Controller {
$mensaje = request()->validate([
'nombre' => 'required',
'correo' => 'required|email',
'asunto' => 'required',
'contenido' => ['required', 'min:3'],
], [
'nombre.required' => 'No acepto anónimos',
'contenido.min' => 'Sea breve pero no tanto',
]);
Mail::to('carlos.cuesta@ucaldas.edu.co')->send(
new MensajesRecibidos($mensaje));
10. Según los cambios del punto 9, también hay que realizar cambios en la clase
../app/Mail/MensajesRecibidos.php, para que el constructor de esta clase reciba el
argumento que se está enviando desde el controlador:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Mensajes de contactos</title>
</head>
<body>
Contenido
{{ var_dump($mensaje) }}
</body>
</html>
<!DOCTYPE html>
<html lang="es">
<head> ... </head>
<body>
<p>Recibiste un mensaje de:
nombre'] }} -
{{ $mensaje['
{{ $mensaje[' correo'] }}
</p>
<p><strong>Asunto:</strong> {{ $mensaje['asunto'] }} </p>
<p><strong>Contenido:</strong> {{ $mensaje['contenido'] }} </p>
</body>
</html>
13. Una de las formas para visualizar el correo en el navegador consiste en modificar
../app/Http/Controllers/MensajesController.php para que el método store() retorne
la instancia de la clase MensajesRecibidos. Ejemplo:
public function store() {
$mensaje = request()->validate([
'nombre' => 'required',
'correo' => 'required|email',
'asunto' => 'required',
'contenido' => ['required', 'min:3'],
], [
'nombre.required' => 'No acepto anónimos',
'contenido.min' => 'Sea breve pero no tanto',
]);
MailTrap proporciona otra forma de probar envíos de correos sin llenar nuestras cuentas:
1. Si no tiene una cuenta en este sitio, créela ahora y realice en dicho sitio, la
configuración básica que se le solicita.
2. En el archivo ./.env, actualice las entradas que se resaltan, teniendo en cuenta los
valores proporcionados por MailTrap:
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=06aaaaaaaaaafd
MAIL_PASSWORD=a6bbbbbbbbbbb4
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=cc@misitio.com
MAIL_FROM_NAME='Carlos Cuesta I.'
4. Si desea realizar otras pruebas, por ejemplo con una cuenta de Gmail (como su
cuenta de correo de estudiante de la U. de Caldas), puede seguir las indicaciones
que se dan aquí.
Las migraciones son un mecanismo proporcionado por Laravel que permite el control de
versiones sobre los cambios en la estructura de nuestra base de datos, así las acciones de
crear, modificar o eliminar la estructura de una base de datos será una tarea más sencilla
que si se utiliza un gestor de bases de datos. Miremos de qué se trata:
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=tiendadb
DB_USERNAME=postgres
DB_PASSWORD=XXXXXXXXX
Importante: cada nuevo proyecto, por defecto tiene un archivo .env con los datos de
configuración necesarios para el mismo, cuando utilizamos un sistema de control de
versiones como GIT, por seguridad, este archivo se excluye del repositorio.
C:\Program Files\PostgreSQL\11\bin
Nota: también puede usar pgAdmin o cualquier otro gestor para llevar a cabo este
paso.
5. Por defecto, Laravel trae dos clases para el manejo de migraciones, una para crear
la tabla de los usuarios y otra para el reinicio de contraseñas.
Toda migración tiene los métodos up() y down(). Revise la estructura de estas
clases alojadas en ../database/migrations/ y aproveche para familiarizarse con los
métodos que definen los tipos de datos utlizados, que en realidad son alias, lo cual
puede comprobar dando control Ctrl+Clic para ver la definición de los mismos.
6. Una vez creadas las migraciones hay que ir a la ventana de comandos y sobre la
carpeta del proyecto ejecutar el comando:
Adicional a las tablas creadas, también se crea una tabla migrations que guarda el
registro de las migraciones ejecutadas.
Al ejecutar este comando puede verificar en la tabla migrations que las filas
correspondientes a la creación realizada en el paso 6, fueron eliminadas.
Suponga que la tabla de usuarios ya tiene datos y que se requiere agregar el teléfono a la
misma. Para ello es necesario crear una migración adicional que permita hacer el cambio:
1. Ejecute el comando:
3. Ejecute de nuevo:
php artisan migrate
Si se desea que el campo se agregue luego de una columna específica (sólo MySQL
por lo que veo) se puede utilizar la sintaxis del siguiente ejemplo:
$table->string('phone')->after('email')->nullable();
Tenga en cuenta que este comando re-crea toda la estructura de la base de datos.
8. Por último, use un gestor de base de datos para observar la estructura creada.
Importante:
● Al utilizar el comando php artisan make:migration nombre_archivo, asegúrese
de elegir un nombre representativo que luego le ayude a recordar lo que hace. Esto
facilitará cualquier consulta que requiera sobre la tabla migrations que se crea en las
bases de datos con el fin de gestionar las migraciones.
../database/migrations/2018_03_27_040953_add_phone_to_users_table.php
Suponga que la tabla de users ya tiene datos y que se requiere agregar la dirección a dicha
tabla.
1. Ejecute el siguiente comando para crear una clase que permita agregar o quitar
campos de una tabla:
3. Ejecute el comando:
Para mayor claridad, la gráfica siguiente muestra la estructura con la que debe
quedar la tabla users:
Nota: se pueden crear campos de llaves primarias de otros tipos, con la sintaxis:
$table->string('code', 30)->primary();
Este y otros temas como las llaves foráneas y los índices compuestos, están ampliamente
ilustrados en la sección de migraciones de la documentación oficial y sería conveniente que
practicara sobre dichos conceptos. La sugerencia es la siguiente:
● Tome como referente este modelo de base de datos para practicar migraciones.
● Empiece por las tablas de color verde, continúe luego con las de color amarillo,
hasta llegar a las de color naranja.
● Revise en pgAdmin o en cualquier otro gestor de Postgres los resultados que vaya
obteniendo.
3. Verifque que PHP tiene asociadas las extensiones para trabajar con Postgres:
4. Actualice el archivo ../app/Http/Controllers/CatalogoController.php para que quede
con un único método index() implementado de la siguiente manera:
Importante: recuerde que la notación \DB indica que se está importando la clase DB
y que esto es equivalente a la instrucción use DB; agregada antes de la cabecera
de la clase que se está implementando.
5. Puesto que las filas de la tabla se obtienen como objetos de tipo stdClass, hay que
hacer un cambio en ../resources/views/catalogo.blade.php para acceder a las
propiedades de los objetos. En el código que se incluye enseguida se resalta el
cambio realizado. Basicamente hay que tener en cuenta que para acceder a los
elementos de un array asociativo se utiliza la notación $nombreArray['item'] y
que para acceder a las propiedades de un objeto se usa la notación
$nombreObjeto->propiedad:
@extends('plantilla')
@section('titulo')
Catálogo
@endsection
@section('contenido')
<h1>Catálogo de productos</h1>
<ul>
@isset ($catalogo)
@forelse($catalogo as $itemCatalogo)
<li> {{ $itemCatalogo->nombre }} </li>
@empty
<li>No hay productos para mostrar</li>
@endforelse
@else
<li>Catálogo no definido</li>
@endisset
</ul>
@endsection
Por supuesto también puede visualizar los múltiples campos de cada fila:
Antes de continuar adelante con otros talleres, se sugiere que consulte sobre arquitectura
REST. También es importante que consulte sobre las fortalezas y debilidades de la
implementación REST con Query Builder y con Eloquent. En particular, recomiendo esta
discusión al respecto.
1. Cree una migración que permita crear el esquema para la tabla “mensajes”.
Como puede ver, dichos campos, corresponden a los del formulario que se utilizó
para el envío de correos en un taller anterior.
Nota: para continuar con el siguiente taller, se requiere que haya llevado a cabo estos
pasos.
Se conoce como RESTful a los servicios web que ejecutan la arquitectura REST. Para
empezar, como siempre, la recomendación es:
Si le asalta alguna duda sobre estos tres asuntos, lo mejor es que consulte este articulo
antes de dar el primer paso.
2. Ejecute el comando:
4. De la lista proporcionada por la ayuda, nos sirve la opción -r o --resource que
genera un clase controladora RESTful con los siete métodos necesarios para la
implementación de un recurso, entendiendo recurso como la “información” (página,
sección, archivo, tabla, ...) a la que se quiere acceder. Así que ejecute el comando:
Comprueba que efectivamente se crea de nuevo el controlador, ahora con los siete
métodos RESTful, debidamente parametrizados y con las dependencias necesarias.
RESTful
HTTP URL/Ruta Acción del controlador Respuesta
Formulario de
GET /mensajes/crear MensajesController@create
creación
Guarda mensajes y
POST /mensajes MensajesController@store
redirecciona
Un mensaje
GET /mensajes/{id} MensajesController@show
específico
PUT/
Actualiza mensajes y
PATC /mensajes/{id} MensajesController@update
redirecciona
H
Elimina mensajes y
DEL /mensajes/{id} MensajesController@destroy
redirecciona
5. En el archivo ..\routes\web.php, cree una ruta con nombre que responda a la URL
/mensajes/create, de la segunda fila de la tabla:
<?php
Route::get('mensajes/crear', 'MensajesController@create')
->name('crear-mensaje');
<nav>
<li class="{{ seleccionado('inicio') }}">
<a href="/">Inicio</a>
</li>
use DB;
use Carbon\Carbon;
Route::post('mensajes', 'MensajesController@store')
->name('guardar-mensaje');
12. Implemente el método store para guarde en la tabla “mensajes” los datos del
formulario y por ahora, simule una redirección:
13. Cree una nueva ruta en ..\routes\web.php para mostrar todos los mensajes:
Route::get('mensajes', 'MensajesController@index')
->name('ver-mensajes');
14. Cambie la instrucción return en el método store del controlador por la versión que se
incluye enseguida, que redirecciona a la ruta creada en el punto anterior:
return redirect()->route('ver-mensajes');
16. Para mostrar todos los registros de la tabla, el método index del constructor que se
viene implementando, debe acceder a los registros de la tabla “mensajes” y pasarlos
a la vista index. Así que implemente dicho método como se muestra enseguida:
17. Ahora ya la vista index tiene acceso a los registros de la tabla “mensajes”, entonces
proceda a crear en dicha vista, una tabla que muestre los registros enviados por el
controlador:
@extends('plantilla')
<tbody>
@foreach ($mensajes as $mensaje)
<tr>
<td>{{ $mensaje->nombre}}</td>
<td>{{ $mensaje->email}}</td>
<td>{{ $mensaje->asunto}}</td>
<td>{{ $mensaje->contenido}}</td>
<td>Editar Eliminar</td>
</tr>
@endforeach
</tbody>
</table>
@endsection
Route::get('mensajes/{id}', 'MensajesController@show')
->name('buscar-mensaje');
@extends('plantilla')
@section('titulo', 'Mensaje')
@section('contenido')
<h1>Mensajes</h1>
<p> Enviado por {{ $mensaje->nombre }} -
{{ $mensaje->email }}</p>
<p> {{ $mensaje->contenido }} </p>
@endsection
Route::get('mensajes/{id}/editar', 'MensajesController@edit')
->name('editar-mensaje');
@extends('plantilla')
Mensaje')
@section('titulo', '
@section('contenido')
<h1>Mensaje</h1>
27. La fila 6 de la tabla RESTful, “actualizar mensajes y redireccionar” es una acción que
se implementa en el método update del controlador, de la siguiente manera:
appxx.test/mensajes/6/editar
Route::delete('mensajes/{id}', 'MensajesController@destroy')
->name('eliminar-mensaje');
29. Implemente en el controlador el método para eliminar mensajes:
<td>
<a href="{{ route('editar-mensaje', $mensaje->id) }}">
Editar</a>
<form style="display:inline"
method="POST"
action="{{ route('eliminar-mensaje',
$mensaje->id) }}">
@csrf
{!! method_field('DELETE') !!}
<button type="submit">Eliminar</button>
</form>
</td>
Eloquent - El ORM de Laravel
Mediante Eloquent, Laravel puede mapear los datos que se encuentran en la base de
datos a objetos de PHP y viceversa (Mapeo Objeto-Relacional - ORM). Esto permite
elaborar un código portable en el que no hace falta usar SQL dentro de las clases de PHP.
Como todo el Laravel, Eloquent usa namespaces y puede usar la especificación PSR-4 para
facilitar la carga de dependencias, aspecto que cobra mucha importancia de aquí en
adelante, dado que Eloquent funciona a partir de modelos que facilitan el trabajo con tablas.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
Es de anotar que con esta clase, Eloquet referencia automáticamente a una tabla
cuyo nombre se corresponde con el nombre del modelo, pero en minúsculas y en
plural, en nuestro caso: “productos”.
Es importante tener en cuenta que los nombres de los modelos no utilizan guiones y
que en su defecto se usa la notación CamelCase.
<?php
namespace App\Http\Controllers;
use App\Producto;
class CatalogoController extends Controller {
public function index() {
$catalogo = Producto::get();
return view('catalogo', compact('catalogo'));
}
}
5. Prueba ahora a obtener las filas en el orden inverso en que fueron insertadas:
$catalogo = Producto::latest()->get();
7. Sobre las fechas es importante anotar que estas son recibidas como instancias de
Carbon, una librería especializada en manejo de fechas. Pruebe por ejemplo a
modificar el archivo ../resources/views/catalogo.blade.php, para que los elementos
de lista muestren la fecha de creación de las filas:
<li>
{{ $itemCatalogo->id }}-{{ $itemCatalogo->nombre }} <br>
{{ $itemCatalogo->created_at }}
</li>
$itemCatalogo->created_at->format('Y')
$itemCatalogo->created_at->format('m')
$itemCatalogo->created_at->format('Y-m-d')
$itemCatalogo->created_at->diffForHumans()
Como podrá notar son links funcionales pero carentes de un estilo que puede dar
mediante CSS puro o mediante un Framework de Front-End. Más adelante nos
ocuparemos de esa parte.
@isset ($productos)
@forelse($productos a s $producto)
<li>
{{ $producto->id }}-{{ $producto->nombre }} <br>
</li>
@empty
<li>No hay productos para mostrar</li>
@endforelse
{{ $productos->links() }}
@else
<li>Catálogo no definido</li>
@endisset
Consulta de datos específicos con Eloquent
Se recomienda que para continuar tenga instalada en su navegador una extensión para
formatear archivos JSON. Para Chrome, puede usar JSON Formatter, para Mozilla
JSONView. Ah y no olvide que es bueno trabjar sobre una copia del proyecto...
En este taller se mostrará como consultar los datos específicos de cada producto, dando
clic sobre los elementos del listado de productos y haciendo la búsqueda según su
identificador.
@forelse($productos as $producto)
<li>
<a href="{{ route('producto.show', $producto) }}">
{{ $producto->nombre }}</a>
</li>
@empty
<li>No hay productos para mostrar</li>
@endforelse
Como se puede ver, el enlace llevará a una ruta inexistente aún, llamada
producto.show. El segundo argumento de la función route() recibe como argumento
la instancia de la iteración actual, correspondiente al producto que se quiere mostrar.
$producto->getRouteKey()
2. Ahora hay que modificar ../routes/web.php para indicar las rutas especificadas en el
paso 1:
<?php
App::setLocale('es');
Route::view('/', 'inicio')->name('inicio');
Route::view('/acercade', 'acercade')->name('acercade');
Route::get('/catalogo',
'CatalogoController@index')->name('catalogo.index');
Route::get('/catalogo/{id}',
'CatalogoController@show')->name('producto.show');
Route::view('/contacto', 'contacto')->name('contacto');
Route::post('contacto', 'MensajesController@store');
Observe que se ha incluido una nueva ruta que recibe un parámetro {id}, dicho
parámetro corresponde al segundo argumento utilizado en el llamado que se hizo en
el paso 1 a la función route().
El controlador pasado como segundo argumento del método get() sigue siendo
CatalogoController y el método que se va a implementar para mostrar los datos del
producto, es show().
Si prueba la aplicación ahora, deberían generarse los enlaces para cada elemento
del listado de productos, pero al pulsar clic sobre un enlace se generará un error
indicando que el método show() no existe en el controlador:
4. Para ver en formato JSON los datos del producto que corresponden al ID, se puede
llamar all método find() de la clase Producto (recuerde que dicha clase está definida
en ../app/Producto.php). Por lo tanto el método show cambiaría a:
id: 3,
nombre: "Lipton Green Tea",
precio: "1200.00",
iva: "0.16",
cantidad_disponible: 12,
cantidad_minima: 3,
cantidad_maxima: 20,
created_at: "2018-05-01 13:01:27",
updated_at: "2018-05-01 13:01:27"
}
@extends('plantilla')
@section('contenido')
<h1> {{ $producto->nombre }} </h1>
Precio del producto $ {{ $producto->precio }}
<br>
Cantidad disponible: {{ $producto->cantidad_disponible }}
<br>
Cantidad mínima: {{ $producto->cantidad_minima }}
<br>
Cantidad máxima: {{ $producto->cantidad_maxima }}
<br><br>
@endsection
Pruebe para ver los resultados.
8. Puesto que estamos accediendo por GET, intente visualizar un producto inexistente,
por ejemplo http://appxxxx.test/catalogo/999
Nota: si desea modificar la vista que muestra el error 404, proceda de la siguiente
manera:
Observe que ahora tanto en el método show() como en el método index(), el nombre
del método es análogo al nombre de la vista que devuelve. Esto sin duda alguna
facilita el mantenimiento de la aplicación.
5. Otra recomendación que debe tenerse en cuenta es que los nombre de las clases
deben ser en singular, así que también cambie el nombre del archivo y de la clase
..\app\Http\Controllers\MensajesController.php, por MensajeController.
<li>
<a href="{{ route('productos.show', $producto) }}">
{{ $producto->nombre }}
</a>
</li>
<?php
App::setLocale('es');
Route::view('/', 'inicio')->name('inicio');
Route::view('/acercade', 'acercade')->name('acercade');
Route::get('/catalogo',
'ProductoController@index')->name('productos.index');
Route::get('/catalogo/{id}',
'ProductoController@show')->name('productos.show');
Route::view('/contacto', 'contacto')->name('contacto');
Route::post('contacto', 'MensajeController@store')->name('mensajes.store');
<nav>
<li class="{{ seleccionado('inicio') }}">
<a href="{{ route('inicio') }}">Inicio</a>
</li>
<li class="{{ seleccionado('acercade') }}">
<a href="{{ route('acercade') }}">Acerca de...</a>
</li>
<li class="{{ seleccionado('productos.index') }}">
<a href="{{ route('productos.index') }}">Productos</a>
</li>
<li class="{{ seleccionado('contacto') }}">
<a href="{{ route('contacto') }}">Contacto</a>
</li>
</nav>
Recuerde que hacer referencia a los nombre de las rutas y no a las URL, permite
cambiar las URL sin afectar la navegabilidad.
ontacto.blade.php,
9. Uno de los cambios del punto 7, afecta a ..\resources\views\c
donde simplementa hay que actualizar la ruta:
Pruebe que todo siga funcionando y pruebe también a cambiar la forma como se
selecciona la siguiente ruta en el punto 8. En este caso se usa un comodín (*) para
indicar que “cualquier subruta” debe mostrarse activa.
</li>
<?php
App::setLocale('es');
Route::view('/', 'inicio')->name('inicio');
Route::view('/acercade', 'acercade')->name('acercade');
Route::get('/catalogo',
'ProductoController@index')->name('productos.index');
Route::get('/catalogo/{producto}',
'ProductoController@show')->name('productos.show');
...
Si prueba los cambios, todo debería seguir funcioado correctamente dado que mediante
esta técnica se búsca tambien por el ID.
appxxx.test/catalogo/Doritos
Lo anterior debería dar como resultado que se muestren en pantalla los datos del
producto dado como parámetro. Hasta la versión anterior, tal búsqueda sólo era
posible si se daba el ID del producto.
3. Si tiene migraciones para la creación de la tabla productos, que no tengan una
implementación similar a la del punto 4, elimínelas. Sólo debe quedar una como la
del punto 4.
4. Abra ..\database\migrations\AAAA_MM_DD_122110_create_productos_table.php, la
migración que contiene el método que crea la tabla Productos y modifique dicho
método para agregar la nueva columna que se resalta en el código siguiente:
7. Actualice de nuevo ell método getRouteKeyName() para que permita buscar por la
URL:
8. Pruebe a una búsquede desde la barra de direcciones, basada ésta en una URL.
Ejemplo:
http://appxxx.test/catalogo/de-todito
Inserción de registros
En este taller se mostrará cómo insertar registros utilizando Eloquent y lo más importante,
utilizando el diseño de arquitectura de software REST.
@extends('plantilla')
@section('titulo', 'Agregar producto')
@section('contenido')
@csrf
<label>
Nombre del producto <br>
<input type="text" name="nombre">
</label>
<br>
<label>
Precio <br>
<input type="text" name="precio">
</label>
<br>
<label>
IVA <br>
<input type="text" name="iva">
</label>
<br>
<label>
Cantidad disponible <br>
<input type="text" name="cantidad_disponible">
</label>
<br>
<label>
Cantidad mínima <br>
<input type="text" name="cantidad_minima">
</label>
<br>
<label>
Cantidad máxima <br>
<input type="text" name="cantidad_maxima">
</label>
<br>
<label>
URL <br>
<input type="text" name="url">
</label>
<br><br>
<button>Guardar</button>
</form>
@endsection
2. Ahora hay que agregar en ..\routes\web.php, una ruta que devuelva la vista creada:
<?php
App::setLocale('es');
Route::view('/', 'inicio')->name('inicio');
Route::view('/acercade', 'acercade')->name('acercade');
Route::get('/catalogo',
'ProductoController@index')->name('productos.index');
Route::get('/catalogo/{producto}',
'ProductoController@show')->name('productos.show');
Route::view('/contacto', 'contacto')->name('contacto');
Route::post('contacto',
'MensajeController@store')->name('mensajes.store');
Method App\Http\Controllers\ProductoController::store
does not exist.
Al inspeccionar los elementos del formulario recordamos que la directiva @csrf,
agrega un campo oculto con un token de validación. Veamos:
_token: "OswCpnwzbfyAs6Rn0S9TbQrOnftyvhA0FGd0rp5X",
nombre: null,
precio: null,
iva: null,
cantidad_disponible: null,
cantidad_minima: null,
cantidad_maxima: null,
url: null
}
use Illuminate\Http\Request;
namespace App;
use Illuminate\Database\Eloquent\Model;
En este caso la instrucción return del método store() es opcional y se usa aquí, sólo
para poder verificar en el navegador, los datos que fueron insertados en la tabla.
Veamos un ejemplo:
{
"nombre": "Jet Wafer",
"precio": "5000",
"iva": "0.19",
"cantidad_disponible": "15",
"cantidad_minima": "5",
"cantidad_maxima": "20",
"url": "jet-wafer",
"updated_at": "2019-06-14 15:55:59",
"created_at": "2019-06-14 15:55:59",
"id": 7
}
@extends('plantilla')
@section('contenido')
<h1>Catálogo de productos</h1>
<ul>
...
</ul>
@endsection
return redirect()->route('productos.index');
}
Importante: cuando los datos que llegan al controlador tienen los mismos nombres
que las columnas de la tabla, puede evitarse la asignación uno a uno como se hizo
en esta versión y en su lugar utilizar el método all() del objeto request para hacer la
asignación masiva. Ejemplo:
public function store() {
Producto::create(request()->all());
return redirect()->route('productos.index');
}
Recuerde entonces que la asignación masiva consiste en enviar una matriz a la creación del
modelo, básicamente estableciendo un grupo de campos en el modelo de una sola vez, en
lugar de uno por uno.
Esto evita que un usuario pueda modificar el formulario y enviar un campo adicional que no
se quiere editar. Esto lo puede hacer un usuario accediendo al código fuente de la página y
editando el HTML.
Además de la propiedad fillable existe la propiedad guarded que hace lo opuesto de fillable,
es decir que en el array que se asigne se pueden agregar los campos que no se desean
utilizar masivamente. Ejemplo:
Es decir que cualquier campo inexistente en este array, podrá ser utilizado en la inserción.
La protección dada por la acción masiva se puede deshabilitar si se hace una asignación
como esta: $protected $guarded = []; siempre y cuando se implemente otra protección.
Otra forma de solución muy óptima, ya vista por cierto, es utilizar la validación que
automáticamente pasa sólo el array de campos validados. Ejemplo:
Conclusión: se puede deshabilitar la protección que viene por defecto en Laravel, siempre
y cuando no se use request()->all().
Este taller retoma los conceptos de REST y RESTful, tratados en el taller “Implementacion
de arquitectura REST con Query Builder”, pero vistos desde la óptica de lo ya dicho de
Eloquent, por tanto, se recomienda que trabaje sobre una copia de dicho proyecto.
1. Verifque que su proyecto tenga las siguientes rutas definidas, que corresponden a la
arquitectura REST.
Puede tener más, pero lo importante es que incluya las 7 rutas que muestra la figura.
Route::resource('mensajes', 'MensajesController');
Observará que obtiene un listado de rutas muy similar al anterior, salvo las rutas con
nombres y otras dos pequeñeces.
use App\Mensaje;
}
Como puede ver, se pasan uno a uno los campos y no requiere pasar las fechas de
creación y actualización.
La segunda forma es asumiendo que desde el formulario se envían todos los datos
necesarios para crear el registro:
Mensaje::create($request->all());
Perilita: existe una función dd() (“Dump and Die” / “volcar y morir”) en Laravel que le
puede ayudar en la depuración. Llame esta función en una línea entre los métodos
create() y redirect(), para inspeccionar el contenido del argumento $request:
dd($request->all());
10. Para el método edit del controlador el asunto es sencillo y similar al punto anterior:
<nav>
<li class="{{ seleccionado('inicio') }}">
<a href="{{ route('inicio') }}">Inicio</a>
</li>
<li class="{{ seleccionado('acercade') }}">
<a href="{{ route('acercade') }}">Acerca de...</a>
</li>
<li class="{{ seleccionado('catalogo') }}">
<a href="{{ route('catalogo') }}">Catálogo</a>
</li>
<li class="{{ seleccionado('mensajes.create') }}">
<a href="{{ route('mensajes.create') }}">Contacto</a>
</li>
Las referencias se han cambiado todas a rutas con nombres y se han incluido dos
condiciones, la primera para verificar si hay un usuario autenticado, en cuyo caso se
muestra el enlace a los mensajes de los clientes y se permite cerrar la sesión, y la
segunda para verificar si es un usuario invitado, en cuyo caso se muestra el enlace
para autenticarse, pero no el enlace de los mensajes.
5. También actualice el archivo ..\routes\web.php para que queden las siguientes rutas:
<?php
Route::resource('mensajes', 'MensajesController');
Route::get('login', 'Auth\LoginController@showLoginForm')
->name('login');
Route::post('login', 'Auth\LoginController@login');
Route::get('logout', 'Auth\LoginController@logout')->name('logout');
Observe que aparecen tres nuevas rutas provistas por un controlador que ha pasado
desapercibido hasta ahora: ..\app\Http\Controllers\Auth\LoginController.php:
<?php
namespace App\Http\Controllers;
class CatalogoController extends Controller {
@extends('plantilla')
@section('titulo')
Login
@endsection
@section('contenido')
<h1>Página de inicio</h1>
<form method="POST" action="login">
@csrf
<input type="email" name="email" placeholder="Correo">
<input type="password" name="password" placeholder="Contraseña">
<input type="submit" value="Ingresar">
</form>
<br>
@endsection
8. Actualice el atributo $redirectTo de ..\app\Http\Controllers\Auth\LoginController.php
para establecer la ruta a la que debe ir luego de la autenticación:
function __construct() {
$this->middleware('auth', ['except' => ['create', 'store']]);
}
Un poco de Front-End
Voy a darles un respiro con la programación del lado del back-end para mirar algunos
aspectos de la presentación.
Si procedió como se recomendó al principio y utiliza Laragon para este curso, notará que a
nivel de la carpeta de la aplicación hay un archivo llamado package.json que es donde se
definen las dependencias o módulos de Node.JS, que también se instaló junto a Laravel.
Así como Laravel viene con un manejador de paquetes llamado Composer, Node.JS viene
con uno llamado NPM, que se utilizará para instalar algunas dependencias necesarias.
Para comprobar que tanto Node como su gestor de paquetes está instalado, puede hacerlo
desde la terminal, como se indica en la siguiente gráfica:
Ahora sí empecemos:
npm install
Compruebe que terminada la instalación se ha creado una carpeta
..\node_modules\. Esta carpeta es análoga a la carpeta vendor de Composer.
Laravel Mix provee al usuario de una potente y versátil API que le permitirá definir de
forma rápida y sencilla el procesado de CSS y JavaScript, entre otras cosas.
Revise el archivo ..\webpack.mix.js que es donde se definen todas las tareas que se
llevarán a cabo cuando Laravel Mix se ejecute. Este archivo es un archivo de
configuración para definir cómo y en qué orden se irán ejecutando las tareas
definidas. Ver más...
La primera línea del archivo hace referencia a Laravel mix que es el encargado de
proporciona todo lo necesario para hacer uso del compilador Webpack.
La primera tarea referenciada es un archivo SASS que debe ser compilado y que se
encuentra en ..\resources\sass\
Enseguida vamos a realizar unas pruebas con CSS para entender cómo se lleva a
cabo este proceso, no para continuar proporcionando estilos de esa manera...lo que
se haga ahora se descartará más adelante.
// Fonts
@import url('https://fonts.googleapis.com/css?family=Nunito');
// Variables
@import 'variables';
ootstrap
// B
// @import '~bootstrap/scss/bootstrap';
$color: #0E6655;
nav {
list-style-type: none;
width: 70%;
display: flex;
}
nav>li {
flex: 1;
}
.activo a {
color: $color;
text-decoration: none;
}
.error {
color: red;
font-size: 12px
}
5. Ejecute el comando:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>@yield('titulo', 'Tienda')</title>
<link rel="stylesheet" href="/css/app.css">
</head>
<body>
...
</body>
</html>
Los enlaces deberían resaltarse con los ligeros cambios correspondientes a los
estilos del paso 4.
Nota: use npm run watch para supervisar los archivos y ejecutar todas las tareas
definidas en el archivo ..\webpack.mix.js.
Esto le evitará estar ejecutando el comando npm run dev, por cada cambio.
8. Suponga, sólo suponga, que desea cambiar de SASS a less. Por definición, los
archivos less deben ir en una carpeta con el mismo nombre, así que se crearía este
nuevo estilo como ..\resources\less\app.less, con un contenido muy similar al del
archivo sass:
@color: #0E6655;
nav {
list-style-type: none;
...
}
...
10. Modifique el archivo ..\resources\sass\app.scss para que sólo quede con estas
líneas:
// F onts
@import url('https://fonts.googleapis.com/css?family=Nunito');
// V ariables
@import 'variables';
// B ootstrap
@import '~bootstrap/scss/bootstrap';
Es todo lo que necesitaremos por ahora.
11. Actualice también el archivo ..\app\helpers.php para que la única función que tiene,
quede retornando el nombre de una clase de Bootstrap que cumpla con la misma
función:
< ?php>
function seleccionado($nombreEnlace) {
return request()->routeIs($nombreEnlace) ?
'nav-item active' : 'nav-item';
}
13. Un snippet para VSC como el de Bootstrap 4 de Ashok Koyi, realmente ayuda a
ganar tiempo cuando de la capa de presentación se trata.
14. Ahora se quiere que el formulario de contacto también se vea mejorado. Ejemplo:
Esta versión del formulario la puede descargar de aquí.
17. Ahora sí, mejore las demás vistas que queden faltando y cuando termine de mejorar
el estilo de la aplicación, pare el supervisor (^C) y ejecute el comando:
@import url('https://fonts.googleapis.com/css?family=Nunito');
@import 'variables';
@import '~bootstrap/scss/bootstrap';
@import '/resources/componentes/navbarligth';
@import '/resources/componentes/frm-dialog;
Si ejecuta el comando npm run dev (combinar todo) o el comando o npm run production
(combinar todo y minimizar archivos) y luego revisa el contenido del archivo compilado
..\public\css\app.css, notará que las importaciones aparecen al final de dicho archivo.
Sin embargo no es una buena idea, llenar de parámetros, a la función sass o a cualquier
otra del objeto mix. Mejor dele una mirada a Laravel Mix y a la sección de front-end de
Laravel donde se proporciona información importante sobre el tema.
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
...
<link rel="stylesheet" href="/css/app.css">
<!-- en lo posible use async o defer para hacer carga diferida -->
<script async src="/js/app.js"></script>
</head>
<script>
let arr = ['Uno', 'Dos', 'Tres', 'Cuatro', '...'];
$.each(arr, function (index, value) {
console.log(value);
});
</script>
Nuestros propios script JS. Otra prueba que es importante realizar con Mix es la
siguiente:
mix.js('resources/js/app.js', 'public/js')
.js('resources/js/components/prueba.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css');
3. Incluirlo en ..\resources\js\app.js
require('./bootstrap');
require('./components/prueba');
window.Vue = require('vue');
// …
4. Compilar (npm run dev) y probar que el script hace lo que tiene que hacer.
mix.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css')
.browserSync({
proxy: 'http://appxx.test/', // ruta de su proyecto
});
3. Realice algún cambio en la aplicación y guarde. Si todo salió bien, los cambios se
reflejarán automáticamente en el navegador.
4. Observe que por consola se muestra información sobre las URLs de acceso. Si tiene
dispositivos conectados a la misma red, también puede monitorear la aplicación por
la URL externa que se proporciona.
Nota: al implementar el CRUD para usuarios tenga que es antiético e inseguro guardar las
contraseñas sin encriptar, por lo tanto debe utilizar la función bcrypt() para encriptarlas.
ANEXOS
Una noticia que deben leer los que me preguntan por qué
prefiero a Postgres
https://platzi.com/blog/el-apagon-de-platzi-migramos-de-mysql-a-postgresql/
Recomendados
En esta sección se irán agregando las referencias que los compañeros de curso
recomiendan. Se les agradece mucho estas contribuciones que, sin duda, permitirán
ampliar el campo de conocimientos: