ToDo List Sample
Now that Laravel 5 has been installed in Laravel 5 Framework Install on Ubuntu, it's the time to build simple app with it.
We'll build a ToDo list app that we can do CRUD operations.
In the process, we'll learn how Laravel handles database migration as well as the Laravel's Eloquent models. Of course, we'll see the Routing and View Layouts. All basics stuffs but fundamental elements of Laravel.
Let's create our project:
$ laravel new todo
Probably, we may want to set project-dir/.env:
DB_HOST=127.0.0.1 DB_DATABASE=mysql DB_USERNAME=root DB_PASSWORD=secret
We need to make sure that put our passoword for db into the file.
let's use the make:migration command to generate a new database migration for our tasks table:
$ php artisan make:migration create_tasks_table --create=tasks
The migration will be placed in the database/migrations directory of our project.:
To add an additional string column for the name of our tasks, let's edit the file (***-create_tasks_table.php):
... public function up() { Schema::create('tasks', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->timestamps(); }); } ...
We're ready to create all of our database tables:
$ php artisan migrate
From MySQL DB, we see a new tasks table which contains the columns defined in our migration:
Eloquent is Laravel's default ORM (object-relational mapper). Usually, each Eloquent model corresponds directly with a single database table.
So, let's define a Task model that corresponds to our tasks database table we just created.
We'll use the make:model command:
$ php artisan make:model Task
The model will be placed in the app directory of our application (app/Task.php). The Task model is assumed to correspond with the tasks database table.
All Laravel routes are defined in the app/Http/routes.php.
Our app needs three routes that do the following:
- Display a list of all of our tasks
- Add new tasks
- Delete existing tasks
We'll wrap all of these routes in the web middleware so they have session state and CSRF protection.
Let's put all of these routes in app/Http/routes.php file:
<?php use App\Task; use Illuminate\Http\Request; Route::group(['middleware' => 'web'], function () { /** * Show Task Dashboard */ Route::get('/', function () { return view('tasks'); }); /** * Add New Task */ Route::post('/task', function (Request $request) { // }); /** * Delete Task */ Route::delete('/task/{task}', function (Task $task) { // }); });
In get(), we pass tasks to the view function will create a View object instance that corresponds to the template in resources/views/tasks.blade.php.
We can check routes using the artisan route:list command:
$ php artisan route:list +--------+----------+-------------+------+---------+------------+ | Domain | Method | URI | Name | Action | Middleware | +--------+----------+-------------+------+---------+------------+ | | GET|HEAD | / | | Closure | web | | | POST | task | | Closure | web | | | DELETE | task/{task} | | Closure | web | +--------+----------+-------------+------+---------+------------+
Let's define a new layout view in resources/views/layouts/app.blade.php. The .blade.php extension instructs the framework to use the Blade templating engine to render the view.
Our new app.blade.php view should look like this:
<!DOCTYPE html> <html lang="en"> <head> <title>Laravel Quickstart - Basic</title> <!-- CSS And JavaScript --> </head> <body> <div class="container"> <nav class="navbar navbar-default"> <!-- Navbar Contents --> </nav> </div> @yield('content') </body> </html>
THe @yield('content') in the layout is a Blade directive that specifies where all child pages that extend the layout can inject their own content.
We now want to define a view (resources/views/tasks.blade.php) that contains a form to create a new task as well as a table that lists all existing tasks.
Let's define this view in resources/views/tasks.blade.php:
@extends('layouts.app') @section('content') <!-- Bootstrap Boilerplate... --> <div class="panel-body"> <!-- Display Validation Errors --> @include('common.errors') <!-- New Task Form --> <form action="{{ url('task') }}" method="POST" class="form-horizontal"> {!! csrf_field() !!} <!-- Task Name --> <div class="form-group"> <label for="task" class="col-sm-3 control-label">Task</label> <div class="col-sm-6"> <input type="text" name="name" id="task-name" class="form-control"> </div> </div> <!-- Add Task Button --> <div class="form-group"> <div class="col-sm-offset-3 col-sm-6"> <button type="submit" class="btn btn-default"> <i class="fa fa-plus"></i> Add Task </button> </div> </div> </form> </div> <!-- TODO: Current Tasks --> @endsection
The @extends directive informs Blade that we are using the layout we defined in resources/views/layouts/app.blade.php.
All of the content between @section('content') and @endsection will be injected into the location of the @yield('content') directive within the app.blade.php layout.
The @include('common.errors') directive will load the template located at resources/views/common/errors.blade.php though we haven't defined this template yet.
Now we have defined a basic layout and view for our / route.
Now that we have a form in our view, we may want to add code to our POST /task route to validate the incoming form input and create a new task.
We will make the name field required with less than 255 characters. If the validation fails, we will redirect the user back to the / URL, as well as flash the old input and errors into the session.
/** * Add New Task */ Route::post('/task', function (Request $request) { $validator = Validator::make($request->all(), [ 'name' => 'required|max:255', ]); if ($validator->fails()) { return redirect('/') ->withInput() ->withErrors($validator); } // Create The Task... });
Flashing the input into the session will allow us to maintain the user's input even when there are validation errors.
In the code, the ->withErrors($validator); call will flash the errors from the given validator instance into the session so that they can be accessed via the $errors variable in our view.
The common.errors in @include('common.errors') directive will allow us to easily show validation errors in the same format across all of our pages.
Let's define the contents of the view in previous section. Our new file (resources/views/common/errors.blade.php) should look like this:
@if (count($errors) > 0) <!-- Form Error List --> <div class="alert alert-danger"> <strong>Whoops! Something went wrong!</strong> <br><br> <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif
The $errors variable is available in every Laravel view.
Now that input validation is handled, let's actually create a new task by continuing to fill out our route.
Once the new task has been created, we will redirect the user back to the / URL.
To create the task, we may use the save method after creating and setting properties on a new Eloquent model:
<?php use App\Task; use Illuminate\Http\Request; Route::group(['middleware' => 'web'], function () { /** * Show Task Dashboard */ Route::get('/', function () { return view('tasks'); }); /** * Add New Task */ Route::post('/task', function (Request $request) { $validator = Validator::make($request->all(), [ 'name' => 'required|max:255', ]); if ($validator->fails()) { return redirect('/') ->withInput() ->withErrors($validator); } $task = new Task; $task->name = $request->name; $task->save(); return redirect('/'); }); /** * Delete Task */ Route::delete('/task/{task}', function (Task $task) { // }); });
First, we need to edit our / route to pass all of the existing tasks to the view.
The view function accepts a second argument which is an array of data that will be made available to the view, where each key in the array will become a variable within the view:
Route::get('/', function () { $tasks = Task::orderBy('created_at', 'asc')->get(); return view('tasks', [ 'tasks' => $tasks ]); });
Once the data is passed, we can work on the tasks in our tasks.blade.php view and display them in a table.
@extends('layouts.app') @section('content') <!-- Bootstrap Boilerplate... --> <div class="panel-body"> <!-- Display Validation Errors --> @include('common.errors') <!-- New Task Form --> <form action="{{ url('task') }}" method="POST" class="form-horizontal"> {!! csrf_field() !!} <!-- Task Name --> <div class="form-group"> <label for="task" class="col-sm-3 control-label">Task</label> <div class="col-sm-6"> <input type="text" name="name" id="task-name" class="form-control"> </div> </div> <!-- Add Task Button --> <div class="form-group"> <div class="col-sm-offset-3 col-sm-6"> <button type="submit" class="btn btn-default"> <i class="fa fa-plus"></i> Add Task </button> </div> </div> </form> </div> <!-- Current Tasks --> @if (count($tasks) > 0) <div class="panel panel-default"> <div class="panel-heading"> Current Tasks </div> <div class="panel-body"> <table class="table table-striped task-table"> <!-- Table Headings --> <thead> <th>Task</th> <th> </th> </thead> <!-- Table Body --> <tbody> @foreach ($tasks as $task) <tr> <!-- Task Name --> <td class="table-text"> <div>{{ $task->name }}</div> </td> <td> <!-- TODO: Delete Button --> </td> </tr> @endforeach </tbody> </table> </div> </div> @endif @endsection
The @foreach Blade construct allows us to write concise loops that compile down into blazing fast plain PHP code.
Let's add a delete button to each row of our task listing within the tasks.blade.php view.
We'll create a small single-button form for each task in the list:
@extends('layouts.app') @section('content') <!-- Bootstrap Boilerplate... --> <div class="panel-body"> <!-- Display Validation Errors --> @include('common.errors') <!-- New Task Form --> <form action="{{ url('task') }}" method="POST" class="form-horizontal"> {!! csrf_field() !!} <!-- Task Name --> <div class="form-group"> <label for="task" class="col-sm-3 control-label">Task</label> <div class="col-sm-6"> <input type="text" name="name" id="task-name" class="form-control"> </div> </div> <!-- Add Task Button --> <div class="form-group"> <div class="col-sm-offset-3 col-sm-6"> <button type="submit" class="btn btn-default"> <i class="fa fa-plus"></i> Add Task </button> </div> </div> </form> </div> <!-- Current Tasks --> @if (count($tasks) > 0) <div class="panel panel-default"> <div class="panel-heading"> Current Tasks </div> <div class="panel-body"> <table class="table table-striped task-table"> <!-- Table Headings --> <thead> <th>Task</th> <th> </th> </thead> <!-- Table Body --> <tbody> @foreach ($tasks as $task) <tr> <!-- Task Name --> <td class="table-text"> <div>{{ $task->name }}</div> </td> <!-- Delete Button --> <td> <form action="{{ url('task/'.$task->id) }}" method="POST"> {!! csrf_field() !!} {!! method_field('DELETE') !!} <button>Delete Task</button> </form> </td> </tr> @endforeach </tbody> </table> </div> </div> @endif @endsection
Now that we have a "Delete" button, we need a way to tell our route to actually delete the given task.
We can use implicit model binding to automatically retrieve the Task model that corresponds to the {task} route parameter.
In our route callback, we will use the delete method to delete the record.
Once the record is deleted, we will redirect the user back to the / URL:
<?php use App\Task; use Illuminate\Http\Request; Route::group(['middleware' => 'web'], function () { /** * Show Task Dashboard */ Route::get('/', function () { $tasks = Task::orderBy('created_at', 'asc')->get(); return view('tasks', [ 'tasks' => $tasks ]); }); /** * Add New Task */ Route::post('/task', function (Request $request) { $validator = Validator::make($request->all(), [ 'name' => 'required|max:255', ]); if ($validator->fails()) { return redirect('/') ->withInput() ->withErrors($validator); } $task = new Task; $task->name = $request->name; $task->save(); return redirect('/'); }); /** * Delete Task */ Route::delete('/task/{task}', function (Task $task) { $task->delete(); return redirect('/'); }); });
Let's run our app on local using the following command:
$ php artisan serve Laravel development server started on http://localhost:8000/
Then, open up the browser and test!
Let's run our app on Apache:
Then, open up the browser and test!
Here are the steps to configure the Apache:
- Put the project under /var/www.
- Set premission related:
- /etc/apache2/sites-available/laravel.example.com.conf:
- Enable rewrite:
- Start server:
$ sudo chown -R www-data.www-data /var/www/todo $ sudo chmod -R 775 /var/www/todo/app
<VirtualHost *:80> ServerName laravel.example.com DocumentRoot /var/www/todo/public <Directory /> AllowOverride None </Directory> <Directory /var/www/todo/public> AllowOverride All Require all granted Options Indexes FollowSymLinks </Directory> ErrorLog ${APACHE_LOG_DIR}/error.log LogLevel warn CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost>
$ sudo a2enmod rewrite
$ sudo service apache2 restart
Configuration on Nginx can be found ToDo list running on Nginx.
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization