In this articke, we will make a basic todo application with Laravel 5.8. You will be learning all the basic and important parts of this framework.
Let’s get started.
Laravel is the most popular PHP framework built by Taylor Otwell and community. It uses MVC architecture pattern. It provides a lot of features like a complete Authentication System, Database Migrations , A Powerful ORM, Pagination, and so on. Before creating the application, you will need to have PHP 7.2 and MySQL (we are not using apache in this series) and composer installed. I use xampp which is a package that comes with PHP, MySQL, and Apache. Composer is a dependency manager for PHP. It’s similar to npm which is a dependency manager for Javascript. Also, read the server requirements for laravel if you run into installation errors.
Laravel provides an installer which can be used for creating Laravel projects. It can be install with composer:
composer global require "laravel/installer"
Now we can create our laravel project with:
laravel new myapp
Above command will create our project. We can also use composer to create a laravel project (which I do):
composer create-project laravel/laravel myapp
A Model is used to interact with the database and of course it does not have to be a database. It could be a JSON File or some other resource. A Controller contains the logic e.g how to validate form data and save a resource to the database by interacting through Model. View is the User Interface (UI) of the application that contains HTML or the presentation markup. It can also have logic e.g loops and conditionals. Template engines are used to embed logic in Views. Laravel has Blade template engine that is used for adding logic inside the views. you can read more about MVC at wikipedia.
Let’s look at the basic folder structure of laravel application e.g where are Views, Models, and Controllers etc.
In laravel, there are two routes file web.php and api.php. web.php file is used for registering all the web routes like mywebsite.com/about or mywebsite.com/contact. api.php is used for registering all the routes related to an api. We are only using web routes so don’t worry about any api routes.
Models are stored in app directory and there is a default user model that comes with laravel for authentication purposes. app\Http\Controllers has all the controllers and there are some default controllers for authentication.
Views can be found in resources/views and welcome.blade.php is a default view provided by laravel. Laravel views have a .blade.php extension so whenever you create a view don’t forget to add the blade extension. resources/sass has all sass files and js has vuejs components and bootstrap files (we won’t talk about that).
You can put your css, js, and all other static assets in public directory. Config directory has all the configuration files related to our application like database, session, and other configurations (we will talk about config in the next tutorial). There are other directories but you don’t need to worry about that.
Since the root directory of the project is public, we will have to create a virtual host but instead of creating a virtual host, I want to make it simple by using Laravel development server with command (you should be inside the root of your project directory):
php artisan serve
This will start the development server on port 8000. By any chance, if you can’t use that port then you can use **— **port flag to specify a different port:
php artisan serve --port=8080
Routing is a mechanism by which requests (as specified by a URL and HTTP method) are routed to the code that handles them and that code in MVC is a controller method. e.g / will be your home and /about will be your about page. If you open up the web.php file, you will see just one route:
<?php
Route::get('/', function () {
return view('welcome');
});
We have a ‘/’ route that uses a callback/closure making a GET request that returns a view named welcome. In a route, the first parameter is the url and the second parameter can be a callback or controller action. Let’s create a controller named PagesController and update our route. In laravel, we have the ability to name our routes which comes in handy when you change the url later on. We will define two routes for our index page and about page:
<?php
// syntax: 'ControllerName@MethodName'
Route::get('/','PagesController@index')->name('pages.index');
Route::get('/about','PagesController@about')->name('pages.about');
GET method is used for retrieving data from the server. In our case we will always use GET routes for displaying a plain view or a view with data. With GET method we can pass data to the query string e.g search term or date range for a report but you should not insert sensitive data like email or password since it’s visible in the URL. For passing sensitive data to the server (when we submit a form), we use POST method because the data is sent in the request body. There are other request methods but the most commonly used are GET and POST. PUT method is used for updating data and DELETE for deleting data.
We need to create that PagesController and we can do that by typing this command:
php artisan make:controller PagesController
Laravel uses autoloading and all the classes are namespaced so if you want to create a controller inside a sub-directory then you should use artisan(It will create the directory if it does not exist):
php artisan make:controller Admin/LoginController
Above command will create a LoginController inside a sub-directory called Admin. Most of the stuff can be done through artisan which is a command line tool that comes with laravel. You can create models, controllers, migrations and other files with artisan.
Let’s open the PagesController located in app\Http\Controllers:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class PagesController extends Controller
{
//
}
It has a class PagesController that’s extending Controller class which will provide us all the controller functionality. It also has a Request class imported with “use” statement which is used to access form data and validation. In this controller, we need two methods namely index and about, both will return views. let’s add them:
public function index(){
return view('pages.index');
}
public function about(){
return view('pages.about');
}
Index method is returning index view stored inside the pages folder and about method is returning a view named about. In laravel, you don’t have to specify view extension and if you have a view inside a folder e.g index view inside posts folder then use dots like view(‘posts.index’).
Also If you want to see the list of all the registered routes in your application then type the below artisan command:
php artisan route:list
First of all, we will create a layout file which will have all the markup that gets repeated by views e.g doctype, head, and body tags. Usually, layout files are stored in layouts folder in views directory, named app (you can name it whatever you want). let’s create an app.blade.php file after creating layouts folder and add this markup:
<!DOCTYPE html>
<html lang="en">
<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">
<link rel="stylesheet" href="{{asset('css/app.css')}}"> {{-- <- bootstrap css --}}
<title>@yield('title','Laravel 5.8 Basics')</title>
</head>
<body>
{{-- That's how you write a comment in blade --}}
@include('inc.navbar')
<main class="container mt-4">
@yield('content')
</main>
@include('inc.footer')
<script src="{{asset('js/app.js')}}"></script> {{-- <- bootstrap and jquery --}}
</body>
</html>
Blade directives start with “@” symbol. @include() is used for including a separate view file. As you can see inc is the name of the folder and navbar is the name of the view. @yield() will output the content of the view section that’s extending this layout. Double curly braces “{{}}” are used for echoing a variable or calling a helper function. Asset() helper is used for generating URLs to public assets stored in public directory like CSS, Images, and Javascript. Note that laravel comes with bootstrap 4 and that’s what we are including.
Let’s create navbar.blade.php in inc folder and add the markup:
<nav class="navbar navbar-expand-md navbar-light navbar-laravel">
<div class="container">
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a href="{{route('pages.index')}}" class="nav-link">Home</a>
</li>
<li class="nav-item">
<a href="{{route('pages.about')}}" class="nav-link">About</a>
</li>
</ul>
<!-- Right Side Of Navbar -->
<ul class="navbar-nav ml-auto">
</ul>
</div>
</div>
</nav>
Inside the navbar we are using url() helper which is used for generating application urls. route() function is used to generate urls of named routes. config() is used for retrieving values from files inside the config folder, app.name will return “name” value from the app.php config file, second parameter is a fallback value or else it will return null. You can use dot syntax for accessing values inside of an array e.g config(‘file.array.string’,’fallback value’).
Now whenever you create a view just add the below lines of code. All the content of your page will be inside the @section directive.
@extends('layouts.app')
@section('content')
My Page Content
@endsection
Index.blade.php view inside the pages folder will have the following markup:
@extends('layouts.app')
@section('content')
<h2 class="mt-5 mb-3 text-center">Laravel 5.8 For Beginners!</h2>
<p class="text-center">Lorem ipsum dolor sit amet consectetur adipisicing elit Aut.</p>
@endsection
And for the about.blade.php view:
@extends('layouts.app')
@section('title')
Laravel 5.8 Basics | About Page
@endsection
@section('content')
<h3>About Page</h3>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit.</p>
@endsection
As you have noticed, we have a @yield directive inside the title html tag and we are defining the section inside our about view so when you will access the about page from the browser you will see that the title on the browser tab is changed.
In the next tutorial, we will talk about accessing and updating config values from .env file then we will create, read, update, and delete todos with MySQL database.
Source code till this tutorial HERE | complete project HERE
In this tutorial, we will talk about Models, Database Migrations and make CRUD (CRUD means Create, Read, Update, and Delete from a database) operations on todos table with MySQL.
Before creating migrations, We will need to setup our database, assuming you know how to create a database using phpmyadmin. After creating the database, we will add the database credentials in our application. Laravel has a **.**env environment file which will have all the sensitive data like database details, mail driver details, etc because it’s not recommended to store such information directly inside the code (environment files are not limited to PHP. They are used in all other major frameworks). Values inside the **.**env files are loaded inside the files from the config directory. .env file is located at the root of our application. Let’s take a look at the file (Below is not the complete env file. I have just paste the variables that are important for beginners to know):
#Use double quotes if you have spaces between values e.g APP_NAME="My First Application"
APP_NAME=Laravel #Application name
APP_ENV=local #Application environment, can change to "production"
APP_KEY=your_application_key #Application encryption key
APP_DEBUG=true #Display errors
APP_URL=http://localhost #Application URL
#Supports: MySQL, SQL Server, SQLite, and PostgreSQL.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=your_database_name
DB_USERNAME=your_db_username
DB_PASSWORD=your_db_password
All APP values are loaded in config/app.php config file and DB values in config/database.php.
Note: Whenever you making changes to .env file then don’t forget to restart the server ( if you are using laravel dev server) and if you are using a virtual host and changes don’t seem to take effect then just run php artisan config:clear (This command will clear the configuration cache) in your terminal.### Migrations
Since we have setup our database, now let’s take a look at database migrations. Migration are used to store the details about a database table and it’s properties so you don’t have to manually create all the tables by going to the database interface or something like phpmyadmin. We can create migrations using artisan with “make:migration” command:
php artisan make:migration create_todos_table
In laravel, the name of the model has to be singular and the name of the migration should be plural so it can automatically find the table name. You can find these migration files in database/migrations directory. Laravel by default comes with two migrations namely users and password_resets (all migration files are prepended with a timestamp), which are used for authentication. If you want to create a migration but have a different table name in mind then you can explicitly define the table name with “ — create” flag:
php artisan make:migration create_todos_table --create=todos
When you open the create_todos_table.php. You will see two methods, up() and down(). up() is used for creating/updating tables, columns, and indexes. The down() method is used for reversing the operation done by up() method.
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateTodosTable extends Migration
{
public function up()
{
Schema::create('todos', function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('todos');
}
}
Inside up() method, We have Schema:create(‘table_name’,callback) method which will be used for creating a new table. Inside the callback, we have $table->bigIncrements(‘id’) which will create an auto_increment integer column with a primary key and argument ‘id’ is the name of the column. Second one is $table->timestamps() which will create two timestamp columns created_at and updated_at. created_at will be filled when a row is created and updated_at when a row is updated. We will add two columns title and body (for the sake of simplicity) but if you want to know the available columns types then see the list.
Schema::create('todos', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('title')->unique(); //unique varchar equivalent column
$table->text('body'); //text equivalent column
$table->timestamps();
});
You can also specify length to a string column as the second argument:
$table->string('title',255);
Before running any migration commands we need to add a line of code inside the AppServiceProvider.php which can be found in app/Providers directory. Open the file and inside the boot() method, add this line:
\Schema::defaultStringLength(191);
Above code will set the default length of a string column to 191. Since laravel comes with utf8mb4 charset as default which supports emojis and it’s max length for a unique key is 191. If it exceeds the limit then laravel will generate an error and won’t run migrations. If you want to specify a different charset then you can set it in database.php config file.
To run migrations, We can use “migrate” command:
php artisan migrate
Above command will run the migrations and create all the tables. It will execute the up() method. If you want to reverse the migrations, you can use migrate:rollback command which will execute down() method:
php artisan migrate:rollback
After creating the tables, We will create model that will interact with the database resource (table) e.g Post model for posts table. To create a model we simply need to run make:model command:
php artisan make:model Todo
We can also add -m flag which will create a Model as well as a migration for it.
php artisan make:model Todo -m
Above command will also create a migrating named create_todos_table. You can find all the Models inside the app directory. Let’s open the Todo model.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Todo extends Model
{
//
}
We can specify properties to modify the behavior of a model. Let’s start with $table property which is used to specify the name of the table that this model will interact with. By default, It will define the table name as the plural of the model name e.g todos table for Todo model and users table for User model. When you don’t want to use timestamps on your table then you will also have to specify $timestamps property and set it to false in your Model because Laravel expects your table to have created_at and updated_at timestamp columns.
class Todo extends Model
{
protected $table = 'todos';
public $timestamps = false;
}
Above is just an example, so you don’t need to put it in your code to follow this series.
Now all the basic stuff is done, let’s start creating views for our todos and add functionality to our TodosController. We will have 4 views Index, Create, Edit, and Show. So let’s create a folder named todos inside the views directory and create all those four views. After that, we will first create the controller:
php artisan make:controller TodosController --resource
Note that we have also added a — resource flag which will define six methods inside the TodosController namely:
Note: Whenever you making changes to .env file then don’t forget to restart the server ( if you are using laravel dev server) and if you are using a virtual host and changes don’t seem to take effect then just run php artisan config:clear (This command will clear the configuration cache) in your terminal.
Let’s add the routes to our web.php files:
Route::get('/todos','TodosController@index')->name('todos.index');
Route::get('/todos/create','TodosController@create')->name('todos.create');
Route::post('/todos','TodosController@store')->name('todos.store'); // making a post request
Route::get('/todos/{id}','TodosController@show')->name('todos.show');
Route::get('/todos/{id}/edit','TodosController@edit')->name('todos.edit');
Route::put('/todos/{id}','TodosController@update')->name('todos.update'); // making a put request
Route::delete('/todos/{id}','TodosController@destroy')->name('todos.destroy'); // making a delete request
We can pass dynamic parameters with {} brackets and you might have noticed that show, update, and destroy has the same url but different methods so it’s legit. Just like — resource flag, laravel has a method called resource() that will generate all the above routes. You can also use that method instead of specifying them individually like above:
Route::resource('/todos','TodosController');
After defining routes, we need to add the links to our navbar so open the navbar.blade.php view inside the inc folder and update the
that has the links for the right side of our navbar:<!-- Right Side Of Navbar -->
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a href="{{route('todos.index')}}" class="nav-link">Todos</a>
</li>
<li class="nav-item">
<a href="{{route('todos.create')}}" class="nav-link">New Todo</a>
</li>
</ul>
public function index()
{
$todos = Todo::orderBy('created_at','desc')->paginate(8);
return view('todos.index',[
'todos' => $todos,
]);
}
Todo::where('completed',true)->take(8)->get(); // only take 8 todos from the database. <- just an example, we don't have completed column
Todo::where('title','LIKE',"{%}$search_keyword{%}")->get(); // with LIKE operator.
Todo::where('title','LIKE',"{%}$search_keyword{%}")->take(8)->get(); //retrieve only 8 results.
Todo::select(['title','body'])->findOrFail(id); // for specific columns, you can also chain where() with select().
Todo::where('title','value')->whereOr('body','value')->firstOrFail(); // with SQL OR operator
Todo::where('title','value')->whereAnd('body','value')->firstOrFail(); // with SQL AND operator.
@extends('layouts.app')
@section('content')
<h2 class="text-center">All Todos</h2>
<ul class="list-group py-3 mb-3">
@forelse($todos as $todo)
<li class="list-group-item my-2">
<h5>{{$todo->title}}</h5>
<p>{{str_limit($todo->body,20)}}</p>
<small class="float-right">{{$todo->created_at->diffForHumans()}}</small>
<a href="{{route('todos.show',$todo->id)}}">Read More</a>
</li>
@empty
<h4 class="text-center">No Todos Found!</h4>
@endforelse
</ul>
<div class="d-flex justify-content-center">
{{$todos->links()}}
</div>
@endsection
php artisan vendor:publish --tag=laravel-pagination
{{$todos->links('vendor.pagination.bootstrap-4')}} // bootstrap-4 is the name of the pagination view
public function create()
{
return view('todos.create');
}
@extends('layouts.app')
@section('content')
<h3 class="text-center">Create Todo</h3>
<form action="{{route('todos.store')}}" method="post">
@csrf
<div class="form-group">
<label for="title">Todo Title</label>
<input type="text" name="title" id="title" class="form-control {{$errors->has('title') ? 'is-invalid' : '' }}" value="{{old('title')}}" placeholder="Enter Title">
@if($errors->has('title'))
<span class="invalid-feedback">
{{$errors->first('title')}}
</span>
@endif
</div>
<div class="form-group">
<label for="body">Todo Description</label>
<textarea name="body" id="body" rows="4" class="form-control {{$errors->has('body') ? 'is-invalid' : ''}}" placeholder="Enter Todo Description">{{old('body')}}</textarea>
@if($errors->has('body')) {{-- <-check if we have a validation error --}}
<span class="invalid-feedback">
{{$errors->first('body')}} {{-- <- Display the First validation error --}}
</span>
@endif
</div>
<button type="submit" class="btn btn-primary">Create</button>
</form>
@endsection
Note: Whenever you making changes to .env file then don’t forget to restart the server ( if you are using laravel dev server) and if you are using a virtual host and changes don’t seem to take effect then just run php artisan config:clear (This command will clear the configuration cache) in your terminal.* Installing and setting up Laravel locally.
public function store(Request $request)
{
//validation rules
$rules = [
'title' => 'required|string|unique:todos,title|min:2|max:191',
'body' => 'required|string|min:5|max:1000',
];
//custom validation error messages
$messages = [
'title.unique' => 'Todo title should be unique', //syntax: field_name.rule
];
//First Validate the form data
$request->validate($rules,$messages);
//Create a Todo
$todo = new Todo;
$todo->title = $request->title;
$todo->body = $request->body;
$todo->user_id = Auth::id();
$todo->save(); // save it to the database.
//Redirect to a specified route with flash message.
return redirect()
->route('todos.index')
->with('status','Created a new Todo!');
}
$request->input('field_name'); // access an input field
$request->has('field_name'); // check if field exists
$request->title; // dynamically access input fields
request('key') // you can use this global helper if needed inside a view
return redirect('/todos'); // to a specific url
return redirect(url('/todos')); // to a specific url with url helper
return redirect(url()->previous()); // to a previous url
return redirect()->back(); // redirect back (same as above)
@if(session('status')) {{-- <- If session key exists --}}
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{session('status')}} {{-- <- Display the session value --}}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
@endif
<script>
//close the alert after 3 seconds.
$(document).ready(function(){
setTimeout(function() {
$(".alert").alert('close');
}, 3000);
});
</script>
<style>
.alert{
z-index: 99;
top: 60px;
right:18px;
min-width:30%;
position: fixed;
animation: slide 0.5s forwards;
}
@keyframes slide {
100% { top: 30px; }
}
@media screen and (max-width: 668px) {
.alert{ /* center the alert on small screens */
left: 10px;
right: 10px;
}
}
</style>
@extends('layouts.app')
@section('content')
<h3 class="text-center">{{$todo->title}}</h3>
<p>{{$todo->body}}</p>
<br>
<a href="{{route('todos.edit',$todo->id)}}" class="btn btn-primary float-left">Update</a>
<a href="#" class="btn btn-danger float-right" data-toggle="modal" data-target="#delete-modal">Delete</a>
<div class="clearfix"></div>
<div class="modal fade" id="delete-modal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Delete Todo</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p>Are you sure!</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" onclick="document.querySelector('#delete-form').submit()">Proceed</button>
<button type="button" class="btn btn-primary" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
<form method="POST" id="delete-form" action="{{route('todos.destroy',$todo->id)}}">
@csrf
@method('DELETE')
</form>
@endsection
public function show($id)
{
$todo = Todo::findOrFail($id);
return view('todos.show',[
'todo' => $todo,
]);
}
$todo = Todo::where('title','this is title')->firstOrFail();
public function destroy($id)
{
//Delete the Todo
$todo = Todo::findOrFail($id);
$todo->delete();
//Redirect to a specified route with flash message.
return redirect()
->route('todos.index')
->with('status','Deleted the selected Todo!');
}
public function edit($id)
{
//Find a Todo by it's ID
$todo = Todo::findOrFail($id);
return view('todos.edit',[
'todo' => $todo,
]);
}
@extends('layouts.app')
@section('content')
<h3 class="text-center">Edit Todo</h3>
<form action="{{route('todos.update',$todo->id)}}" method="post">
@csrf
@method('PUT')
<div class="form-group">
<label for="title">Todo Title</label>
<input type="text" name="title" id="title" class="form-control {{ $errors->has('title') ? 'is-invalid' : '' }}" value="{{ old('title') ? : $todo->title }}" placeholder="Enter Title">
@if($errors->has('title')) {{-- <-check if we have a validation error --}}
<span class="invalid-feedback">
{{$errors->first('title')}} {{-- <- Display the First validation error --}}
</span>
@endif
</div>
<div class="form-group">
<label for="body">Todo Description</label>
<textarea name="body" id="body" rows="4" class="form-control {{ $errors->has('body') ? 'is-invalid' : '' }}" placeholder="Enter Todo Description">{{ old('body') ? : $todo->body }}</textarea>
@if($errors->has('body')) {{-- <-check if we have a validation error --}}
<span class="invalid-feedback">
{{$errors->first('body')}} {{-- <- Display the First validation error --}}
</span>
@endif
</div>
<button type="submit" class="btn btn-primary">Update</button>
</form>
@endsection
public function update(Request $request, $id)
{
//validation rules
$rules = [
'title' => "required|string|unique:todos,title,{$id}|min:2|max:191", //Using double quotes
'body' => 'required|string|min:5|max:1000',
];
//custom validation error messages
$messages = [
'title.unique' => 'Todo title should be unique',
];
//First Validate the form data
$request->validate($rules,$messages);
//Update the Todo
$todo = Todo::findOrFail($id);
$todo->title = $request->title;
$todo->body = $request->body;
$todo->save(); //Can be used for both creating and updating
//Redirect to a specified route with flash message.
return redirect()
->route('todos.show',$id)
->with('status','Updated the selected Todo!');
}
#laravel #php #web-development