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.