Saturday, December 29, 2018

A Simple Laravel CRUD App

A Simple Laravel CRUD App

A Laravel CRUD Sample

Laravel is an excellent PHP framework for web development. It creates a RESTful web app with just a few commands. The procedures are shown as the following steps:

1. Create a New Project

$> laravel new laravel_product
Crafting application...
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Package operations: 86 installs, 0 updates, 0 removals
  - Installing doctrine/inflector (v1.3.0): Loading from cache
  - Installing doctrine/lexer (v1.0.1): Loading from cache
  - Installing dragonmantank/cron-expression (v2.2.0): 
                    :
                    :
Package manifest generated successfully.
Application ready! Build something amazing.

After creating a new project named 'laravel_product', the files needed have been created under the directory.

$> cd laravel_product
$> php artisan -v
Laravel Framework 5.7.19

2: Configure MySQL Database

Edit .env file

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_product
DB_USERNAME=xxxx
DB_PASSWORD=xxxx

Add a database in MySQL server

Add a database manually by using Sequel Pro and following parameters.

  • Database Name: laravel_product
  • Database Encoding: UTF-8 Unicode (utf8mb4)
  • Database Collation: Default (utf8mb4_general_ci)

Create database

Create tables

$> php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table

3: Create a Model 'Product' and a Migration File.

$> php artisan make:model Product -m
Model created successfully.
Created Migration: 2018_12_26_181923_create_products_table

Edit migration file 2018_12_26_181923_create_products_table.php

Insert fields between id and timestamps:

  $table->increments('id');
    :
  $table->string('prod_name');
  $table->text('prod_desc');
  $table->integer('prod_price');
  $table->integer('prod_qty'); 
    :
  $table->timestamps();

Create table 'products'

$> php artisan migrate
Migrating: 2018_12_26_181923_create_products_table
Migrated:  2018_12_26_181923_create_products_table

Add the fillable property inside Product.php file.

<?php
  namespace App;
  use Illuminate\Database\Eloquent\Model;

  class Product extends Model {
      protected $fillable = [
        'prod_name',
        'prod_desc',
        'prod_price',
        'prod_qty'
      ];
  }

4: Create Routes and Controller

Create ProductController.php

Use following command to create controller.

  • php artisan make:controller
$> php artisan make:controller ProductController --resource
Controller created successfully.

Add routes code in /routes/web.php file.

Route::resource('products', 'ProductController');

Show routes

$> php artisan route:list
+-----------+-------------------------+------------------+-----------+
| Method    | URI                     | Name             | Action    |
+-----------+-------------------------+------------------+-----------+
| GET|HEAD  | /                       |                  | Closure   |
| GET|HEAD  | api/user                |                  | Closure   |
| GET|HEAD  | products                | products.index   | *@index   |
| POST      | products                | products.store   | *@store   |
| GET|HEAD  | products/create         | products.create  | *@create  |
| GET|HEAD  | products/{product}      | products.show    | *@show    |
| PUT|PATCH | products/{product}      | products.update  | *@update  |
| DELETE    | products/{product}      | products.destroy | *@destroy |
| GET|HEAD  | products/{product}/edit | products.edit    | *@edit    |
+-----------+-------------------------+------------------+-----------+
* = App\Http\Controllers\ProductController

5: Create the views

a. Create a file layout.blade.php inside '/resources/views' folder.

b. Create a folder 'products' inside '/resources/views' folder.

c. Then, create the following three files inside '/resource/view/products'.

  • create.blade.php
  • edit.blade.php
  • index.blade.php

d. Edit layout.blade.php

<!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-Compatiable" content="ie=edge">
 <title>Laravel 5.7 A simple CRUD Example</title>
    <link href="{{ asset('css/app.css') }}" rel="stylesheet" type="text/css">
</head>
<body>
 <!-- Navbar start -->
  <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
    <a class="navbar-brand" href="{{ route('products.index') }}">
    A Laravel CRUD Sample</a>
    <button class="navbar-toggler" type="button" data-toggle="collapse" 
      data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" 
      aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarsExampleDefault">
      <ul class="navbar-nav mr-auto">
        <li class="nav-item active">
          <a class="nav-link" href="{{ route('products.create') }}">
            Add A Product 
            <span class="sr-only">(current)</span></a>
        </li>
      </ul>
      <form method="post" action="#" class="form-inline my-2 my-lg-0">
        <input class="form-control mr-sm-2" type="text" placeholder="Search" 
          aria-label="Search">
        <button class="btn btn-outline-success my-2 my-sm-0" 
          type="submit">Search</button>
      </form>
    </div>
  </nav> 
  <!-- Navbar end -->
 <div class="container">
  @yield('content')
 </div>
 <script src="{{ asset('js/app.js') }}"></script>
</body>
</html>

e. Edit create.blade.php

Remember to add @csrf in the tag in order to have CSRF Protection.

<form method="post" action="{{ route('products.store') }}"> 
  @csrf  // must added.
     :
</form>

create.blade.php is as follows:

@extends('layout')

@section('content')
<div class="card uper">
 <div class="card-header">
  <h4>Add Product</h4>
 </div>
 <div class="card-body">
   @if ($errors->any())
  <div class="alert alert-danger">
    <ul>
      @foreach ($errors->all() as $error)
     <li>{{ $error }}</li>
     <button type="button" class="close" data-dismiss="alert" 
       aria-label="Close">
       <span aria-hidden="true">&times;</span>
              </button>
   @endforeach 
    </ul>
    </div><br/>
  @endif
  <form method="post" action="{{ route('products.store') }}">
    @csrf
    <div class="form-group">
      <label for="prod_name">Product Name:</label>
            <input type="text" class="form-control" name="prod_name"/>
    </div>
    <div class="form-group">
      <label for="prod_desc">Product Description:</label>
            <input type="text" class="form-control" name="prod_desc"/>
    </div>
          <div class="form-group">
      <label for="prod_price">Product Price:</label>
      <input type="text" class="form-control" name="prod_price"/>
    </div>
    <div class="form-group">
      <label for="prod_qty">Product Quantity:</label>
   <input type="text" class="form-control" name="prod_qty"/>
    </div>
    <button type="submit" class="btn btn-primary">Add</button>
    <a href="{{ route('products.index') }}" class="btn btn-info">Back</a>
   </form>
    </div>
  </div>
@endsection

f. Code 'create' function in ProductController.php

Remember to add use App\Product; in the controller in order to use the Model 'Product'.

<?php
  namespace App\Http\Controllers;
  use Illuminate\Http\Request;
  use App\Product;  // must add

  public function create() 
    {
      return view('shares.create');
    }

6: Save Data

Code the "store" function in the ProductController.php

public function store(Request $request)
  {
    $request->validate([
      'prod_name'  => 'required',
      'prod_desc'  => 'required',
      'prod_price' => 'required|integer',
      'prod_qty'   => 'required|integer'
    ]);
    $product = new Product([
      'prod_name'  => $request->get('prod_name'),
      'prod_desc'  => $request->get('prod_desc'),
      'prod_price' => $request->get('prod_price'),
      'prod_qty'   => $request->get('prod_qty')
    ]);
    $product->save();
    return redirect('/products')->with('success','Product has been added!');
  }

Add some testing data and save.

7: Display the data.

Code index.blade.php

@extends('layout')

@section('content')
<div class="uper">
 @if(session()->get('success'))
  <div class="alert alert-success">
    {{ session()->get('success') }}
    <button type="button" class="close" data-dismiss="alert" aria-label="Close">
   <span aria-hidden="true">&times;</span>
          </button>
   </div><br/>
 @endif
</div>
<table class="table table-hover">
  <thead> 
 <tr>
  <td><b>ID</b></td>
  <td><b>Product Name</b></td>
  <td><b>Product Description</b></td>
  <td><b>Product Price</b></td>
  <td><b>Product Qty</b></td>
  <td colspan="2"><b>Action</b></td>
 </tr>
  </thead>
  <tbody>
   @foreach($products as $product)
   <tr>
     <td>{{$product->id}}</td>
     <td>{{$product->prod_name}}</td>
     <td>{{$product->prod_desc}}</td>
     <td>{{$product->prod_price}}</td>
     <td>{{$product->prod_qty}}</td>
     <td><a href="{{ route('products.edit', $product->id) }}" 
    class="btn btn-primary">Edit</a></td>
     <td>
        <form method="post" action="{{ route('products.destroy', $product->id) }}">
          @method('DELETE')
          @csrf
          <button type="submit" class="btn btn-danger">Delete</button>
        </form>
      </td>
   </tr>
   @endforeach
  </tbody>
</table>
<div class="pagination justify-content-center">
 {{ $products->links() }}
</div>
@endsection

Code the "index" function in the ProductController.php

public function index() 
  {
    $products = Product::all();
    return view('products.index', compact('products'));
  }

8: Edit and Update Data

Code the "edit" function in the ProductController.php

public function edit($id) 
  {
    $product = Product::find($id);
    return view('products.edit', compact('product'));
  }

Code "edit.blade.php"

@extends('layout')

@section('content')
<div class="card uper">
  <div class="card-header">
   <h4>Edit Product</h4>
  </div>
  <div class="card-body">
   @if ($errors->any())
     <div class="alert alert-danger">
      <ul>
        @foreach ($errors->all() as $error)
          <li>{{ $error }}</li>
          <button type="button" class="close" data-dismiss="alert" 
            aria-label="Close">
     <span aria-hidden="true">&times;</span>
            </button>
        @endforeach
      </ul>
     </div><br/>
   @endif 
   <form method="post" action="{{ route('products.update', $product->id) }}">
     @method('PATCH')
     @csrf
     <div class="form-group">
      <label for="prod_name">Product Name</label>
      <input type="text" class="form-control" name="prod_name" 
        value={{ $product->prod_name }} /> 
     </div>
     <div class="form-group">
      <label for="prod_desc">Product Description</label>
      <input type="text" class="form-control" name="prod_desc" 
        value={{ $product->prod_desc }} /> 
     </div>
     <div class="form-group">
      <label for="prod_price">Product Price</label>
      <input type="text" class="form-control" name="prod_price" 
        value={{ $product->prod_price }} /> 
     </div>
     <div class="form-group">
      <label for="prod_qty">Product Qty</label>
    <input type="text" class="form-control" name="prod_qty" 
      value={{ $product->prod_qty }} /> 
   </div>
   <button type="submit" class="btn btn-primary">Update</button>
    </form>
  </div>
</div>
@endsection

Code the "update" function in the ProductController.php

public function update(Request $request, $id)
  {
    $request->validate([
      'prod_name'  => 'required',
      'prod_desc'  => 'required',
      'prod_price' => 'required|integer',
      'prod_qty'   => 'required|integer'
    ]);
    $product = Product::find($id);
    $product->prod_name  = $request->get('prod_name');
    $product->prod_desc  = $request->get('prod_desc');
    $product->prod_price = $request->get('prod_price');
    $product->prod_qty   = $request->get('prod_qty');
    $product->save();
    return redirect('/products')->with('success', 'Product has been updated!');
  }

9: Delete the data

Code the "delete" function in ProductController.php

public function destroy($id)
  {
    $product = Product::find($id);
    $product->delete();
    return redirect('/products')->with('success', 'Product has been deleted!');
  }

10: Start the CRUD web app.

Start the server:

$> php artisan serve

Go to "http://localhost:8000/products" and will see the product index page.

11: Pagination

If it needs to have the pagination functionality, it's just as easy as doing two things.

Modify the 'index' function in ProductController.php.

a. change to pagination method.

Just change

$products = Product::all();

to:

// number: the record number per page.
$products = Product::paginate(number);  

Pagination links:

  • $products->links()
     :
    </table>
  <div class="pagination justify-content-center">
    {{ $products->links() }}
  </div>
@endsection

That's all! Isn't it simple and easy!

Thursday, December 27, 2018

The Work Around Solution to Composer Broken When PHP 7.3 Installed via Homebrew

How to Add Constants in Larvel 5

There was an error came out after I upgraded PHP version to 7.3. When I execute composer --version or composer update, a lot of error messages shown:

PHP Warning: preg_match(): JIT compilation failed: no more memory in 
phar:///usr/local/bin/composer.phar/vendor/symfony/console/Application.php on line 755
   :
   :

The composer was broken!

This is a known PHP 7.3 bug.

After searching the solution, somebody suggests a work around below:

  1. Edit php.ini file (locate at /usr/local/etc/php/7.3/php.ini).

  2. Disable PHP PCRE JIT compilation:

    [Pcre]
    ;pcre.jit=1 
    pcre.jit=0
    

Then the composer is alive again! It’s great!
Remember to change back to the original setting while the bug fix.

Tuesday, December 4, 2018

Database Connection Setup in Laravel

How to Add Constants in Larvel 5

Lraravel supports MySQL, PostgreSQL, SQL Server and SQLite database. Database setup is stored in .env file and config/database.php.

The priority of database setup information is:
.env > config/database.php

Setup procedure for database connection:

SQLite:

  1. Add a empty sqlite database file.
    > touch database/database.sqlite
  2. Comment out:
   #DB_CONNECTION=mysql 
   #DB_HOST=127.0.0.1
   #DB_PORT=3306
   #DB_DATABASE=homestead
   #DB_USERNAME=homestead
   #DB_PASSWORD=secret
  1. Add commends to .env file:
   DB_CONNECTION=sqlite  
   DB_DATABASE=database/database.sqlite
  1. php artisan migrate

MySQL:

  1. Add a new database in mysql server.
  2. Edit .env file:
   DB_CONNECTION=mysql 
   DB_HOST=127.0.0.1
   DB_PORT=3306
   DB_DATABASE=xxxxx
   DB_USERNAME=xxxxx
   DB_PASSWORD=xxxx
  1. php artisan migrate
   Migration table created successfully.
   Migrating: 2014_10_12_000000_create_users_table
   Migrated:  2014_10_12_000000_create_users_table
   Migrating: 2014_10_12_100000_create_password_resets_table
   Migrated:  2014_10_12_100000_create_password_resets_table

Using artisan command to create a database in mysql.

What if I want to create a database in Laravel?
Please refer to this article Using an Artisan command to create a database in Laravel or Lumen

Monday, December 3, 2018

The Installation of Laravel

How to Add Constants in Larvel 5

Laravel is a PHP framework for web development. Here goes the installation procedure.

1. Install Composer (if not installed)

> brew install composer

2. Install Laravel

> composer global require laravel/installer

3. Add path to $PATH

Edit and add the following code to the shell configuration file:

  • zsh: .zshrc

  • bash: .bash_profile

# Laravel
export PATH="$HOME/.composer/vendor/bin:$PATH"

Close and restart the terminal.

4. Start coding

> cd (your laravel project directory)
> laravel new (project name)
  :
  :
> cd (project name)
> php artisan serve

-> open browser
-> http://localhost:8000

Tuesday, November 13, 2018

How to Upload Multiple Files in PHP

How to Add Constants in Larvel 5

When we upload multiple files using PHP, the $_FILES variable as an array is not as a usual form as we think. It is formed by using file’s attribute as the key, not the index number. Thus, we can not loop through $_FILES variable with PHP 'foreach' loop statement.

1. $_FILES Variable Structure

The $_FILES variable is formed as follows:

array(5) {
["name"]=> array(2) { [0]=> string(17) "IMG_2586w_1x1.JPG"
                      [1]=> string(17) "IMG_2541w_1x1.JPG"
                    }
["type"]=> array(2) { [0]=> string(10) "image/jpeg"
                      [1]=> string(10) "image/jpeg"
                    }
["tmp_name"]=> array(2) { [0]=> string(26) "/private/var/tmp/phpwJBYnV"
                          [1]=> string(26) "/private/var/tmp/phpzbodce"
                        }
["error"]=> array(2) { [0]=> int(0)
                       [1]=> int(0)
                     }
["size"]=> array(2)  { [0]=> int(952096)
                       [1]=> int(879215)
                     }
}

This form of array needs to be converted to the form below, where the file’s index is the key instead of file’s attribute. This step is necessary for going to further process.

array(2) {
[0]=> array(5) { ["name"]=> string(17) "IMG_2586w_1x1.JPG"
                 ["type"]=> string(10) "image/jpeg"
                 ["tmp_name"]=> string(26) "/private/var/tmp/phpt3l7ct"
                 ["error"]=> int(0)
                 ["size"]=> int(952096)}
[1]=> array(5) { ["name"]=> string(17) "IMG_2541w_1x1.JPG"
                 ["type"]=> string(10) "image/jpeg"
                 ["tmp_name"]=> string(26) "/private/var/tmp/phpPIhTD2"
                 ["error"]=> int(0)
                 ["size"]=> int(879215)}
}

2. Converting Function

A converting function is written as follows:

/* convert multiple uploading files array $_FILES 
   to an array with an index as the key.
   from : [attribute] => [ [index] => [value], [index] => [value] ]
   to:    [index] => [ [attribute] => [value], [attribute] => [value] ]
*/
function convert_upload_file_array($upload_files) {
  $converted = array();

  foreach ($upload_files as $attribute => $val_array) {
    foreach ($val_array as $index => $value) {
      $converted[$index][$attribute] = $value;
    }
  }
  return $converted;
}

3. Multiple Uploading Files Setup

Now, the multiple files uploading process can be started from a upload form page:

upload.html

<form action="product.php" method="post" enctype="multipart/form-data">
  <input type="file" class="form-control-file" name="prod_pic[]" id="prod_pic[]">
  <button type="submit" class="btn btn-primary">Save Prodcut Info</button>
</form>

Note that the 'name' property of the <input> tag should be added an array tag '[]'. In addition, enctype="multipart/form-data" should be added in the tag in order to enable the file uploading function. Thus, the $_FILES variable can be an array to carry multiple files information.

<form .... enctype="multipart/form-data">
     :
  <input ... name="prod_pic[]">
  <input ... name="prod_pic[]">
     :
</form>  

Then, the product.php converts $_FILE variable and save files to the destination path.

product.php

<?php
  if (isset($_FILES['prod_pic'])) {
    $pics = convert_upload_file_array($_FILES['prod_pic']);

    foreach ($pics as $key => $pic) {
      $target = "images/{$pic['name']}";
      move_uploaded_file($pic['tmp_name'], $target);
    }
  }
?>

Monday, November 12, 2018

Insert and Update An Image to MySQL Server Field with Blob Data Type in PHP

How to Add Constants in Larvel 5

Normally the images are stored in a file directory, not in the database. It is because that storing image data in database will make database bulky. In addition, there is some techniques to take care of. How to store and retrieve an image from MySQL server? Here is how:

1. Blob data type in MySQL server

First, create a blob type field in MySQL server:

mysql> describe products;
+-----------------+-----------------------+------+-----+---------+----------------+
| Field           | Type                  | Null | Key | Default | Extra          |
+-----------------+-----------------------+------+-----+---------+----------------+
| prod_id         | mediumint(8) unsigned | NO   | PRI | NULL    | auto_increment |
| prod_pic        | mediumblob            | YES  |     | NULL    |                |
| prod_pic_type   | varchar(11)           | YES  |     | NULL    |                |
+-----------------+-----------------------+------+-----+---------+----------------+

2. Insert into MySQL server:

Add enctype="multipart/form-data" in the <form> tag in order to enable the file upload functionality.
Set the type to ‘file’ for <input> tag.

Upload image file form:

<form action="index.php" method="post" enctype="multipart/form-data">
  <input type="file" name="prod_pic" accept=".jpg, .jpeg, .png">
  <button type="submit">Submit</button>  
</form>

Insert image into MySQL server in index.php

$prod_pic = $mysqli->real_escape_string(file_get_contents($_FILES['prod_pic']['tmp_name']));
$prod_pic_type = $_FILES['prod_pic']['type'];
 :
 :
$sql = "UPDATE products 
        SET prod_pic      = '{$prod_pic}',
            prod_pic_type = '{$prod_pic_type}'
        WHERE prod_id = {$prod_id}";
$mysqli->query($sql) or die($mysqli->connect_error);
$mysqli->close(); 

As you can see, the key part is:

file_get_contents($_FILES['prod_pic']['tmp_name'])

and

$mysqli->real_escape_string(......)

3. Retrieve an image from MySQL server:

There are two methods to retrieve images from MySQL server:

A. Get image with a function.

Somewhere in the page: (using Smarty template engine)

 <img src="get_image.php?prod_id={$product.prod_id}" />

get_image.php

<?php
  $mysqli = new mysqli( _DBHOST, _DBUSER, _DBPASSWD, _DBNAME);
  if ($mysqli->connect_error){
    die ('DB Error: ('.$mysqli->connect_errno.' )'.$mysqli->connect_error);
  } else {
    $mysqli->set_charset('utf8');
  }

  $prod_id = isset($_REQUEST['prod_id']) ? filter_var($_REQUEST['prod_id'], FILTER_SANITIZE_NUMBER_INT) : 0;

  $sql = "SELECT prod_pic, prod_pic_type FROM products WHERE prod_id = '{$prod_id}'";
  $result = $mysqli->query($sql) or die($mysqli->connect_error);
  $product = $result->fetch_assoc();
  $mysqli->close();

  header("Content-type: ".$product['prod_pic_type']);
  echo $product['prod_pic'];
?>

The last two lines of code is the key part.

header("Content-type: ".$product['prod_pic_type']);  
echo  $product['prod_pic'];

B. Using base64 encode to show the image

<img src="data:image/{$product.prod_pic_type};base64,{base64_encode($product.prod_pic)}" />

( using Smarty template engine )

4. Conclusion

Method B seems to be a better choice since it does not need to retrieve data from server twice as method A does. Also, method B is more concise and cleaner. It’s only one line of code and an extra PHP function is not necessary.

Monday, November 5, 2018

Deploy A PHP MySQL Application to Heroku

How to Add Constants in Larvel 5

I usually put my apps on Heroku since I have developed apps in Ruby on Rails. It’s a reliable PaaS service and provides a free dyno for users. Therefore, Heroku is an excellent place to show your apps to others.

Heroku is known as a very friendly PaaS service for Ruby on Rail. Just few git commends, an app in RoR can be easily deployed to Heroku. Now it supports PHP as well. The following steps are the real operations of the deployment of my app to Heroku. It seems that very simple and similar to the deployment of RoR, only clearDB (MySQL) needs to be installed manually.

Install Composer

Composer is PHP’s package manager. Using brew to install composer is the easiest way in macOS.

$ brew install composer

Add An Empty composer.json File in the Root Directory

The Heroku PHP Support will be applied to applications only when the application has a file named composer.json in the root directory. Just add a text file with a pair of curly braces in the root directory if there is no depending package.

composer.json

{}

git commit

$ git add .
$ git commit -m ‘add composer.json file’

Heroku Login

Follow the instruction from: Getting Started with PHP : Set-up

$ heroku login
heroku: Enter your login credentials
Email [chxxxxxx@gmail.com]:
Password: *************
Logged in as chxxxxxx@gmail.com

Deploy the App

Create An App Name

$ heroku create
Creating app... done, ⬢ dry-crag-62595
https://dry-crag-62595.herokuapp.com/ | https://git.heroku.com/dry-crag-62595.git

or

$ heroku create [app_name] 

[app_name]: the name of the app

Show The git remote Configuration

$ git remote -v
heroku  https://git.heroku.com/dry-crag-62595.git (fetch)
heroku  https://git.heroku.com/dry-crag-62595.git (push)
origin  http://github.com/chaoyee/php_crud_demo.git (fetch)
origin  http://github.com/chaoyee/php_crud_demo.git (push)

Deploy the App

$ git push heroku master
Enumerating objects: 300, done.
Counting objects: 100% (300/300), done.
Delta compression using up to 8 threads
Compressing objects: 100% (295/295), done.
Writing objects: 100% (300/300), 1.03 MiB | 10.37 MiB/s, done.
Total 300 (delta 93), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> PHP app detected
remote: -----> Bootstrapping...
remote: -----> Installing platform packages...
remote: NOTICE: No runtime required in composer.lock; using PHP ^7.0.0
remote: - nginx (1.8.1)
remote: - php (7.2.11)
remote: - apache (2.4.34)
remote: -----> Installing dependencies...
remote: Composer version 1.7.2 2018-08-16 16:57:12
remote: -----> Preparing runtime environment...
remote: NOTICE: No Procfile, using 'web: heroku-php-apache2'.
remote: -----> Checking for additional extensions to install...
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote:
remote: -----> Compressing...
remote: Done: 16.2M
remote: -----> Launching...
remote: Released v3
remote: https://dry-crag-62595.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/dry-crag-62595.git
* [new branch] master -> master

View Logs

If there is any error, we can view the log for more information.

$ heroku logs --tail

2018-11-05T08:24:32.207801+00:00 app[api]: Release v1 created by user chaoyee22@gmail.com
2018-11-05T08:24:32.313450+00:00 app[api]: Enable Logplex by user chaoyee22@gmail.com
2018-11-05T08:24:32.207801+00:00 app[api]: Initial release by user chaoyee22@gmail.com
2018-11-05T08:29:59.153108+00:00 heroku[web.1]: State changed from starting to up
:
:

Define A Procfile

A Procfile is a text file in the root directory of the application which defines process types and explicitly declares what command should be executed to start the application.

Procfile:

web: heroku-php-apache2

Change Apps Name

Since Heroku generates a random name for the app, we can change it if needed.

$ heroku apps:rename [newname]
$ git remote rm heroku
$ git remote add heroku https://git.heroku.com/[newname].git

[newname]: the new name of the app.

Create ClearDB (MySQL)

ClearDB is a database service based on MySQL Server for Heroku.

$ heroku addons:create cleardb:ignite

Creating cleardb:ignite on ⬢ php-crud-demo... free
Created cleardb-animated-20025 as CLEARDB_DATABASE_URL
Use heroku addons:docs cleardb to view documentation

Get the database url:

$ heroku config | grep CLEAR_DATABASE_URL

=== php-crud-demo Config Vars

CLEARDB_DATABASE_URL: mysql://bf4e1f86266e0e:xxxxxxxx@[us-cdbr-iron-east-01.cleardb.net/heroku_340c60cc91508db?reconnect=true

The format of CLEARDB_DATABASE_URL is:
CLEARDB_DATABASE_URL =>
mysql://[username}:[password]@[host]/[database name]?reconnect=true

Login to MySQL Server

$ mysql -u bf4e1f86266e0e -h us-cdbr-iron-east-01.cleardb.net -p
password: ******
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 114469329
Server version: 5.5.56-log MySQL Community Server (GPL)
 
Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

Now, the MySQL Server is installed.

Create Tables and Insert Test data

Using Sequel Pro, MySQL Workbench, phpMyAdmin or other applications to execute sql script and create tables and insert test data.

Adding following PHP code to database configuration (ex. config.php) in the Application in order to connect to MySQL server.

<?php  
$url = parse_url(getenv("CLEARDB_DATABASE_URL"));  
define('_DBHOST', $url["host"]);  
define('_DBUSER', $url["user"]);  
define('_DBPASSWD', $url["pass"]);  
define('_DBNAME', substr($url["path"], 1));

$mysqli = new mysqli( _DBHOST, _DBUSER, _DBPASSWD, _DBNAME);
?>

Deploy Application to Heroku

$ git add .
$ git commit -m ‘add db connection’
$ git push heroku master

Open App at Heroku

$ heroku open

Now, the app starts up in the browser from Heroku service.
Here is the demo app php-crud-demo.

Wednesday, October 31, 2018

How to Post Codebase on GitHub without Revealing Database Login and Password Parameters in PHP

How to Add Constants in Larvel 5

It’s quite simple.

Inside configuration file congif.php:

<?php 
  include("secret.php");
  $password = PASSWORD;  //defined in secret.php
  $username = USERNAME;
?>

Inside secret.php

<?php
  define('PASSWORD', 'example_password');    
  define('USERNAME', 'example_username') ;   
?>

Inside .gitignore, just add:

path/secret.php

path: the path to secret.php

Therefore, secret.php will not be uploaded to GitHub and keep these secret parameters safe!

Tuesday, October 16, 2018

Do Not Use Cookies as The Way We Use Variables

How to Add Constants in Larvel 5

Considering the code as follows:

<?php
  setcookie("test", "1", time() + 3600);  // set cookie
  echo $_COOKIE["test"];
?>

The result shows ‘undefined index’ after execution:

Notice: Undefined index: test in /xxx/index.php on line 2

Why is this happened? Has cookie not been set?
No, it is because the browser hasn’t sent a HTTP request to the server, so the browser hasn’t got the request cookie yet.

According to the document rfc6252 from Internet Engineering Task Force (IETF), it explains how cookies work at the introduction:

Using the Set-Cookie header field, an HTTP server can pass name/value pairs and associated metadata (called cookies) to a user agent. When the user agent makes subsequent requests to the server, the user agent uses the metadata and other information to determine whether to return the name/value pairs in the Cookie header.

Hmm~~, what does that mean exactly?

Let’s open the Chrome Developer Tools and show you how cookies works.

I wrote three php programs as follows:

  1. set_cookie.php: to set cookie ‘test’ and value as 1.
<?php
  setcookie("test", "1", time() + 3600);
  echo $_COOKIE["test"];
?>
  1. show_cookie.php: show the value of cookie ‘test’.
<?php
  echo $_COOKIE["test"];
?>
  1. delete_cookie.php: delete the cookie 'test.
<?php
  setcookie("test", "", time() - 3600);
?>

Check the cookies in Chrome Devtools

  1. Now, let’s start to run set_cookie.php and see what will happen through Chrome devtools. In Chrome devtools windows, there is a Response Cookie ‘test’ as 1 shown in the ‘Cookies windows’. The cookie ‘test’ is not shown at the side of ‘Request Cookies’ meaning that the browser hasn’t received cookie data from server yet. So, it shows ‘undefined index’ message in the browser as expected.

run set_cookie.php
set_cookie

  1. Next, let’s run show_cookie.php to send a HTTP request to the server and show the cookie ‘test’. It shows cookie ‘test’ and value as 1 at the side of ‘Request Cookies’. As expected, the browser shows ‘1’, the value of cookie ‘test’.

run show_cookie.php
show_cookie

  1. let’s run delete_cookie.php to delete the cookie ‘test’. It shows a ‘delete’ value at the side of ‘Response Cookies’. The cookie ‘test’ and value 1 is still at the side of ‘Request Cookies’.

delete_cookie.php
delete_cookie

  1. Let’s run show_message.php again to send a HTTP request to the server. It also send the response cookies to the server and delete the cookie ‘test’. The cookie ‘test’ has disappeared at both sides of the ‘Cookies windows’. Therefore, it shows ‘undefined index’ in the browser again.

show_cookie again
show_cookie again

Use cookies for storing data while variables for calculation.

As shown above, the behavior of cookies are different from variables. We can not use cookies just as the way we use variables.

Do not make the calculation according to cookies values if a HTTP request hasn’t been sent to the server. Using variables to do so to make sure the correct calculation.

Friday, October 12, 2018

Four Ways to Show Message Bar After Redirecting to Another Page in PHP

How to Add Constants in Larvel 5

There are four ways to show a message bar up front of the web page when needed in PHP. As shown below, the main function of the message bar is to notify users a warning message.

Message Bar

Four ways to build up a message bar are as follows:

1. Passing message by assigning a smarty variable.

index.php

<?php    
         :
$msg = 'Please login!';
         :
$smarty->assign('msg', $msg);    
// redirect to index.html
$smarty->display('index.html'); 
  
?> 

index.html

{if (isset($msg)}
  {$msg}
{/if}

It works very good except one situation: program redirects to another page and exits the original one before Smarty variable has been assigned.

Considering a situation below:

<?php
         :
if !login() {
  $msg = 'Please login!';
  header("location:login.php");
  exit;
}
         :
$smarty->assign('msg', $msg);    
// redirect to index.html
$smarty->display('index.html'); 
?>

Normally, we put $smarty->assign('msg', $msg); at the end of index.php file. It will not work in the above example since a header() command redirects to the login page and exits index.php. $msg will not be assigned to a Smarty variable.

2. Query string parameters

ex.
shopping_cart.html

<a href="index.php?msg=Cart is empty!">Empty Cart</a>  

index.php

$msg = isset($_REQUEST['msg']) ? 
  filter_var($_REQUEST['msg'], 
  FILTER_SANITIZE_MAGIC_QUOTES) : '';
echo $msg;

It shows a long line of words in the url windows of the browser. I really don’t like it. It is not a good way to do it.

ex.
index.php

      :
setcookie('msg', 'Hello! New user!', time() + 365*86400);
      :
function show_message() {
  $msg = $_COOKIE['msg'];
  setcookie('msg', '', time() - 3600);
  return $msg;
} 

index.html

{if isset($smarty.cookies.msg)}
  {show_message()}
{/if}  

It seems to be a good solution, However, it resulted in some unexpected error while using cookie method. It was not stable when I tested it. The browser could not render the correct page. I recommend not to use it.

4. Using Session

ex.
index.php

<?php
session_start();
       :
       :
write_message('Hello! New user!');
header("location:{$_SERVER['HTTP_REFERER']}");
  
function write_message($msg = '') {
  $_SESSION['msg'] = $msg;
}

function show_message() {
  $msg = $_SESSION['msg'];
  unset($_SESSION['msg']);
  return $msg;
}
?> 

index.html

{if isset($smarty.session.msg)}
  <div class="alert alert-danger alert-dismissible show" role="alert">
    {show_message()}
    <button type="button" class="close" data-dismiss="alert" aria-label="Close">
      <span aria-hidden="true">&times;</span>
    </button>
  </div>
{/if} 

The session method is the most recommended method to show a message bar at the redirected page. It’s quite easy, simple and no restriction at all. You can insert write_message('xxxxx') anywhere in code and it will show up in the redirected page. The only one drawback to criticize is that it consumes server’s resources since session runs on the server.