Arranquemos poniendo en la terminal, en cualquier carpeta que querramos, el siguiente comando:
composer create-project laravel/laravel repaso
Tengamos en cuenta que este comando, va a crear una carpeta dentro de la carpeta donde estemos trabajando actualmente. Por ejemplo, si estamos en la carpeta
php
y creamos el proyectorepaso
, se va a crear la carpetarepaso
dentro dephp
que es donde vamos a trabajar.
http://repaso.nicorodrigues.com.ar/repaso.sql
http://repaso.nicorodrigues.com.ar/repasoConDni.sql
git clone https://github.com/nicorodrigues/repasoLaravel
cd repasoLaravel
composer install
cp .env.example .env
php artisan key:generate
Intentamos levantar el servidor para ver si no hubo problemas
cd repaso
php artisan serve
Si no se puede entrar por alguna razón, lo más probable es que haya habido un error en la creación del .env, por eso cambiamos el .env.example a .env y generamos la key
cp .env.example .env
php artisan key:generate
Intentamos levantar el servidor de nuevo
php artisan serve
Si entramos a la dirección que nos muestra la consola, podemos ver la vista por defecto de laravel:
http://127.0.0.1:8000 - o su equivalente - http://localhost:8000
Si quisieramos que laravel se levante en otro puerto, podemos especificarselo cuando lo levantamos
php artisan serve --port 8080
Si sale todo bien, cerramos nuestro servidor (vamos a la consola y apretamos ctrl+c) Usando el archivo del campus, importamos la base de datos en el workbench.
Configuramos nuestro archivo .env con los datos de la base de datos:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=repaso
DB_USERNAME=root
DB_PASSWORD=
La contraseña es la misma que usamos para entrar al workbench (generalmente no tiene password o es root)
Volvemos a levantar el servidor sabiendo que todo está en orden... Borramos nuestra ruta default en el archivo web.php y arrancamos de 0 con una ruta que use get:
Route::get('/', 'HomeController@index');
Creamos el controlador necesario:
php artisan make:controller HomeController
Dentro del controlador, generamos el método index, el cual declaramos que ibamos a usar en la ruta '/':
public function index() {
return view('index');
}
Teniendo el sistema ruta-controlador armado, nos falta la vista! La creamos a mano con el formato 'nombreDeVista.blade.php' en la carpeta resources/views:
index.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style media="screen">
.contenedor {
display: flex;
width: 500px;
}
.rawr {
width: 50%;
margin-top: 110px;
}
.dino {
width: 50%;
}
</style>
</head>
<body>
<div class="contenedor">
<div class="rawr">
<marquee>RAWR RAWRRRR</marquee>
<marquee>RAWR RAWRRRR</marquee>
</div>
<div class="dino">
<pre>
.-=-==--==--.
..-==" ,'o`) `.
,' `"' \
: ( `.__...._
| ) / `-=-.
: ,vv.-._ / / `---==-._
\/\/\/VV ^ d88`;' / `.
`` ^/d88P!' / , `._
^/ !' ,. , / "-,,__,,--'""""-.
^/ !' ,' \ . .( ( _ ) ) ) ) ))_,-.\
^(__ ,!',"' ;:+.:%:a. \:.. . ,' ) ) ) ) ,"' '
',,,'',' /o:::":%:%a. \:.:.: . ) ) _,'
"""' ;':::'' `+%%%a._ \%:%| ;.). _,-""
,-='_.-' ``:%::) )%:| /:._,"
(/(/" ," ,'_,'%%%: (_,'
( (//(`.___; \
\ \ ` `
`. `. `. :
\. . .\ : . . . :
\. . .: `.. . .:
`..:.:\ \:...\
;:.:.; ::...:
):%:: :::::;
__,::%:( :::::
,;:%%%%%%%: ;:%::
;,--""-.`\ ,=--':%:%:\
/" "| /-".:%%%%%%%\
;,-"'`)%%)
/" "|
</pre>
</div>
</div>
</body>
</html>
Como sabemos que vamos a trabajar con una base de datos de productos, empezamos por armar los modelos correspondientes necesarios...
php artisan make:model Product -> Esto correspondería a la tabla products
php artisan make:model Category -> Esto correspondería a la tabla categories
php artisan make:model Property -> Esto correspondería a la tabla properties
Ojo, siempre tener en cuenta que el modelo por convención se escribe con la primera letra mayúscula y en singular. Mostramos los productos de la base de datos siguiendo el sistema ruta-controlador-vista:
Route::get('/productos', 'ProductsController@index');
Creamos el controlador:
php artisan make:controller ProductsController
Creamos el método para la ruta:
public function index() {
$products = \App\Product::all();
$variables = [
"products" => $products,
];
return view('products.index', $variables);
}
Imprimimos los productos en la vista!
index.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" href="/css/app.css">
</head>
<body>
<ul>
@foreach ($products as $product)
<li>{{$product->name}}</li>
@endforeach
</ul>
</body>
</html>
Este foreach nos muestra el producto, de ahí podemos sacar los datos que querramos. En este caso $producto es una collection (lo cual es un array potenciado, de la mano de eloquent) y nos permite acceder a sus propiedades de la misma forma que accedemos a atributos de un objeto.
Si nosotros quisieramos ver un solo producto, armaríamos el sistema de forma parecida.
Arrancamos con la ruta:
Route::get('/productos/{id}', 'ProductsController@show');
Utilizamos
{id}
para tomar como parámetro el ID necesario y enviarselo a la función show, la cual nos va a devolver la vista con el producto elegido. En el caso de no encontrar un producto, nos devuelveFalse
Aprovechando el hecho de que queremos ser específicos con lo que vamos a mostrar, le agregamos al modelo Product
una función que nos permita saber el precio total de un producto (cost
+ profit_margin
)
public function getPrice() {
return $this->cost + ($this->cost * $this->profit_margin / 100);
}
El
profit_margin
es un porcentaje decost
que se agrega al costo base para generar el precio final.
Seguimos con el método necesario:
public function show($id) {
$product = \App\Product::find($id);
$variables = [
"product" => $product,
];
return view('products.show', $variables);
}
Y por último, como corresponde, armamos la vista!!
show.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{$product->name}}</title>
<link rel="stylesheet" href="/css/app.css">
</head>
<body>
<h1>{{$product->name}}</h1>
<p>{{$product->cost}}</p>
<p>{{$product->profit_margin}}</p>
<p>{{$product->getPrice()}}</p>
</body>
</html>
Ya que tenemos una forma de listar los productos y otra forma que nos permite ver un producto concreto, vamos a permitir que cada uno de los productos de nuestra lista, nos lleve a su descripción. Para esto, lo único que tenemos que hacer es modificar la vista del listado:
index.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" href="/css/app.css">
</head>
<body>
<ul>
@foreach ($products as $product)
<li><a href="/productos/{{$product->id}}">{{$product->name}}</a></li>
@endforeach
</ul>
</body>
</html>
Ahora... hace falta agregar productos! Para esto vamos a hacer el recorrido de nuevo, pero esta vez necesitamos 2 rutas! get y post
Route::get('/productos/agregar', 'ProductsController@create');
Route::post('/productos/agregar', 'ProductsController@store');
Tengamos en cuenta que al poner estas rutas y teniendo la ruta anterior
/productos/{id}
, si entramos a/productos/agregar
vamos a estar pasando la palabraagregar
como{id}
. Para solucionar esto, ambas rutas las ponemos ANTES de la ruta/productos/{id}
.
La primera ruta nos va a permitir ir al formulario que vamos a usar para guardar los productos!
public function create() {
return view('products.create');
}
Creamos el formulario!
create.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" href="/css/app.css">
</head>
<body>
<div class="container">
<h1>Agregar Productos</h1>
<form class="col-md-5" action="/productos/agregar" method="post">
{{ csrf_field() }}
<div class="form-group">
<label for="name">Nombre</label>
<input type="text" name="name" id="name" value="" class="form-control">
</div>
<div class="form-group">
<label for="cost">Precio</label>
<input type="text" name="cost" id="cost" value="" class="form-control">
</div>
<div class="form-group">
<label for="profit_margin">Ganancias</label>
<input type="text" name="profit_margin" id="profit_margin" value="" class="form-control">
</div>
<div class="form-group">
<label for="category_id">Categoría</label>
<input type="text" name="category_id" id="category_id" value="" class="form-control">
</div>
<div class="form-group">
<button type="submit" name="button" class="btn btn-primary">Enviar</button>
</div>
</form>
</div>
</body>
</html>
Nunca olvidarse, cuando se crea un formulario de agregar el
{{ csrf_field() }}
para que Laravel nos permita recibir los datos pasando su validación de seguridad.
La segunda ruta nos va a validar y guardar los datos!
public function store(Request $request) {
$rules = [
"name" => "required|unique:products",
"cost" => "required|numeric",
"profit_margin" => "required|numeric",
"category_id" => "required|numeric|between:1,3"
];
$messages = [
"required" => "El :attribute es requerido!",
"unique" => "El :attribute tiene que ser único!",
"numeric" => "El :attribute tiene que ser numérico!",
"between" => "El :attribute tiene que estar entre :min y :max!"
];
$request->validate($rules, $messages);
$producto = \App\Product::create([
'name' => $request->input('name'),
'cost' => $request->input('cost'),
'profit_margin' => $request->input('profit_margin'),
'category_id' => $request->input('category_id')
]);
return redirect('/productos');
}
Si tenemos un error en nuestra validación, Laravel se encarga de volvernos al formulario y entregarnos los errores en una variable llamada casualmente $errors
, lo que nos queda es encargarnos de mostrar sus contenidos! Para esto, vamos a modificar el formulario que teníamos:
create.blade.php
<form class="col-md-5" action="/productos/agregar" method="post">
{{ csrf_field() }}
<div class="form-group">
<label for="name">Nombre</label>
<input type="text" name="name" id="name" value="{{old('name')}}" class="form-control">
@if ($errors->has('name'))
<div class="alert alert-danger">
<ul>
@foreach ($errors->get('name') as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
<div class="form-group">
<label for="cost">Costo</label>
<input type="text" name="cost" id="cost" value="{{old('cost')}}" class="form-control">
@if ($errors->has('cost'))
<div class="alert alert-danger">
<ul>
@foreach ($errors->get('cost') as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
<div class="form-group">
<label for="profit_margin">Ganancias</label>
<input type="text" name="profit_margin" id="profit_margin" value="{{old('profit_margin')}}" class="form-control">
@if ($errors->has('profit_margin'))
<div class="alert alert-danger">
<ul>
@foreach ($errors->get('profit_margin') as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
<div class="form-group">
<label for="category_id">Categoría (1-3):</label>
<input type="text" name="category_id" id="category_id" value="{{old('category_id')}}" class="form-control">
@if ($errors->has('category_id'))
<div class="alert alert-danger">
<ul>
@foreach ($errors->get('category_id') as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
<div class="form-group">
<button type="submit" name="button" class="btn btn-primary">Enviar</button>
</div>
</form>
Hay que recordar que para que la función "create" funcione, necesitamos declarar alguna de las siguientes variables en el modelo elegido:
protected $fillable = [ "columnasPermitidas" ]; protected $guarded = [ " columnasProhibidas" ];
Ya tenemos la posibilidad de guardar datos en la base de datos...
Ahora, qué pasa si queremos agregarle categorías a nuestros productos...?
Nosotros ya trajimos cosas de la base de datos, pero todavía no jugamos con las categorías o las propiedades.
En el caso de las categorías, vamos a estar teniendo una relación de "uno a muchos".
En este caso estaríamos trabajando con la columna category_id
de la tabla products
que referencia a la columna id
de categories
. Es decir, cada categoría va a tener muchos productos, pero cada producto solamente va a tener 1 categoría, por eso los productos van a ser los que digan en sus datos, a qué categoría coresponden category_id
.
Es decir: products.category_id
--→ categories.id
Para esto vamos a agregar las relaciones correspondientes en ambos modelos, utilizando belongsToMany
(pertenece a muchos) y hasMany
(tiene muchos):
Al producto le agregamos su relación con la categoría, en su caso sería con belongsTo
public function category() {
return $this->belongsTo('\App\Category', 'category_id', 'id');
}
El orden de parametros es:
1. Modelo de los datos que se van a traer
2. Foreign Key de ese modelo apuntando al actual
3. Primary Key del modelo actual
A la categoría le agregamos su relación con los productos, en su caso sería con hasMany
public function products() {
return $this->hasMany('\App\Product', 'category_id', 'id');
}
El orden de parametros es:
1. Modelo de los datos que se van a traer
2. Foreign Key de ese modelo apuntando al actual
3. Primary Key del modelo actual
Una vez seteados ambas funciones, ya podemos llamar a sus productos/categoria como si fuera un atributo más.
En el caso que quisieramos obtener la categoría de un producto lo haríamos de la siguiente forma:
$product->category
En el caso de querer obtener los productos de una categoría sería de la misma forma:
$category->products
Vamos a modificar nuestro sistema para que podamos ver la categoría de un producto, por lo cual, vamos a empezar por editar el controlador de productos:
public function show($id) {
$product = \App\Product::find($id);
$variables = [
"product" => $product,
"category" => $product->category
];
return view('products.show', $variables);
}
Una vez modificado el controlador, pasamos a modificar la vista para mostrar la categoría:
show.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{$product->name}}</title>
</head>
<body>
<h1>{{$product->name}}</h1>
<p>{{$product->cost}}</p>
<p>{{$product->getPrice()}}</p>
<p>{{$category->name}}</p>
</body>
</html>
Fiuuu... Ya terminamos con las categorías!! El tema es que todavía nos quedan propiedades por aplicarles a los productos... Si vemos nuestra base de datos, las propiedades se encuentran en su propia tabla, separada de los productos y categorias. El problema está en que ni la tabla de productos ni la tabla de propiedades tienen Foreign Key relacionadolas.
Para esto, tenemos una tabla llamada product_property que contiene 2 columnas:
- product_id
- property_id
En este caso vamos a necesitar especificarle a Laravel que lo que tenemos es una relación de muchos a muchos, ya que muchas propiedades pueden relacionarse con muchos productos y muchos productos pueden relacionarse con muchas propiedades.
Arranquemos por decirle al modelo de productos que va a tener muchas propiedades agregandole la siguiente función:
public function properties() {
return $this->belongsToMany('\App\Property', 'product_property', 'product_id', 'property_id');
}
El orden de parametros es:
1. Modelo de los datos que se van a traer
2. Tabla que une a ambos modelos
3. Foreign Key del modelo actual
4. Foreign Key del otro modelo
De la misma forma que le implementamos esa función al modelo de Productos, le implementamos una parecida al modelo Propierty!
public function products() {
return $this->belongsToMany('\App\Product', 'product_property', 'property_id', 'product_id');
}
El orden de parametros es:
1. Modelo de los datos que se van a traer
2. Tabla que une a ambos modelos
3. Foreign Key del modelo actual
4. Foreign Key del otro modelo
Ya tenemos las relaciones de muchos a muchos creadas! Solo nos queda editar nuestro sistema para poder trabajar con las mismas.
Arrancamos como siempre por el método:
public function show($id) {
$product = \App\Product::find($id);
$variables = [
"product" => $product,
"category" => $product->category,
"properties" => $product->properties
];
return view('products.show', $variables);
}
Nos vamos para la vista y hacemos un foreach ya que tenemos que estar preparados para más de una propiedad:
show.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{$product->name}}</title>
<link rel="stylesheet" href="/css/app.css">
</head>
<body>
<h1>{{$product->name}}</h1>
<p>{{$product->cost}}</p>
<p>{{$product->getPrice()}}</p>
<p>{{$category->name}}</p>
<p>Propiedades:</p>
<ul>
@foreach ($properties as $property)
<li>{{$property->name}}</li>
@endforeach
</ul>
</body>
</html>
Ya podemos traer datos de la base de datos usando 3 tablas... Y si queremos agregar datos...?
Puede que al principio parezca complicado, o incluso asuste, pero añadir datos utilizando más de una tabla, no es nada difícil.
Cuando tenemos una relación como la de Product
y Category
en la cual una categoría puede tener más de un producto, nos referimos a una relación de Uno a Muchos, para la cual vamos a usar la función llamada associate()
la cual nos permite asociar un producto a una categoría.
Empezamos modificando la función que devuelve la vista del formulario para que además le lleve las categorías, de esta forma vamos a obligar a que seleccionen de nuestra lista!
public function create() {
$categories = \App\Category::all();
$variables = [
"categories" => $categories,
];
return view('products.create', $variables);
}
Una vez modificada la función, modificamos la vista para que efectivamente tengamos un campo para seleccionar la categoría:
create.blade.php
<form class="col-md-5" action="/productos/agregar" method="post">
{{ csrf_field() }}
<div class="form-group">
<label for="name">Nombre</label>
<input type="text" name="name" id="name" value="{{old('name')}}" class="form-control">
@if ($errors->has('name'))
<div class="alert alert-danger">
<ul>
@foreach ($errors->get('name') as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
<div class="form-group">
<label for="cost">Costo</label>
<input type="text" name="cost" id="cost" value="{{old('cost')}}" class="form-control">
@if ($errors->has('cost'))
<div class="alert alert-danger">
<ul>
@foreach ($errors->get('cost') as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
<div class="form-group">
<label for="profit_margin">Margen de Ganancias</label>
<input type="text" name="profit_margin" id="profit_margin" value="{{old('profit_margin')}}" class="form-control">
@if ($errors->has('profit_margin'))
<div class="alert alert-danger">
<ul>
@foreach ($errors->get('profit_margin') as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
<div class="form-group">
<select name="category_id">
@foreach ($categories as $category)
<option value="{{$category->id}}">{{$category->name}}</option>
@endforeach
</select>
</div>
<div class="form-group">
<button type="submit" name="button" class="btn btn-primary">Enviar</button>
</div>
</form>
Modificamos el método para guardar productos con su correspondiente categoría usando los métodos provistos por Laravel:
public function store(Request $request) {
$rules = [
"name" => "required|unique:products",
"cost" => "required|numeric",
"profit_margin" => "required|numeric",
"category_id" => "required|numeric|between:1,3"
];
$messages = [
"required" => "El :attribute es requerido!",
"unique" => "El :attribute tiene que ser único!",
"numeric" => "El :attribute tiene que ser numérico!",
"between" => "El :attribute tiene que estar entre :min y :max."
];
$request->validate($rules, $messages);
$product = \App\Product::create([
'name' => $request->input('name'),
'cost' => $request->input('cost'),
'profit_margin' => $request->input('profit_margin'),
]);
$category = \App\Category::find($request->input('category_id'));
$product->category()->associate($category);
$product->save();
return redirect('/productos');
}
associate()
nos pide como parametro un objeto del tipo que querramos asociar a nuestro producto, en este caso le pasamos un objeto del tipocategory
.
Parece mucho trabajo, no? (╯°□°)╯︵ ┻━┻
Si comparamos todo lo que hicimos hasta ahora con lo que sabemos php plano, nos damos cuenta que Laravel nos permite hacer todo de forma mucho más sencilla.
Ahora vamos con algo más interesante pero igual de fácil. Cuando manejemos 3 tablas, Laravel nos provee de la función sync()
.
Como venimos haciendo, arrancamos modificando el método que devuelve la vista:
public function create() {
$categories = \App\Category::all();
$properties = \App\Property::all();
$variables = [
"categories" => $categories,
"properties" => $properties,
];
return view('products.create', $variables);
}
Seguimos nuestro camino, esta vez hacia la vista!
create.blade.php
<form class="col-md-5" action="/productos/agregar" method="post">
{{ csrf_field() }}
<div class="form-group">
<label for="name">Nombre</label>
<input type="text" name="name" id="name" value="{{old('name')}}" class="form-control">
@if ($errors->has('name'))
<div class="alert alert-danger">
<ul>
@foreach ($errors->get('name') as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
<div class="form-group">
<label for="cost">Costo</label>
<input type="text" name="cost" id="cost" value="{{old('cost')}}" class="form-control">
@if ($errors->has('cost'))
<div class="alert alert-danger">
<ul>
@foreach ($errors->get('cost') as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
<div class="form-group">
<label for="profit_margin">Margen de Ganancia</label>
<input type="text" name="profit_margin" id="profit_margin" value="{{old('profit_margin')}}" class="form-control">
@if ($errors->has('profit_margin'))
<div class="alert alert-danger">
<ul>
@foreach ($errors->get('profit_margin') as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
<div class="form-group">
<select name="category_id">
@foreach ($categories as $category)
<option value="{{$category->id}}">{{$category->name}}</option>
@endforeach
</select>
</div>
<div class="form-group">
@foreach ($properties as $property)
<label for="property{{$property->id}}">{{$property->name}}</label>
<input type="checkbox" name="properties[]" value="{{$property->id}}"
id="property{{$property->id}}">
@endforeach
</div>
<div class="form-group">
<button type="submit" name="button" class="btn btn-primary">Enviar</button>
</div>
</form>
Haciendo un
foreach
de$properties
, podemos crear una lista decheckbox
que nos permita elegir las propiedades que queremos en nuestro producto.
Con el formulario ya preparado, vamos al método que recibe los datos para decirle que además nos guarde las propiedades del producto!
public function store(Request $request) {
$input = $request->except('_token');
$rules = [
"name" => "required|unique:products",
"cost" => "required|numeric",
"profit_margin" => "required|numeric"
];
$messages = [
"required" => "El :attribute es requerido!",
"unique" => "El :attribute tiene que ser único!",
"numeric" => "El :attribute tiene que ser numérico!"
];
$validator = Validator::make($input, $rules, $messages);
$product = \App\Product::create([
'name' => $request->input('name'),
'cost' => $request->input('cost'),
'profit_margin' => $request->input('profit_margin')
]);
$category = \App\Category::find($request->input('category'));
$product->properties()->sync($request->input('properties'));
$product->category()->associate($category);
$product->save();
return redirect('/productos');
}
Para sincronizar nuestras propiedades, seleccionamos al producto y le aclaramos que queremos utilizar la función
sync()
asociada a nuestra funciónproperties()
. A su vez, le pasamos como parámetro async()
, un array con losid
de cada una de las properties que queremos agregarle.
Como nosotros utilizamos un
foreach
para generar loscheckbox
, nos aseguramos que cada uno tenga en suname
el string"properties[]"
, lo que nos devuelve un array que contiene losid
de cada una de las seleccionadas.
De esta forma ya tenemos nuestro sistema completo. Podemos agregar y ver productos con sus categorías y propiedades correspondientes!
Ahora que tenemos la posibilidad de crear y ver productos. Qué pasa si queremos eliminarlos?
Ambos sistemas de relaciones se manejan de formas diferentes, las relaciones de unos a muchos son relativamente fácil de eliminar.
Como siempre, empezamos por crear la ruta!
Route::delete('/productos/{id}', 'ProductsController@destroy');
Hay algo que llama la atención, tenemos un
delete
como método en la ruta. Esto es uno de los nuevos métodos de envío que vamos a usar en Laravel. Si biendelete
sigue siendopost
, Laravel sabe distinguirlo de unpost
común.
Sigamos por crear el controlador:
public function destroy($id) {
$product = \App\Product::find($id);
$product->delete();
return redirect('/productos');
}
Para poder usar el método delete
, vamos a modificar nuestra ruta show
de forma que nos permita borrar un producto que querramos.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{$product->name}}</title>
<link rel="stylesheet" href="/css/app.css">
</head>
<body>
<h1>{{$product->name}}</h1>
<p>{{$product->cost}}</p>
<p>{{$product->getPrice()}}</p>
<p>{{$category->name}}</p>
<p>Propiedades:</p>
<ul>
@foreach ($properties as $property)
<li>{{$property->name}}</li>
@endforeach
</ul>
<form action="/productos/{{$product->id}}" method="post">
{{ csrf_field() }}
{{ method_field('delete') }}
<button type="submit">Borrar</button>
</form>
</body>
</html>
Como se ve, para trabajar con métodos fuera de
get
ypost
, tenemos que mandar un formulario a través depost
y agregarle, primero elcsrf_field
como siempre y además una nueva función llamadamethod_field
al cual le pasamos como parámetro el verdadero método que queremos utilizar.
Ahora... tenemos un problema... Si nosotros eliminamos un producto que tiene propiedades, estas propiedades no se eliminan. Qué podemos hacer para modificar esto?
Simplemente vamos a la función destroy
y agregamos una linea:
public function destroy($id) {
$product = \App\Product::find($id);
$product->properties()->sync([]);
$product->delete();
return redirect('/productos');
}
Simplemente agregando el
sync()
con un array vacío, Laravel se encarga de eliminar las propiedades correspondientes.
Para la edición de datos, vamos a hacer como con la creación de datos y pasamos a crear 2 rutas nuevas:
Route::get('/productos/{id}/edit', 'ProductsController@edit');
Route::patch('/productos/{id}', 'ProductsController@update');
Como vimos con la eliminación de datos, no hay mucha diferencia del proceso entre las diferentes relaciones.
Veamos como modificar datos en una relación de uno a muchos, arrancando por el método que nos va a devolver la vista con el formulario de edición:
public function edit($id) {
$product = \App\Product::find($id);
$categories = \App\Category::all();
$properties = \App\Property::all();
$variables = [
'product' => $product,
'categories' => $categories,
'properties' => $properties,
];
return view('products.edit', $variables);
}
Recordemos que nuestro método recibe por parámetro un
id
y usamos esteid
para recuperar de la base de datos el producto a modificar.
Teniendo el método creado, vamos a generar la vista necesaria para poder mostrar y modificar los datos de nuestro producto. Tomemos de referencia nuestro formulario de creación pero lo alteramos un poco
edit.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" href="/css/app.css">
</head>
<body>
<div class="container">
<h1>Editando: {{$product->name}}</h1>
<form class="col-md-5" action="/productos/{{$product->id}}" method="post">
{{ csrf_field() }}
{{ method_field('patch') }}
<div class="form-group">
<label for="name">Nombre</label>
<input type="text" name="name" id="name" value="{{$product->name}}" class="form-control">
@if ($errors->has('name'))
<div class="alert alert-danger">
<ul>
@foreach ($errors->get('name') as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
<div class="form-group">
<label for="cost">Costo</label>
<input type="text" name="cost" id="cost" value="{{$product->cost}}" class="form-control">
@if ($errors->has('cost'))
<div class="alert alert-danger">
<ul>
@foreach ($errors->get('cost') as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
<div class="form-group">
<label for="profit_margin">Margen de Ganancia</label>
<input type="text" name="profit_margin" id="profit_margin" value="{{$product->profit_margin}}" class="form-control">
@if ($errors->has('profit_margin'))
<div class="alert alert-danger">
<ul>
@foreach ($errors->get('profit_margin') as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
<div class="form-group">
<select name="category_id">
@foreach ($categories as $category)
@if ($category->id == $product->id)
<option value="{{$category->id}}" selected>{{$category->name}}</option>
@else
<option value="{{$category->id}}">{{$category->name}}</option>
@endif
@endforeach
</select>
</div>
<div class="form-group">
@foreach ($properties as $property)
<label for="property{{$property->id}}">{{$property->name}}</label>
@if (in_array($property->id, $product->properties()->pluck('id')->toArray()))
<input type="checkbox" name="properties[]" value="{{$property->id}}"
id="property{{$property->id}}" checked>
@else
<input type="checkbox" name="properties[]" value="{{$property->id}}" id="property{{$property->id}}">
@endif
@endforeach
</div>
<div class="form-group">
<button type="submit" name="button" class="btn btn-primary">Enviar</button>
</div>
</form>
</div>
</body>
</html>
Como se ve, hay muchísimos cambios en el formulario de edición pero no son nada complicados. Lo que hacemos por ejemplo en la sección de categorías es dentro del
foreach
controlar cual es la categoría que tenemos seteada en el producto y la marcamos. En el caso de las propiedades, evaluamos cadacheckbox
para saber si está entre las propiedades del producto. De ser así, las imprimimos comochecked
.
Ya tenemos las rutas creadas y la vista con el formulario accesible, simplemente nos falta guardar los nuevos valores dentro del producto elegido. Para esto, necesitamos solamente un método más que reciba los datos y los procese.
public function update(Request $request, $id) {
$product = \App\Product::find($id);
$category = \App\Category::find($request->input('category_id'));
$product->name = $request->input('name');
$product->cost = $request->input('cost');
$product->profit_margin = $request->input('profit_margin');
$product->category()->associate($category);
$product->save();
$product->properties()->sync($request->input('properties'));
return redirect('/productos/' . $id);
}
Recibimos por parametros el
Request
y elid
para poder traer los datos necesarios de la base de datos y actualizar todos y cada uno de los datos manualmente con los nuevos valores.
Ya podemos crear, ver, editar y eliminar productos... Pero de qué nos sirve todo esto sin usuarios?
Para solucionar este problema, tenemos la ayuda de Laravel, que como siempre, nos hace la vida fácil...
Si queremos crear un sistema de autenticación, lo único que tenemos que hacer es escribir en consola el siguiente comando:
php artisan make:auth
Este comando nos crea las rutas, vistas y controladores necesarios para nuestro sistema de usuarios.
Empecemos viendo las rutas que nos regala Laravel:
// Authentication Routes...
Route::get('login', 'Auth\LoginController@showLoginForm')->name('login');
Route::post('login', 'Auth\LoginController@login');
Route::post('logout', 'Auth\LoginController@logout')->name('logout');
// Registration Routes...
Route::get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
Route::post('register', 'Auth\RegisterController@register');
// Password Reset Routes...
Route::get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
Route::get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
Route::post('password/reset', 'Auth\ResetPasswordController@reset');
Si bien en el web.php solo vamos a ver
Auth::Routes()
, esa linea sola contiene todas las rutas de arriba. Si quisieramos modificar alguna, simplemente la ponemos debajo deAuth::Routes()
y va a pisar la correspondiente. Por ejemplo, si quisieramos pasar /register a /registro pondríamos lo siguiente: Route::get('registro', 'Auth\RegisterController@showRegistrationForm')->name('register');
Tenemos MUCHAS rutas preparadas, todas apuntando a un controlador. Esperemos un poco más antes de verlos, por ahora vayamos a las vistas.
En este momento tenemos 5 vistas nuevas creadas:
auth\login.blade.php
auth\register.blade.php
auth\passwords\email.blade.php
auth\passwords\reset.blade.php
home.blade.php
Estas vistas están repartidas en diferentes carpetas.
auth
contiene las vistas delogin
/register
/carpeta passwords
.passwords
que se encuentra dentro deauth
, contiene las dos vistas de recuperación de contraseña que nos proveé Laravel (más adelante vemos como usarlas).views
que es la carpeta principal de vistas, contiene la última vista, que es la pantalla que vemos una vez que estamos logueados.
No vamos a meternos muy adentro en las especificaciones de cada vista, pero veamos un resumen de cada una:
Esta vista nos permite simplemente loguearnos a nuestro sitio web utilizando nuestro email
y password
.
Esta vista nos permite registrarnos a nuestro sitio web, completando los campos name
, email
, password
y password_confirmation
.
Esta es la vista que nos permite ingresar el correo para recuperar nuestro password
via email
.
A diferencia de la vista anterior, esta es la vista que nos permite realizar el cambio de contraseña después de acceder al link recibido en el mail que se envió desde auth\email.blade.php
La última vista, esta hecha para mostrar como podemos controlar las sesiones desde Laravel, es decir, solo podemos acceder a ella si estamos logueados. Si nos logueamos, nos redirige automáticamente a la vista.
Entiendo, entiendo, es un tema complicado... Vale la pena entender qué es lo que hace cada una de las vistas ya que nos sirven de ejemplo para modificarlas o incluso crear las nuestras propias.
Bien, ya tenemos una noción de qué es lo que hacen las vistas, vamos ahora por los controladores correspondientes:
Tenemos 4 controladores nuevos dentro de la carpeta auth
:
Auth\ForgotPasswordController.php
Auth\LoginController.php
Auth\RegisterController.php
Auth\ResetPasswordController.php
Son pocos, pero tenemos muchas explicaciones para hacer al respecto! Arranquemos por explicar como se manejan estos controladores...
Si bien tenemos un controlador para cada una de las 4 mayores vistas nuevas, estos controladores funcionan a través del uso de traits
(tal vez los recuerden de películas como: Por dios, cuando se acaba objetos?
o No entiendo nada, por favor ayuda.
), los cuales permiten que usen funciones declaradas dentro de la carpeta vendor
, los cuales NO TENEMOS QUE TOCAR ya que son cosas que no se comparten entre los diferentes desarrolladores.
La forma correcta de trabajar con estos archivos es por ejemplo, si quisieramos modificar la validación del login, vamos a nuestro Auth\LoginController.php
y vemos que dice use AuthenticatesUsers
, lo que quiere decir que está utilizando el trait
AuthenticatesUsers
, con una simple busqueda (ctrl + p), vemos que ese trait está en \vendor\laravel\framework\src\Illuminate\Foundation\Auth\AuthenticatesUsers.php
... COMO???
Ese es el problema con la carpeta vendor, todo nuestro sistema está ahí!! Por eso vamos a modificarlo lo menos posible.
En este archivo vemos que hay una función que se llama validateLogin
, la cual recibe un $request
y genera una validación...
Para modificarla, lo que vamos a hacer es tan simple como copiarla textualmente y pegarla en nuestro Auth\LoginController.php
, debajo del __construct
y modificamos lo que querramos. Por ejemplo:
protected function validateLogin(Request $request)
{
$this->validate($request, [
$this->username() => 'required|string',
'password' => 'required|string|min:6',
]);
}
Ahora nuestro login
requiere que el password tenga al menos 6 caracteres!! Parecía complicado, no?
De la misma forma que logramos eso, podemos cambiar cualquier tipo de función en los controladores que nos dió el comando php artisan make:auth
!!
Ahora, qué pasa por ejemplo si quisieramos modificar nuestro registro para agregar campos?
Tenemos un camino de 5 pasos:
Agreguemos primero el campo a la vista!
auth\register.blade.php
<div class="form-group{{ $errors->has('dni') ? ' has-error' : '' }}">
<label for="dni" class="col-md-4 control-label">DNI</label>
<div class="col-md-6">
<input id="dni" type="dni" class="form-control" name="dni" required>
@if ($errors->has('dni'))
<span class="help-block">
<strong>{{ $errors->first('dni') }}</strong>
</span>
@endif
</div>
</div>
Tenemos el campo, vamos por el controlador!
En el controlador tenemos que modificar 2 métodos, agregandole lo necesario sobre nuestro campo:
Auth\RegisterController.php
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed',
'dni' => 'required|numeric|between:1,100000000'
]);
}
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
'dni' => $data['dni']
]);
}
Con el controlador ya modificado, nos vamos para el modelo!
User.php
protected $fillable = [
'name', 'email', 'password', 'dni'
];
Hacemos esto para permitirle a la función create, modificar la columna
dni
de la base de datos.
Por último, y más importante, hay que agregarle la columna a la tabla de la base de datos!! (como yo se que hay conocimiento que se sabe de memoria, obviamente, no voy a explicar esa parte).
Uno de los conceptos más importantes en Laravel, es el de middleware
.
Un middleware
es una capa de abstracción más entre una ruta y un controlador.
Qué significa esto?
Pongamos como ejemplo la edición de un producto. Nosotros ya tenemos el sistema que permite la edición de un producto, pero que pasaría si quisieramos que una vez subido un producto, el único que tuviera acceso a la vista de edición fuese el administrador?
Ahí es donde entran los middleware
. Estos nos dejan compartir funcionalidad entre las diferentes rutas, por ejemplo una barrera que se fija si sos el administrador o sos un usuario común; si sos un administrador, seguís de largo, si sos un usuario, volvés al home!
Para crear un middleware que controle el rol de nuestro usuario, ponemos el siguiente comando:
php artisan make:middleware CheckName
Esto nos va a generar un archivo en la carpeta App\Http\Middleware
llamado CheckName
. Este archivo ya viene con código, vamos a analizar el único método que tiene mientras le agregamos nuestro propio código:
public function handle($request, Closure $next, $name)
{
if (\Auth::check() && \Auth::user()->name === $name) {
return $next($request);
}
return redirect('/');
}
Nos preparamos el middleware agregandole un parámetro más, llamado
$name
y luego checkeamos a través de unif
, si el usuario está logueado y si el nombre recibido es el mismo al que tiene el usuario. De ser así, va a correr la lineareturn $next($request);
. Esta linea permite que el flujo siga, en cambio, si no fuera el nombre que esperamos, lo redirigimos a la ruta'/'
.
Ya tenemos el código del middleware, ahora tenemos que registrarlo en el Kernel
, esto nos va a permitir acceder al middleware desde las diferentes rutas.
app\Http\kernel.php
En este archivo vamos a tener 3 tipos diferentes de formas de llamar a un middleware
.
En este array
, llamamos a los middleware
que queremos que corran en TODAS las rutas, sin importar cual sea.
A diferencia del primer array
, acá empezamos a especificarnos. Este es un array de arrays asociativos
. La key
que le pongamos al array asociativo
va a ser el nombre por el cual vamos a utilizar los middleware
contenidos.
Este es el array
que vamos a utilizar. Acá definimos en la key
la forma en la cual queremos llamar a nuestro middleware
y en el value
ponemos la ruta del mismo.
En nuestro caso, usamos la tercera forma, para ser más específicos agregando la siguiente linea al array $routeMiddleware
:
'checkname' => \App\Http\Middleware\CheckName::class,
Bien, ya tenemos la configuración hecha... Ahora, cómo los usamos?
Vamos a la ruta a la cual queremos ponerselo, en este caso queremos evitar que puedan editar los usuarios:
Route::get('/productos/{id}/edit', 'ProductsController@edit')->middleware('checkname:admin');
Podemos ver que le agregamos a la ruta el
middleware
utilizando->middleware()
a la vez que le pasamos por parámetrocheckname:admin
. Qué es esto?checkname
es el nombre que pusimos en el archivokernel.php
y al ponerle :admin le estamos pasando por parámetro la palabraadmin
que luego, se va a guardar en la variable$name
dentro delmiddleware
.
Ya vimos que Laravel nos hace la vida más fácil en todo sentido, por qué no agregar a la lista la creación de base de datos? Las migraciones nos permiten escribir de forma ordenada, los cambios que vamos haciendo en la base de datos (creación de tablas, agregado de columnas, modificación de campos, etc). Para esto vamos a crear (idealmente) un archivo por cada cambio que fuesemos a hacerle a nuestra base de datos.
Creémos la base de datos que puse al principio de la guía desde 0:
Empezamos creando la base de datos (o schema) desde workbench/terminal con el nombre que querramos.
Después configuramos nuestro .env con la base de datos creada y vamos a crear la migración para la tabla de products
corriendo el siguiente comando desde una terminal:
php artisan make:migration create_products_table
Con ese comando estamos siguiendo la convención de
Laravel
sobre como crear migraciones de creación de tablas, poniendo de nombrecreate_nombredetabla_table
ya nos genera las funciones necesarias para trabajar, que simplemente tenemos que modificar.
Dentro de toda migración vamos a encontrar 2 funciones:
En la función up()
vamos a poner la estructura que queremos crear, siempre utilizando la clase Schema
:
Schema::create('products', function (Blueprint $table) {
$table->increments('id');
$table->string('name', 191);
$table->string('image', 191)->nullable();
$table->tinyInteger('active')->default(1);
$table->double('cost', 8, 2);
$table->double('profit_margin', 5, 2);
$table->integer('category_id')->nullable()->unsigned();
$table->timestamps();
});
Como estamos CREANDO una tabla, utilizamos la función
create
que recibe como parametros el nombre de la tabla a crear y una función que a su vez recibeBlueprint $table
, donde vamos a escribir nuestros campos de la tabla de la siguiente forma:$table->tipodedato('nombredecolumna')
; Podemos concatenarle más modificadores, para ver una lista completa de todo, se puede usar el siguiente link: https://laravel.com/docs/5.5/migrations#columns
En la función down()
nosotros vamos a decirle a Laravel como revertir el proceso hecho en up()
, para que cuando quisieramos volver atrás una migración, lo podamos hacer de forma sencilla:
Schema::dropIfExists('products');
En este caso, lo que hacemos es eliminar la tabla
products
si es que existe.
De esa forma creamos las tablas restantes:
php artisan make:migration create_categories_table
Schema::create('categories', function (Blueprint $table) {
$table->increments('id');
$table->string('name', 191);
$table->tinyInteger('active')->default(1);
$table->timestamps();
});
Schema::dropIfExists('categories');
php artisan make:migration create_properties_table
Schema::create('properties', function (Blueprint $table) {
$table->increments('id');
$table->string('name', 191);
$table->tinyInteger('active')->default(1);
$table->timestamps();
});
Schema::dropIfExists('properties');
Con la tabla product_property
es un poco diferente, ya que vamos a estar creando una tabla de relaciones:
php artisan make:migration create_product_property_table
Schema::create('product_property', function (Blueprint $table) {
$table->integer('product_id')->nullable()->unsigned();
$table->integer('property_id')->nullable()->unsigned();
$table->foreign('product_id')->references('id')->on('products');
$table->foreign('property_id')->references('id')->on('properties');
});
Solo necesitamos dos campos, ya que vamos a usar esta tabla para relacionar productos con propiedades. Lo que hacemos además es crear las relaciones utilizando la siguiente lógica:
$table->foreign('nombredetablaensingular_id')->references('id')->on('nombredetabla');
Tenemos que asegurarnos que los dos camposproduct_id
yproperty_id
sean del tipo exacto que sea el campo deid
original.
Schema::dropIfExists('product_property');
Tenemos las tablas creadas... Pero hay un problema! Nos olvidamos de ponerle la relación al campo category_id
de la tabla products
. No hay que desesperar, podemos hacerlo con una migración más, para modificar la tabla!
php artisan make:migration add_relation_to_products_table
Si bien tenemos la migración creada, esta vez vamos a escribir el código un poco diferente:
Schema::table('products', function (Blueprint $table) {
$table->foreign('category_id')->references('id')->on('categories');
});
Schema::table('products', function (Blueprint $table) {
$table->dropForeign('posts_user_id_foreign');
});
Ahora sí tenemos toda la estructura de base de datos creada. Ahora, solo a base de ejemplo, no voy a aplicar esta migración en el cóðigo, pero dejo una muestra de como sería una migración para modificar el largo de la columna name:
php artisan make:migration alter_name_column_products
Schema::table('products', function (Blueprint $table) {
$table->string('name', 160)->change();
});
Schema::table('products', function (Blueprint $table) {
$table->string('name', 191)->change();
});
Lo que hacemos de esta forma es modificar la columna, con el mismo comando que la creariamos, pero agregandole
->change()
al final de la misma. Para la funcióndown()
hacemos lo contrario a lo que hicimos enup()
.
Espectacular! Ya tenemos nuestras tablas de la base de datos creadas, ahora necesitamos rellenarlas con datos para poder probar nuestro sistema! Para esto vamos a usar Seeders
junto a Factories
y Faker
. Qué es todo esto?
Los seeders son los encargados de decir qué vamos a agregar en la base de datos.
Las factories se encargan de diseñar (en nuestro caso junto a faker
) la estructura de un dato que vayamos a generar. Es decir, vamos a decirle a Laravel específicamente como tienen que ser los datos que vayan en la tabla.
La mejor parte de las factories está en Faker
. Faker es una librería que nos permite crear datos fictícios con casi cualquier tipo de formato que quisieramos, desde un número al azar hasta párrafos enteros.
Para más info sobre qué cosas podemos crear con
Faker
ver el siguiente link: https://github.com/fzaninotto/Faker#formatters
Arranquemos por factory
, así le mostramos a Laravel cómo queremos que sean nuestros productos.
php artisan make:factory ProductFactory
Al correr ese comando vamos a tener un archivo nuevo, al cual vamos a ingresar para agregarle nuestros datos:
database\factories\ProductFactory.php
$factory->define(\App\Product::class, function (Faker $faker) {
return [
'name' => $faker->name,
'cost' => $faker->numberBetween(1, 2000),
'profit_margin' => $faker->numberBetween(1, 100),
];
});
Dentro del return, especificamos utilizando el formato
key => value
el nombre de la columna y el contenido que vamos a darle, aplicando$faker
para generarlo.
Hacemos lo mismo con Categories
y Properties
:
php artisan make:factory CategoryFactory
database\factories\CategoryFactory.php
$factory->define(\App\Category::class, function (Faker $faker) {
return [
'name' => $faker->word,
];
});
php artisan make:factory PropertyFactory
database\factories\PropertyFactory.php
$factory->define(\App\Property::class, function (Faker $faker) {
return [
'name' => $faker->word,
];
});
Ya tenemos todos nuestras Factory
creadas, pero nunca especificamos los datos de relaciones... Eso es porque eso lo hacemos en nuestros Seeders
!!
Para los seeders, el camino es el mismo.
Generamos nuestro seeder:
php artisan make:seeder CategoriesSeeder
Si entramos al archivo creado, podemos ver que tenemos solamente una función, la cual vamos a modificar con nuestro código:
database\seeds\CategoriesSeeder.php
public function run()
{
factory(\App\Category::class, 5)->create()->each(function ($elem) {
$elem->products()->save(factory(App\Product::class)->make());
});
}
Si quisieramos hacer más de uno por categoría:
public function run()
{
factory(\App\Category::class, 5)->create()->each(function ($elem) {
$elem->products()->saveMany(factory(App\Product::class, 5)->make());
});
}
Arrancamos con un seeder un poco avanzado, para que se vaya viendo las posibilidades de lo que se puede hacer:
factory(\App\Category::class, 5)->create()
nos crea nuestra categoría utilizando lafactory
deCategory
A eso le hacemos le agregamos ->each() que recibe como parametro una función y nos permite correr esa función a cada uno de las categorías creadas. Por último le agregamos a cada uno de nuestros productos, la categoría creada utilizando la siguiente linea:$elem->products()->save(factory(App\Product::class)->make());
donde $elem es la categoría actual, de ahí llamamos a los productos de esa categoría y le decimos que a esa categoría le agregue un elemento creado dentro del método save utilizandofactory(App\Product::class)->make()
Si nosotros quisieramos crear y agregar más de un solo producto por categoría, bastaría con realizar unfor
dentro de la función que recibeeach
o simplemente repetir la linea$elem->products()->save(factory(App\Product::class)->make());
más de una vez.
Ya sabemos como crear y seedear una base de datos en Laravel, vamos a realizar los pasos restantes!!
php artisan make:seeder PropertiesSeeder
database\seeds\PropertiesSeeder.php
public function run()
{
factory(\App\Property::class, 10)->create();
}
Y por último:
php artisan make:seeder ProductsSeeder
database\seeds\ProductsSeeder.php
Para nuestra último Seeder
vamos a correr un código un tanto complicado:
public function run()
{
factory(\App\Product::class, 10)->create()->each(function ($elem) {
$properties = [];
$maxCat = \App\Category::all()->count();
$maxProp = \App\Property::all()->count();
for ($i=0; $i < 3; $i++) {
$properties[] = \App\Property::find(rand(1,$maxProp));
}
$properties = collect($properties);
$elem->category()->associate(\App\Category::find(rand(1, $maxCat)));
$elem->save();
$elem->properties()->sync($properties);
});
}
La lógica de arriba es la siguiente: Creamos nuestro producto y con un
each()
le corremos a cada uno, una función.
Dentro de esta función hacemos varias cosas:
- Creamos un
array
dondo vamos a guardar nuestras propiedades- Contamos la cantidad de categorías que tenemos y las guardamos en
$maxCat
- Contamos la cantidad de propiedades que tenemos y las guardamos en
$maxProp
- Corremos un
for
que nos permite cargar a nuestro array de propiedades, 3 propiedades existentes en la base de datos.- Transformamos nuestro array en una collection
- Al elemento actual, le cargamos utilizando
associate()
una categoría existente en la base de datos y lo guardamos- Por último, al elemento actual, le sincronizamos todos los tags que agarramos de la base de datos, pero solamente le pasamos sus
id
utilizando la funciónpluck()
de eloquent.
Una vez terminado esto, solamente nos falta decirle a Laravel que use estos seeders al momento de llenar la base de datos! Para esto, vamos al archivo database\seeds\DatabaseSeeder.php
y le agregamos las siguientes lineas en su función run()
:
$this->call(PropertiesSeeder::class);
$this->call(CategoriesSeeder::class);
$this->call(ProductsSeeder::class);
Ahora que tenemos nuestros seeders con sus factories y las migraciones creadas, solamente nos falta correrlos!
Vamos a la terminal y escribimos lo siguiente:
php artisan migrate --seed
Esto nos corre todas las migraciones y además, una vez terminada la migración, corre nuestros seeders. Si quisieramos hacer todo por separado, haríamos lo siguiente:
php artisan migrate
php artisan db:seed
En el caso de que tuvieramos problemas con alguna migración, o simplemente quisieramos volver atrás, podemos utilizar el siguiente comando
php artisan migrate:rollback --step 2
Este comando nos permite volver atrás nuestras últimas 2 migraciones. Es decir, vamos a ejecutar la funcion
down()
de las últimas 2 migraciones realizadas.
Si por otra parte, quisieramos resetear completamente nuestra base de datos, haríamos lo siguiente:
php artisan migrate:refresh
En la nueva versión de Laravel, tenemos un comando que nos permite borrar todas nuestras tablas y correr las migraciones de nuevo, sin utilizar las funciones down()
:
php artisan migrate:fresh
Por último, si nosotros quisieramos seedear nuestra base de datos mientras corremos cualquier comando de migrate
, lo único que deberíamos hacer es agregarle --seed
al final.
Upa, ya tenemos todo armado... Ah, no... NOS FALTAN LAS FOTOS!
Para empezar, si quisieramos guardar cosas en Laravel, lo recomendable es correr el comando que vamos a ver a continuacion para generar un soft link
. Qué es esto? Simple, nos genera un acceso directo a otra carpeta. De esta forma acceder a archivos que físicamente pueden o no estar en nuestra carpeta public, como si lo estuvieran.
Corremos el siguiente comando en la terminal:
php artisan storage:link
Si van a la carpeta public
, ahora tenemos una nueva carpeta llamada storage
, la cual realmente es un acceso directo a la verdadera carpeta storage/app/public
.
Propongamonos ahora guardar una foto en nuestros productos. Para esto, necesitaríamos otra columna en nuestra tabla de productos... Creémos una migración!!
php artisan make:migration add_fotopath_to_products_table
Le agregamos funcionalidad a la migración:
Schema::table('products', function (Blueprint $table) {
$table->string('fotopath')->nullable();
});
Schema::table('products', function (Blueprint $table) {
$table->dropColumn('fotopath');
});
Como último paso, corremos la migración poniendo en consola el siguiente comando!
php artisan migrate
Ya con los pasos hechos hasta ahora tenemos la base de datos preparada para que nuestros productos tengan imágenes... Lo que nos falta es subirlas!!!
Arranquemos agreguemosle a nuestra vista de creación de productos lo necesario para que se pueda subir una imagen junto a los datos:
Arranquemos por modificar el <form>
para que efectivamente envíe archivos:
<form class="col-md-5" action="/productos/agregar" method="post" enctype="multipart/form-data">
Al agregarle
enctype="multipart/form-data"
le estamos diciendo a nuestro formulario, que aparte de enviar texto plano, va a estar enviando archivos.
Ya tenemos el formulario preparado, vamos a agregarle el campo de selección de imagen:
<div class="form-group">
<label for="fotoPath">Imagen</label>
<input type="file" name="fotoPath" id="fotoPath" class="form-control">
@if ($errors->has('fotoPath'))
<div class="alert alert-danger">
<ul>
@foreach ($errors->get('fotoPath') as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
Perfecto! Ya tenemos el sistema preparado para enviar imagenes... Ahora a recibirlas!
Arrancamos adaptando nuestro fillable
para que podamos guardar el path en nuestra tabla:
app/Product.php
protected $fillable = ['name', 'cost', 'profit_margin', 'category_id', 'fotopath'];
Modifiquemos el método store
de nuestro ProductsController
.
public function store(Request $request) {
$rules = [
"name" => "required|unique:products",
"cost" => "required|numeric",
"profit_margin" => "required|numeric",
"category_id" => "required|numeric|between:1,3"
];
$messages = [
"required" => "El :attribute es requerido!",
"unique" => "El :attribute tiene que ser único!",
"numeric" => "El :attribute tiene que ser numérico!",
"between" => "El :attribute tiene que estar entre :min y :max."
];
$request->validate($rules, $messages);
$extensionImagen = $request->file('fotoPath')->getClientOriginalExtension();
$fotoPath = $request->file('fotoPath')->storeAs('productos', uniqid() . "." . $extensionImagen, 'public');
$product = \App\Product::create([
'name' => $request->input('name'),
'cost' => $request->input('cost'),
'profit_margin' => $request->input('profit_margin'),
'fotopath' => $fotoPath
]);
$category = \App\Category::find($request->input('category_id'));
$product->properties()->sync($request->input('properties'));
$product->category()->associate($category);
$product->save();
return redirect('/productos');
}
Ya que nuestro producto va a tener imagen de forma opcional, no le hacemos una validación. Simplemente le agregamos las siguientes lineas:
$extensionImagen = $request->file('fotoPath')->getClientOriginalExtension();
$fotoPath = $request->file('fotoPath')->storeAs('productos', $request->name . "." . $extensionImagen, 'public');
En la primera linea guardamos la extensión de la imagen subida para utilizarla luego en la función
storeAs
. Para guardar nuestro archivo, primero creamos una variable donde finalmente vamos a tener elpath
llmada$fotoPath
. Empezamos por agarrar nuestro archivo$request->file('fotoPath')
, a esto le concatenamos la funciónstoreAs('productos', $request->name . "." . $extensionImagen, 'public');
A la función le agregamos parametros en este orden:'productos'
es la carpeta donde se va a guardar nuestra imagen$request->id . "." . $extensionImagen
es el nombre con el cual vamos a guardar nuestro archivo, en este caso le pusimos la funcionuniqid()
que nos genera unstring
único y su extensión original, con un . en el medio para que mantenga su formato de archivo.'public'
es el nombre del lugar donde va a estar guardado nuestro, generalmente se pone 'public'
Ya podemos guardar fotos en nuestros productos! Pero ahora... cómo las vemos?
Vamos a la vista donde podemos ver productos específicos y le agregamos la siguiente linea donde querramos:
products/show.blade.php
<img src="{{ asset('storage/' . $product->fotopath) }}" alt="">
Utilizando la función
asset()
generamos una URL hacia nuestra imagen. Le pasamos como parámetro la ubicación de nuestro archivo, en este caso lo tenemos en nuestra base de datos:$product->fotopath
. El problema está en que ese no es el path que utilizamos para recuperarla, sino que tenemos que agregarle'storage/'
antes, para que podamos acceder correctamente.
De esta forma ya tenemos nuestro sistema de imagenes totalmente funcional!!!
Continuará...
┻━┻︵ (°□°)/ ︵ ┻━┻