Cómo crear una lista de tareas con Laravel en PHP

Cómo crear una lista de tareas con Laravel en PHP

Este tutorial consistirá en realizar una aplicación en Laravel de una lista de tareas. Utilizaremos MySQL para almacenar la base de datos y únicamente constará de una tabla. Dicha tabla se llamará tasks y tendrá 4 columnas:

  • id: clave primaria de la tabla.
  • task: el nombre de la tarea.
  • created_at: fecha de creación. Esta columna la crea Laravel automáticamente.
  • updated_at: fecha de modificación. Esta columna también se crea por defecto.

Al final del tutorial nos quedará algo como esto:

Configuración del entorno

  1. Crear cuenta en c9.io.
  2. Crear un nuevo workspace en cloud9 importando el repositorio de Github al que he subido el código: https://github.com/MarioPerezEsteso/To-Do-App-Laravel

El código completo está en la rama dev, así que empezaremos desde la rama master hasta completar este tutorial.

Para configurar el entorno de c9.io y poder usarlo con Laravel tenemos que modificar un fichero de la configuración del servidor web de Apache: /etc/apache2/sites-enabled/001-cloud9.conf. Añadir /public al DocumentRoot:

DocumentRoot /home/ubuntu/workspace/public

Tras esto, copiamos el fichero .env.example a uno nuevo que se llamará .env. En él estará la configuración de la aplicación, como por ejemplo la relacionada con la base de datos. Después, hay que configurar la base de datos y poner la info en el .env:

mysql-ctl cli
SELECT * FROM mysql.user;
exit

El fichero .env debe quedar así:

APP_ENV=development
APP_DEBUG=true
APP_KEY=random_string_here_with_artisan_key_generate

DB_CONNECTION=mysql
DB_HOST=localhost
DB_DATABASE=c9
DB_USERNAME=usuario
DB_PASSWORD=

Para generar el APP_KEY debemos ejecutar el siguiente comando:

php artisan key:generate

Una vez clonado el proyecto, hay que instalar las dependencias con composer. Para ello, dentro del directorio en el que hemos clonado el proyecto introducimos el siguiente comando:

composer install

Migraciones de base de datos

Las migraciones en la base de datos vienen gestionadas por el programa artisan, que también se puede utilizar para crear controladores, seeders, modelos, etc.

Para añadir un script de migración que creará la tabla tasks ejecutamos el siguiente comando:

php artisan make:migration create_tasks_table --create=tasks

Se ha creado un nuevo archivo dentro de la carpeta database/migrations que contiene el código de creación de la tabla donde almacenaremos las tareas. Lo único que tenemos que añadir es una columna de tipo string para guardar el título de la tarea. La llamaremos igualmente task:

$table->string('task');

Finalmente, quedará así:

Schema::create('tasks', function (Blueprint $table) {
$table->increments('id');
$table->string('task');
$table->timestamps();
});

Existen tres comandos principales para gestionar las migraciones. El primero es para ejecutarlas, el segundo es para hacer rollback de la última migración y el tercero es para resetear la base de datos entera:

php artisan migrate
php artisan migrate:rollback
php artisan migrate:reset

Rutas

Las rutas definen los puntos de entrada a la aplicación en fichero routes.php dentro de app/Http. En este caso, únicamente tendremos tres rutas, que son la de la pantalla principal, la de creación de una nueva tarea y la de borrado de tareas:

Route::get('/', [
'uses' => 'TaskController@index',
]);

Route::post('task/store', [
'before' => 'csrf',
'uses' => 'TaskController@store'
]);

Route::get('task/delete/{id}', [
'uses' => 'TaskController@destroy'
]);

Tal y como se puede observar en el snippet anterior, cada ruta hace uso de un método del controlador. La primera ruta llama al index para mostrar tareas, la segunda llama al método store para crear tareas y la tercera y última llama al método destroy para eliminar una tarea.

Controladores

Los controladores también se crearán haciendo uso de artisan. Le especificaremos que deseamos crear un nuevo controlador y le pasaremos el nombre como parámetro el nombre de dicho controlador. Además, vendrá con varios métodos predefinidos que se usarán para editar, crear, mostrar o eliminar los atributos de dicho controlador.

php artisan make:controller TaskController 

Los métodos que tendrá nuestro TaskController serán el index, store destroy:

/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
}

/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
}

/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
}

Más adelante añadiremos el código de cada método.

Modelos

Los modelos están asociados a tablas de la base de datos. En este caso crearemos un modelo Task que se encargará de almacenar la información de cada tarea que creemos desde Laravel cuando estemos utilizando dicha entidad.

php artisan make:model Task 

El modelo Task extiende de la clase Model y contiene el nombre de la tabla, en este caso tasks y los atributos que se pueden introducir en la base de datos:

class Task extends Model
{
   /**
    * The database table used by the model.
    *
    * @var string
    */
   protected $table = 'tasks';

   /**
    * The attributes that are mass assignable.
    *
    * @var array
    */
   protected $fillable = [
       'task',
   ];
}

Vista

Cuando ahora accedamos a la página web, no veremos nada porque no estamos mostrando ninguna vista, así que vamos a crearla dentro de resources/views/index.blade.php:

<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css">
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-offset-3 col-md-6">
<div class="todolist not-done">
<h1>Tasks</h1>
<form action="task/store" method="post">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<input type="text" class="form-control add-todo" name="task" placeholder="Add task">
</form>
<hr>
@if (count($errors) > 0)
<!-- Form Error List -->
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<ul id="sortable" class="list-unstyled">
@if (!empty($tasks) && count($tasks) > 0)
@foreach ($tasks as $task)
<li>
<label>
<a href="{{ url('task/delete/' . $task->id) }}" class="btn btn-danger btn-sm">
<span class="glyphicon glyphicon-trash"></span>
</a>
{{ $task->task }}
</label>
</li>
@endforeach
@endif
</ul>
</div>
</div>
</div>
</div>
</body>
</html>

Repository pattern

Ahora que ya tenemos la vista, ¿cómo le pasamos los datos de nuestras tareas? Vamos a crear un repositorio que hará más sencilla la comunicación con la base de datos. Creamos una carpeta llamada “repositories” dentro de la carpeta “app” y dentro creamos “TaskRepositoryInterface”, que contiene lo siguiente:

namespace App\Repositories;

interface TaskRepositoryInterface
{
  public function all($columns = array('*'));
  public function create(array $attributes);
  public function destroy($ids);
}

Ahora toca implementar los métodos de la interfaz TaskRepository:

namespace App\Repositories;

class TaskRepository implements TaskRepositoryInterface
{
   /**
    * Model class name
    *
    * @var string
    */
   protected $modelClassName = "App\Task";

   /**
    * Get all entities of a model
    *
    * @param array $columns
    * @return mixed
    */
   public function all($columns = array('*'))
   {
       return call_user_func_array("{$this->modelClassName}::all", array($columns));
   }

   /**
    * Create a new entity of a model
    *
    * @param array $attributes
    * @return mixed
    */
   public function create(array $attributes)
   {
       return call_user_func_array("{$this->modelClassName}::create", array($attributes));
   }

   /**
    * Destroy a list of entities by their identifiers
    *
    * @param $ids
    * @return mixed
    */
   public function destroy($ids)
   {
       return call_user_func_array("{$this->modelClassName}::destroy", array($ids));
   }
}

Una vez tenemos el repository creado, podemos listar las tareas que tenemos pendientes haciendo uso de IoC Container dentro del TaskController:

    /**
    * @var TaskRepository
    */
   protected $taskRepository;

   /**
    * Class constructor.
    */
   public function __construct(TaskRepository $taskRepository)
   {
       $this->taskRepository = $taskRepository;   
   }

   /**
    * Display a listing of the resource.
    *
    * @return \Illuminate\Http\Response
    */
   public function index()
   {
       $tasks = $this->taskRepository->all();
       return view('index', compact('tasks'));
   }

Para borrar una task, debemos implementar el método destroy en el controlador. Primero importamos algo necesario para poder redirigir al usuario a la página principal: 

use Illuminate\Support\Facades\Redirect;

A continuación, implementamos el método destroy:

/**
* Remove the specified resource from storage.
*
* @param  int  $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
$this->taskRepository->destroy($id);
return Redirect::to('/');
}

 Para almacenar una nueva task utilizaremos el método store del TaskController. Aquí accederemos a los datos enviados por el formulario a través de la request y los almacenaremos en un array que pasaremos a validar:

    public function store(Request $request)
   {
       $data = [
           'task' => $request->task,    
       ];
       $validator = Validator::make($data, [
           'task' => 'required|max:255',
       ]);

       if ($validator->fails()) {
           return Redirect::to('/')->withErrors($validator);
       }

       $this->taskRepository->create($data);

       return Redirect::to('/');
   }

Testing

Una vez hecho esto, vamos a pasar a realizar algunos tests. Para ello, Laravel nos proporciona los seeders, que son métodos para poblar la base de datos antes de hacer un test. El seeder que vamos a crear se guardará dentro de database/seeds.

php artisan make:seeder TasksSeeder 

Dentro de DatabaseSeeder.php en la carpeta migrations/seeds podemos llamar a los seeds que se van a ejecutar, así que añadimos el nuestro: 

use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;

class DatabaseSeeder extends Seeder
{
   /**
    * Run the database seeds.
    *
    * @return void
    */
   public function run()
   {
       Model::unguard();

       $this->call(TasksSeeder::class);

       Model::reguard();

   }
}

Lo que contendrá esta clase son dos inserciones en la base de datos:

use Illuminate\Database\Seeder;

class TasksSeeder extends Seeder
{
   /**
    * Run the database seeds.
    *
    * @return void
    */
   public function run()
   {
       DB::table('tasks')->insert([
           'task' => 'Code'
       ]);
       
       DB::table('tasks')->insert([
           'task' => 'Sleep'
       ]);
   }
}

Para ejecutar el seeder, introducimos el siguiente comando:

php artisan db:seed

A continuación, debemos crear una base de datos para realizar las migraciones que hayamos ejecutado hasta el momento. Para ello, vamos a la terminal y ejecutamos:

mysql-ctl cli
mysql> create database c9tests;
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| c9                 |
| c9tests            |
| mysql              |
| performance_schema |
| phpmyadmin         |
+--------------------+

6 rows in set (0.00 sec)

Tras esto, en el fichero phpunit.xml añadimos los siguientes tags dentro de los tags de php para configurar la base de datos de testing para phpunit:

<env name="DB_HOST" value="localhost"/>
<env name="DB_DATABASE" value="c9tests"/>
<env name="DB_USERNAME" value="usuario"/>
<env name="DB_PASSWORD" value=""/>

Añadimos, además, los métodos setUp y tearDown a la clase TestCase dentro de la carpeta tests:

public function setUp()
{
    parent::setUp();
    $this->createApplication();
    Artisan::call('migrate:reset');
    Artisan::call('migrate');
    Artisan::call('db:seed');
}

public function tearDown()
{
    Artisan::call('migrate:reset');
    parent::tearDown();
}

Estos métodos realizan las migraciones y los seeds para el testing. El setUp se ejecuta antes de los tests y el tearDown se ejecuta al finalizarlos.

Para ejecutar los tests debemos ejecutar el siguiente comando dentro de la raíz del proyecto, ya que phpunit es una dependencia que hemos resuelto con composer:

vendor/phpunit/phpunit/phpunit

En este caso vamos a realizar el testing del repositorio tasks (TasksRepository) para comprobar el funcionamiento de sus métodos. Dado que sabemos que contiene dos tasks en la base de datos de testing: (Code y Sleep), sabemos que tienen identificadores con valor 1 y 2 respectivamente.

Vamos a probar el método del repositorio que obtiene todas las tasks y que se usa al mostrarlas en la vista.

use App\Repositories\TaskRepository;

class TaskRepositoryTest extends TestCase
{
   /**
    * A basic functional test example.
    *
    * @return void
    */
   public function testGetAllTasks()
   {
       $repository = new TaskRepository();
       $tasks = $repository->all();
       
       $this->assertEquals(2, count($tasks));

       $this->assertEquals('Code', $tasks[0]->task);

       $this->assertEquals('Sleep', $tasks[1]->task);
   }
} 

A continuación, podemos probar el método create para crear una task:

/**
 * Create a new task.
 */
 public function testCreateTask()
 {
 $repository = new TaskRepository();
     $data = [
     'name' => 'Learn Laravel',
     ];

     $task = $repository->create($data);


     $this->assertEquals(3, count($repository->all()));
     $this->assertEquals(3, $task->id);
}

Finalmente, probaremos el método destroy del TaskRepository:

/**
 * Test delete task.
 */
 public function testDeleteTask()
 {
$repository = new TaskRepository();
     $repository->destroy(1);
    $this->assertEquals(1, count($repository->all()));
 }

 Si todo va bien, cuando ejecutemos los tests pasarán todos: 

1 comentarios


Miguel ยท Hace 3 meses

Muy bueno e tuto, ¿Podrías hacer uno de como usar las notificaciones de laravel 5.3? Muchas gracias :D

Deja un comentario