Spize Tech

The blog help to build your skill.

Data Structure and algorithm

Questions and Answers

More


In the previous tuturial, We talked about laravel framework, How to install it, An introduction to MVC architecture, Created routes, controllers. and views. 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.

.ENV File

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 credentials, mail driver credentials, 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
    .env

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.

Migration

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');
    }
}
    empty_create_todos_table.php

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.
<?php
public function up()
{
    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();
    });   
}
    up_method_todos_migration.php

You can also specify length to a string column as the second argument:
<?php
$table->string('title',255);
    optional_string_length.php

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:
<?php
\Schema::defaultStringLength(191);
    default_string_length.php

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

Models

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
{
    //
}
empty_Todo.php

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.

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Todo extends Model
{
    protected $table = 'todos';
        
    public $timestamps = false;
    
}
    timestamps_table_props_model.php

Above is just an example, so you don’t need to put it in your code to follow this series.

 Todos CRUD

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:
  1. Index (used for displaying a list of Todos)
  2. Create (will show a view with form for creating a Todo)
  3. Store (used for creating a Todo inside the database. Note: create method submits to store method)
  4. Show (will display a specified Todo)
  5. Edit (will show a form for editing a Todo. Form will be filled with the existing Todo data)
  6. Update (Used for updating a Todo inside the database. Note: edit submits to update method)
  7. Destroy (used for deleting a specified Todo)
These methods are not specific to TodosController. It can be used for PostsController, CommentsController or any other resource. (A resource is an object e.g user, todo or anything else)

Let’s add the routes to our web.php files:
<?php
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@destr
    todo_crud_routes.php

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:
<?php
Route::resource('/todos','TodosController');
    todo_resource_route.php

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 <ul>  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>
    right_side_todo_links_navbar.blade.php

Displaying Todos 

Now open the TodosController so we can modify our Index method:
<?php
public function index()
{
    $todos = Todo::orderBy('created_at','desc')->paginate(8);
    return view('todos.index',[
        'todos' => $todos,
    ]);
}
    Index_TodosController.php

We are retrieving all the todos with Todo model and ordering by created_at column in descending order then we are chaining it with paginate() which will create pagination links and by default the items displayed per page is 15. After retrieving todos, We are returning a view with $todos array. We can pass data to a view by passing an array of data as a second parameter to the view() method. 

We also have to import the Todo class to our TodosController:

<?php
namespace App\Http\Controllers;
use App\Todo; // <- here
use Illuminate\Http\Request;
class TodosController extends Controller
{
    // controller code
}
    Import_Todo_TodosController.php

Some helpful methods for retreiving models:
<?php
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.
    retrieve_model_methods.php

Now, we can add the markup to our Index view inside the todos folder:
@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
    todos_index.blade.php

Inside the content section, we are using @forelse() loop to iterate todos and content inside the @empty works as an else statement if an array or collection is empty. Blade has @for() , @foreach(), and @while() loops too. Of course, you need to end this @forelse with @endforelse(). Inside @forelse(), We have some HTML markup and you can see that we are echoing $todo object properties. str_limit() is a helper function used to limit the number of string characters and diffForHumans() is used for displaying date in a more readable format e.g 1 day ago or 1 months ago. We are displaying the pagination links with $todos->links(). By default, Laravel supports bootstrap pagination but if you are using a different framework like materialize-css then you can specify custom pagination view inside the links() method. Laravel already comes with some default pagination views. You can publish these pagination views with below command:
php artisan vendor:publish --tag=laravel-pagination
Above command will copy the pagination views to our views/vendor/pagination directory. Then you can specify the view like this:
{{$todos->links('vendor.pagination.bootstrap-4')}} {{-- bootstrap-4 is the name of the pagination view --}}
    custom_pagination_link.blade.php

There will be more than one pagination views. You can customize the view however you like. Now, when you navigate to todos page, you will see “Nothing found!” since there are not todos in the database.(we will create them now)

Creating A Todo

Let’s modify the create method of TodosController which will simply return a view with a form for creating a todo:
<?php
public function create()
{
    return view('todos.create');
}
    create_method_TodosController.php

Adding the markup to create view inside the todos folder:
@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
    todos_create.blade.php

We have a form with POST method submitting to a name route “todos.store”. Inside the form, we have @csrf will generate a hidden input named csrf_token which is important for security and without that you will not be able to submit a form (You will see a page with “Page expired” text after form submission or a 419 error page). old(‘field_name’) is a helper function used for echoing the old values from a submitted form e.g if you failed the validation and got redirected back to the form then you will need those values that were previously filled. Validation errors can be accessed by $errors object. To check for validation failure, We can use $errors->has(‘field_name’) and $errors->first(‘field_name’) will echo the first validation error.

 “field_name” is the value of the “name” attribute defined in our input fields. In our form, we have “title” and “body” fields.

 Let’s modify the Store method of our TodosController which will store the todo to the database:
<?php
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->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!');
}
    store_method_TodosController.php

Store method has $request object as a parameter which will be used to access form data. First thing you want to do is validate the form data. We can use the $request->validate() method for validation, which will receive an array of validation rules. Validation rules is an associative array. Key will be the field_name and value with be the validation rules.

Second parameter is an optional array for custom validation messages. Rules are separated with pipe sign “|”. We are using the most basic validation rules. First is “required” which means the field_name should not be empty (“nullable” rule is vice versa), “string” means it should be a string value, “min” is the limit of minimum characters for a string in an input field and “max” is the maximum characters. “unique:table,column” with see if the same value does not exists in the database (comes handy for storing emails or any other unique data). 

If validation fails then it will redirect us back. After validation, we are create a new object of the Todo model. Since Todo and Request are both objects, We can access their properties with arrow “->” operator. After specifying the values to the model properties, We can call the save() method which will save the Todo to the database.

 When a Todo is successfully created then we are redirecting the user to a specific route and chaining it with with() method which will make the data available for the next request so we can use it as a flash message. Since this will be stored in session, We can access the value with session(‘status’) as well as check if the a value exists. 

Some Methods for accessing form values that are useful:
<?php
$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
    request_methods.php

Helpful redirecting methods:
<?php
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)
    laravel_redirect_helpers.php

We also need to display the message as bootstrap alert so open the app.blade.php layout file (so we can make this alert available in every view) and add the below code right after the app.js script:

@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">&times;</span>
        </button>
    </div>
@endif
<script>
    //close the alert after 3 seconds.
    $(document).ready(function(){
       setTimeout(function() {
          $(".alert").alert('close');
       }, 3000);
    });
</script>
    flash_messages_app.blade.php

Let’s add a little css in the
<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>
    alert_styles_app.blade.php


Displaying a Single Todo 

Let’s open the show.blade.php and add the following markup:
@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">&times;</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
    todos_show.blade.php

All that’s different in our view is that this time we have a single Todo and since it’s a single object we don’t need a loop. There’s an Update button that with open the edit todo view and there’s also a Delete button that will open the modal for confirmation and when you will click the Proceed button it will submit a hidden form having an id of “delete-form” (we will work on destroy method after show method). We are making a delete request when deleting a specific resource so simple anchor tag will not work and a form is needed. Since HTML forms only support GET and POST methods, We will need to add @method(‘DELETE’) which will generate an input field with a value of DELETE ( PUT for updating a resource). Let’s modify the show method in TodosController.
<?php
public function show($id)
{
    $todo = Todo::findOrFail($id);
    return view('todos.show',[
        'todo' => $todo,
    ]);
}
    show_method_TodosController.php

To get a specific resource with an id column, We can use findOrFail() method this method will throw a 404 error if it fails to find the resource. If you want to find a resource by a different column then you can use where(‘column’,’value’) and chain it to firstOrFail() method. For example:
<?php
$todo = Todo::where('title','this is title')->firstOrFail();
    retrieve_todo.php 

Delete a Todo 

We have already created a button in the show view that submits to this method. Now we can modify the destroy method that is responsible for deleting the Todo and all we need to do in this method is fetch the specified Todo and call delete() method on it.
<?php
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!');
}
    destroy_method_TodosController.php

Updating the Todo 

Edit method is same as show method just views are different because we will display the values of our todo object in an html form:
<?php
public function edit($id)
{
    //Find a Todo by it's ID
    $todo = Todo::findOrFail($id);
    return view('todos.edit',[
        'todo' => $todo,
    ]);
}
    edit_method_TodosController.php

Let’s add the markup for our last view edit.blade.php:
@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
    todos_edit.blade.php

This view is the same as the create.blade.php, the difference is that it’s submitting to update method of TodosController having a @method(‘PUT’) inside the form. We are also echoing the values from the $post in the input field with a ternary operator and if validation fails then we will have old() value instead of $post values. Lastly the update method of our TodosController which is same as store but we are updating an existing resource:
<?php
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!');
}
    update_method_TodosController.php

There is a scenario when you want to update the todo body but not title then you will get a validation error saying the title should be unique. In these cases we can specify the id of that model to ignore. Now we can create, display all todos, display a single todo, update them, and lastly delete them, Next tutorial will be on authentication and middleware. Feel free to comment below or create an issue in the github repo.

Here’s the link to the previous tutorial:

Here’s the link to the next tutorial:
 - Part 3


Click here to Download project Source Code

No comments:

Post a Comment