Table of Contents for
PHP 7: Real World Application Development

Version ebook / Retour

Cover image for bash Cookbook, 2nd Edition PHP 7: Real World Application Development by Branko Ajzele Published by Packt Publishing, 2016
  1. Cover
  2. Table of Contents
  3. PHP 7: Real World Application Development
  4. PHP 7: Real World Application Development
  5. PHP 7: Real World Application Development
  6. Credits
  7. Preface
  8. What you need for this learning path
  9. Who this learning path is for
  10. Reader feedback
  11. Customer support
  12. 1. Module 1
  13. 1. Building a Foundation
  14. PHP 7 installation considerations
  15. Using the built-in PHP web server
  16. Defining a test MySQL database
  17. Installing PHPUnit
  18. Implementing class autoloading
  19. Hoovering a website
  20. Building a deep web scanner
  21. Creating a PHP 5 to PHP 7 code converter
  22. 2. Using PHP 7 High Performance Features
  23. Understanding the abstract syntax tree
  24. Understanding differences in parsing
  25. Understanding differences in foreach() handling
  26. Improving performance using PHP 7 enhancements
  27. Iterating through a massive file
  28. Uploading a spreadsheet into a database
  29. Recursive directory iterator
  30. 3. Working with PHP Functions
  31. Developing functions
  32. Hinting at data types
  33. Using return value data typing
  34. Using iterators
  35. Writing your own iterator using generators
  36. 4. Working with PHP Object-Oriented Programming
  37. Developing classes
  38. Extending classes
  39. Using static properties and methods
  40. Using namespaces
  41. Defining visibility
  42. Using interfaces
  43. Using traits
  44. Implementing anonymous classes
  45. 5. Interacting with a Database
  46. Using PDO to connect to a database
  47. Building an OOP SQL query builder
  48. Handling pagination
  49. Defining entities to match database tables
  50. Tying entity classes to RDBMS queries
  51. Embedding secondary lookups into query results
  52. Implementing jQuery DataTables PHP lookups
  53. 6. Building Scalable Websites
  54. Creating a generic form element generator
  55. Creating an HTML radio element generator
  56. Creating an HTML select element generator
  57. Implementing a form factory
  58. Chaining $_POST filters
  59. Chaining $_POST validators
  60. Tying validation to a form
  61. 7. Accessing Web Services
  62. Converting between PHP and XML
  63. Creating a simple REST client
  64. Creating a simple REST server
  65. Creating a simple SOAP client
  66. Creating a simple SOAP server
  67. 8. Working with Date/Time and International Aspects
  68. Using emoticons or emoji in a view script
  69. Converting complex characters
  70. Getting the locale from browser data
  71. Formatting numbers by locale
  72. Handling currency by locale
  73. Formatting date/time by locale
  74. Creating an HTML international calendar generator
  75. Building a recurring events generator
  76. Handling translation without gettext
  77. 9. Developing Middleware
  78. Authenticating with middleware
  79. Using middleware to implement access control
  80. Improving performance using the cache
  81. Implementing routing
  82. Making inter-framework system calls
  83. Using middleware to cross languages
  84. 10. Looking at Advanced Algorithms
  85. Using getters and setters
  86. Implementing a linked list
  87. Building a bubble sort
  88. Implementing a stack
  89. Building a binary search class
  90. Implementing a search engine
  91. Displaying a multi-dimensional array and accumulating totals
  92. 11. Implementing Software Design Patterns
  93. Creating an array to object hydrator
  94. Building an object to array hydrator
  95. Implementing a strategy pattern
  96. Defining a mapper
  97. Implementing object-relational mapping
  98. Implementing the Pub/Sub design pattern
  99. 12. Improving Web Security
  100. Filtering $_POST data
  101. Validating $_POST data
  102. Safeguarding the PHP session
  103. Securing forms with a token
  104. Building a secure password generator
  105. Safeguarding forms with a CAPTCHA
  106. Encrypting/decrypting without mcrypt
  107. 13. Best Practices, Testing, and Debugging
  108. Using Traits and Interfaces
  109. Universal exception handler
  110. Universal error handler
  111. Writing a simple test
  112. Writing a test suite
  113. Generating fake test data
  114. Customizing sessions using session_start parameters
  115. A. Defining PSR-7 Classes
  116. Implementing PSR-7 value object classes
  117. Developing a PSR-7 Request class
  118. Defining a PSR-7 Response class
  119. 2. Module 2
  120. 1. Setting Up the Environment
  121. Setting up Debian or Ubuntu
  122. Setting up CentOS
  123. Setting up Vagrant
  124. Summary
  125. 2. New Features in PHP 7
  126. New operators
  127. Uniform variable syntax
  128. Miscellaneous features and changes
  129. Summary
  130. 3. Improving PHP 7 Application Performance
  131. HTTP server optimization
  132. HTTP persistent connection
  133. Content Delivery Network (CDN)
  134. CSS and JavaScript optimization
  135. Full page caching
  136. Varnish
  137. The infrastructure
  138. Summary
  139. 4. Improving Database Performance
  140. Storage engines
  141. The Percona Server - a fork of MySQL
  142. MySQL performance monitoring tools
  143. Percona XtraDB Cluster (PXC)
  144. Redis – the key-value cache store
  145. Memcached key-value cache store
  146. Summary
  147. 5. Debugging and Profiling
  148. Profiling with Xdebug
  149. PHP DebugBar
  150. Summary
  151. 6. Stress/Load Testing PHP Applications
  152. ApacheBench (ab)
  153. Siege
  154. Load testing real-world applications
  155. Summary
  156. 7. Best Practices in PHP Programming
  157. Test-driven development (TDD)
  158. Design patterns
  159. Service-oriented architecture (SOA)
  160. Being object-oriented and reusable always
  161. PHP frameworks
  162. Version control system (VCS) and Git
  163. Deployment and Continuous Integration (CI)
  164. Summary
  165. A. Tools to Make Life Easy
  166. Git – A version control system
  167. Grunt watch
  168. Summary
  169. B. MVC and Frameworks
  170. Laravel
  171. Lumen
  172. Apigility
  173. Summary
  174. 3. Module 3
  175. 1. Ecosystem Overview
  176. Summary
  177. 2. GoF Design Patterns
  178. Structural patterns
  179. Behavioral patterns
  180. Summary
  181. 3. SOLID Design Principles
  182. Open/closed principle
  183. Liskov substitution principle
  184. Interface Segregation Principle
  185. Dependency inversion principle
  186. Summary
  187. 4. Requirement Specification for a Modular Web Shop App
  188. Wireframing
  189. Defining a technology stack
  190. Summary
  191. 5. Symfony at a Glance
  192. Creating a blank project
  193. Using Symfony console
  194. Controller
  195. Routing
  196. Templates
  197. Forms
  198. Configuring Symfony
  199. The bundle system
  200. Databases and Doctrine
  201. Testing
  202. Validation
  203. Summary
  204. 6. Building the Core Module
  205. Dependencies
  206. Implementation
  207. Unit testing
  208. Functional testing
  209. Summary
  210. 7. Building the Catalog Module
  211. Dependencies
  212. Implementation
  213. Unit testing
  214. Functional testing
  215. Summary
  216. 8. Building the Customer Module
  217. Dependencies
  218. Implementation
  219. Unit testing
  220. Functional testing
  221. Summary
  222. 9. Building the Payment Module
  223. Dependencies
  224. Implementation
  225. Unit testing
  226. Functional testing
  227. Summary
  228. 10. Building the Shipment Module
  229. Dependencies
  230. Implementation
  231. Unit testing
  232. Functional testing
  233. Summary
  234. 11. Building the Sales Module
  235. Dependencies
  236. Implementation
  237. Unit testing
  238. Functional testing
  239. Summary
  240. 12. Integrating and Distributing Modules
  241. Understanding GitHub
  242. Understanding Composer
  243. Understanding Packagist
  244. Summary
  245. Bibliography
  246. Index

Using PDO to connect to a database

PDO is a highly performant and actively maintained database extension that has a unique advantage over vendor-specific extensions. It has a common Application Programming Interface (API) that is compatible with almost a dozen different Relational Database Management Systems (RDBMS). Learning how to use this extension will save you hours of time trying to master the command subsets of the equivalent individual vendor-specific database extensions.

PDO is subdivided into four main classes, as summarized in the following table:

Class

Functionality

PDO

Maintains the actual connection to the database, and also handles low-level functionality such as transaction support

PDOStatement

Processes results

PDOException

Database-specific exceptions

PDODriver

Communicates with the actual vendor-specific database

How to do it...

  1. Set up the database connection by creating a PDO instance.
  2. You need to construct a Data Source Name (DSN). The information contained in the DSN varies according to the database driver used. As an example, here is a DSN used to connect to a MySQL database:
    $params = [
      'host' => 'localhost',
      'user' => 'test',
      'pwd'  => 'password',
      'db'   => 'php7cookbook'
    ];
    
    try {
      $dsn  = sprintf('mysql:host=%s;dbname=%s',
      $params['host'], $params['db']);
      $pdo  = new PDO($dsn, $params['user'], $params['pwd']);
    } catch (PDOException $e) {
      echo $e->getMessage();
    } catch (Throwable $e) {
      echo $e->getMessage();
    }
  3. On the other hand, SQlite, a simpler extension, only requires the following command:
    $params = [
      'db'   => __DIR__ . '/../data/db/php7cookbook.db.sqlite'
    ];
    $dsn  = sprintf('sqlite:' . $params['db']);
  4. PostgreSQL, on the other hand, includes the username and password directly in the DSN:
    $params = [
      'host' => 'localhost',
      'user' => 'test',
      'pwd'  => 'password',
      'db'   => 'php7cookbook'
    ];
    $dsn  = sprintf('pgsql:host=%s;dbname=%s;user=%s;password=%s', 
                   $params['host'], 
                   $params['db'],
                   $params['user'],
                   $params['pwd']);
  5. The DSN could also include server-specific directives, such as unix_socket, as shown in the following example:
    $params = [
      'host' => 'localhost',
      'user' => 'test',
      'pwd'  => 'password',
      'db'   => 'php7cookbook',
      'sock' => '/var/run/mysqld/mysqld.sock'
    ];
    
    try {
      $dsn  = sprintf('mysql:host=%s;dbname=%s;unix_socket=%s', 
                      $params['host'], $params['db'], $params['sock']);
      $opts = [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION];
      $pdo  = new PDO($dsn, $params['user'], $params['pwd'], $opts);
    } catch (PDOException $e) {
      echo $e->getMessage();
    } catch (Throwable $e) {
      echo $e->getMessage();
    }

    Note

    Best practice

    Wrap the statement that creates the PDO instance in a try {} catch {} block. Catch a PDOException for database-specific information in case of failure. Catch Throwable for errors or any other exceptions. Set the PDO error mode to PDO::ERRMODE_EXCEPTION for best results. See step 8 for more details about error modes.

    In PHP 5, if the PDO object cannot be constructed (for example, when invalid parameters are used), the instance is assigned a value of NULL. In PHP 7, an Exception is thrown. If you wrap the construction of the PDO object in a try {} catch {} block, and the PDO::ATTR_ERRMODE is set to PDO::ERRMODE_EXCEPTION, you can catch and log such errors without having to test for NULL.

  6. Send an SQL command using PDO::query(). A PDOStatement instance is returned, against which you can fetch results. In this example, we are looking for the first 20 customers sorted by ID:
    $stmt = $pdo->query(
    'SELECT * FROM customer ORDER BY id LIMIT 20');

    Note

    PDO also provides a convenience method, PDO::exec(), which does not return a result iteration, just the number of rows affected. This method is best used for administrative operations such as ALTER TABLE, DROP TABLE, and so on.

  7. Iterate through the PDOStatement instance to process results. Set the fetch mode to either PDO::FETCH_NUM or PDO::FETCH_ASSOC to return results in the form of a numeric or associative array. In this example we use a while() loop to process results. When the last result has been fetched, the result is a boolean FALSE, ending the loop:
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
      printf('%4d | %20s | %5s' . PHP_EOL, $row['id'], 
      $row['name'], $row['level']);
    }

    Note

    PDO fetch operations involve a cursor that defines the direction (that is, forward or reverse) of the iteration. The second argument to PDOStatement::fetch() can be any of the PDO::FETCH_ORI_* constants. Cursor orientations include prior, first, last, absolute, and relative. The default cursor orientation is PDO::FETCH_ORI_NEXT.

  8. Set the fetch mode to PDO::FETCH_OBJ to return results as a stdClass instance. Here you will note that the while() loop takes advantage of the fetch mode, PDO::FETCH_OBJ. Notice that the printf() statement refers to object properties, in contrast with the preceding example, which references array elements:
    while ($row = $stmt->fetch(PDO::FETCH_OBJ)) {
      printf('%4d | %20s | %5s' . PHP_EOL, 
      $row->id, $row->name, $row->level);
    }
  9. If you want to create an instance of a specific class while processing a query, set the fetch mode to PDO::FETCH_CLASS. You must also have the class definition available, and PDO::query() should set the class name. As you can see in the following code snippet, we have defined a class called Customer, with public properties $id, $name, and $level. Properties need to be public for the fetch injection to work properly:
    class Customer
    {
      public $id;
      public $name;
      public $level;
    }
    
    $stmt = $pdo->query($sql, PDO::FETCH_CLASS, 'Customer');
  10. When fetching objects, a simpler alternative to the technique shown in step 5 is to use PDOStatement::fetchObject():
    while ($row = $stmt->fetchObject('Customer')) {
      printf('%4d | %20s | %5s' . PHP_EOL, 
      $row->id, $row->name, $row->level);
    }
  11. You could also use PDO::FETCH_INTO, which is essentially the same as PDO::FETCH_CLASS, but you need an active object instance instead of a class reference. Each iteration through the loop re-populates the same object instance with the current information set. This example assumes the same class Customer as in step 5, with the same database parameters and PDO connections as defined in step 1:
    $cust = new Customer();
    while ($stmt->fetch(PDO::FETCH_INTO)) {
      printf('%4d | %20s | %5s' . PHP_EOL, 
      $cust->id, $cust->name, $cust->level);
    }
  12. If you do not specify an error mode, the default PDO error mode is PDO::ERRMODE_SILENT. You can set the error mode using the PDO::ATTR_ERRMODE key, and either the PDO::ERRMODE_WARNING or the PDO::ERRMODE_EXCEPTION value. The error mode can be specified as the fourth argument to the PDO constructor in the form of an associative array. Alternatively, you can use PDO::setAttribute() on an existing instance.
  13. Let us assume you have the following DSN and SQL (before you start thinking that this is a new form of SQL, please be assured: this SQL statement will not work!):
    $params = [
      'host' => 'localhost',
      'user' => 'test',
      'pwd'  => 'password',
      'db'   => 'php7cookbook'
    ];
    $dsn  = sprintf('mysql:host=%s;dbname=%s', $params['host'], $params['db']);
    $sql  = 'THIS SQL STATEMENT WILL NOT WORK';
  14. If you then formulate your PDO connection using the default error mode, the only clue that something is wrong is that instead of producing a PDOStatement instance, the PDO::query() will return a boolean FALSE:
    $pdo1  = new PDO($dsn, $params['user'], $params['pwd']);
    $stmt = $pdo1->query($sql);
    $row = ($stmt) ? $stmt->fetch(PDO::FETCH_ASSOC) : 'No Good';
  15. The next example shows setting the error mode to WARNING using the constructor approach:
    $pdo2 = new PDO(
      $dsn, 
      $params['user'], 
      $params['pwd'], 
      [PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING]);
  16. If you need full separation of the prepare and execute phases, use PDO::prepare() and PDOStatement::execute() instead. The statement is then sent to the database server to be pre-compiled. You can then execute the statement as many times as is warranted, most likely in a loop.
  17. The first argument to PDO::prepare() can be an SQL statement with placeholders in place of actual values. An array of values can then be supplied to PDOStatement::execute(). PDO automatically provides database quoting, which helps safeguard against SQL Injection.

    Note

    Best practice

    Any application in which external input (that is, from a form posting) is combined with an SQL statement is subject to an SQL injection attack. All external input must first be properly filtered, validated, and otherwise sanitized. Do not put external input directly into the SQL statement. Instead, use placeholders, and provide the actual (sanitized) values during the execution phase.

  18. To iterate through the results in reverse, you can change the orientation of the scrollable cursor. Alternatively, and probably more easily, just reverse the ORDER BY from ASC to DESC. This line of code sets up a PDOStatement object requesting a scrollable cursor:
    $dsn  = sprintf('pgsql:charset=UTF8;host=%s;dbname=%s', $params['host'], $params['db']);
    $opts = [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]; 
    $pdo  = new PDO($dsn, $params['user'], $params['pwd'], $opts);
    $sql  = 'SELECT * FROM customer '
        . 'WHERE balance > :min AND balance < :max '
        . 'ORDER BY id LIMIT 20';
    $stmt = $pdo->prepare($sql, [PDO::ATTR_CURSOR  => PDO::CURSOR_SCROLL]);
  19. You also need to specify cursor instructions during the fetch operation. This example gets the last row in the result set, and then scrolls backwards:
    $stmt->execute(['min' => $min, 'max' => $max]);
    $row = $stmt->fetch(PDO::FETCH_ASSOC, PDO::FETCH_ORI_LAST);
    do {
      printf('%4d | %20s | %5s | %8.2f' . PHP_EOL, 
           $row['id'], 
           $row['name'], 
           $row['level'], 
           $row['balance']);
    } while ($row = $stmt->fetch(PDO::FETCH_ASSOC, PDO::FETCH_ORI_PRIOR));
  20. Neither MySQL nor SQLite support scrollable cursors! To achieve the same results, try the following modifications to the preceding code:
    $dsn  = sprintf('mysql:charset=UTF8;host=%s;dbname=%s', $params['host'], $params['db']);
    $opts = [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]; 
    $pdo  = new PDO($dsn, $params['user'], $params['pwd'], $opts);
    $sql  = 'SELECT * FROM customer '
        . 'WHERE balance > :min AND balance < :max '
        . 'ORDER BY id DESC 
           . 'LIMIT 20';
    $stmt = $pdo->prepare($sql);
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC));
    printf('%4d | %20s | %5s | %8.2f' . PHP_EOL, 
           $row['id'], 
           $row['name'], 
           $row['level'], 
           $row['balance']);
    } 
  21. PDO provides support for transactions. Borrowing the code from step 9, we can wrap the INSERT series of commands into a transactional block:
    try {
        $pdo->beginTransaction();
        $sql  = "INSERT INTO customer ('" 
        . implode("','", $fields) . "') VALUES (?,?,?,?,?,?)";
        $stmt = $pdo->prepare($sql);
        foreach ($data as $row) $stmt->execute($row);
        $pdo->commit();
    } catch (PDOException $e) {
        error_log($e->getMessage());
        $pdo->rollBack();
    }
  22. Finally, to keep everything modular and re-usable, we can wrap the PDO connection into a separate class Application\Database\Connection. Here, we build a connection through the constructor. Alternatively, there is a static factory() method that lets us generate a series of PDO instances:
    namespace Application\Database;
    use Exception;
    use PDO;
    class Connection
    {
        const ERROR_UNABLE = 'ERROR: no database connection';
        public $pdo;
        public function __construct(array $config)
        {
            if (!isset($config['driver'])) {
                $message = __METHOD__ . ' : ' 
                . self::ERROR_UNABLE . PHP_EOL;
                throw new Exception($message);
            }
            $dsn = $this->makeDsn($config);        
            try {
                $this->pdo = new PDO(
                    $dsn, 
                    $config['user'], 
                    $config['password'], 
                    [PDO::ATTR_ERRMODE => $config['errmode']]);
                return TRUE;
            } catch (PDOException $e) {
                error_log($e->getMessage());
                return FALSE;
            }
        }
    
        public static function factory(
          $driver, $dbname, $host, $user, 
          $pwd, array $options = array())
        {
            $dsn = $this->makeDsn($config);
            
            try {
                return new PDO($dsn, $user, $pwd, $options);
            } catch (PDOException $e) {
                error_log($e->getMessage);
            }
        }
  23. An important component of this Connection class is a generic method that can be used to construct a DSN. All we need for this to work is to establish the PDODriver as a prefix, followed by ":". After that, we simply append key/value pairs from our configuration array. Each key/value pair is separated by a semi-colon. We also need to strip off the trailing semi-colon, using substr() with a negative limit for that purpose:
      public function makeDsn($config)
      {
        $dsn = $config['driver'] . ':';
        unset($config['driver']);
        foreach ($config as $key => $value) {
          $dsn .= $key . '=' . $value . ';';
        }
        return substr($dsn, 0, -1);
      }
    }

How it works...

First of all, you can copy the initial connection code from step 1 into a chap_05_pdo_connect_mysql.php file. For the purposes of this illustration, we will assume you have created a MySQL database called php7cookbook, with a username of cook and a password of book. Next, we send a simple SQL statement to the database using the PDO::query() method. Finally, we use the resulting statement object to fetch results in the form of an associative array. Don't forget to wrap your code in a try {} catch {} block:

<?php
$params = [
  'host' => 'localhost',
  'user' => 'test',
  'pwd'  => 'password',
  'db'   => 'php7cookbook'
];
try {
  $dsn  = sprintf('mysql:charset=UTF8;host=%s;dbname=%s',
    $params['host'], $params['db']);
  $pdo  = new PDO($dsn, $params['user'], $params['pwd']);
  $stmt = $pdo->query(
    'SELECT * FROM customer ORDER BY id LIMIT 20');
  printf('%4s | %20s | %5s | %7s' . PHP_EOL, 
    'ID', 'NAME', 'LEVEL', 'BALANCE');
  printf('%4s | %20s | %5s | %7s' . PHP_EOL, 
    '----', str_repeat('-', 20), '-----', '-------');
  while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    printf('%4d | %20s | %5s | %7.2f' . PHP_EOL, 
    $row['id'], $row['name'], $row['level'], $row['balance']);
  }
} catch (PDOException $e) {
  error_log($e->getMessage());
} catch (Throwable $e) {
  error_log($e->getMessage());
}

Here is the resulting output:

How it works...

Add the option to the PDO constructor, which sets the error mode to EXCEPTION. Now alter the SQL statement and observe the resulting error message:

$opts = [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION];
$pdo  = new PDO($dsn, $params['user'], $params['pwd'], $opts);
$stmt = $pdo->query('THIS SQL STATEMENT WILL NOT WORK');

You will observe something like this:

How it works...

Placeholders can be named or positional. Named placeholders are preceded by a colon (:) in the prepared SQL statement, and are references as keys in an associative array provided to execute(). Positional placeholders are represented as question marks (?) in the prepared SQL statement.

In the following example, named placeholders are used to represent values in a WHERE clause:

try {
  $dsn  = sprintf('mysql:host=%s;dbname=%s', 
                  $params['host'], $params['db']);
  $pdo  = new PDO($dsn, 
                  $params['user'], 
                  $params['pwd'], 
                  [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
  $sql  = 'SELECT * FROM customer '
      . 'WHERE balance < :val AND level = :level '
      . 'ORDER BY id LIMIT 20'; echo $sql . PHP_EOL;
  $stmt = $pdo->prepare($sql);
  $stmt->execute(['val' => 100, 'level' => 'BEG']);
  while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    printf('%4d | %20s | %5s | %5.2f' . PHP_EOL, 
      	$row['id'], $row['name'], $row['level'], $row['balance']);
  }
} catch (PDOException $e) {
  echo $e->getMessage();
} catch (Throwable $e) {
  echo $e->getMessage();
}

This example shows using positional placeholders in an INSERT operation. Notice that the data to be inserted as the fourth customer includes a potential SQL injection attack. You will also notice that some awareness of the SQL syntax for the database being used is required. In this case, MySQL column names are quoted using back-ticks ('):

$fields = ['name', 'balance', 'email', 
           'password', 'status', 'level'];
$data = [
  ['Saleen',0,'saleen@test.com', 'password',0,'BEG'],
  ['Lada',55.55,'lada@test.com',   'password',0,'INT'],
  ['Tonsoi',999.99,'tongsoi@test.com','password',1,'ADV'],
  ['SQL Injection',0.00,'bad','bad',1,
   'BEG\';DELETE FROM customer;--'],
];

try {
  $dsn  = sprintf('mysql:host=%s;dbname=%s', 
    $params['host'], $params['db']);
  $pdo  = new PDO($dsn, 
                  $params['user'], 
                  $params['pwd'], 
                  [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
  $sql  = "INSERT INTO customer ('" 
   . implode("','", $fields) 
   . "') VALUES (?,?,?,?,?,?)";
  $stmt = $pdo->prepare($sql);
  foreach ($data as $row) $stmt->execute($row);
} catch (PDOException $e) {
  echo $e->getMessage();
} catch (Throwable $e) {
  echo $e->getMessage();
}

To test the use of a prepared statement with named parameters, modify the SQL statement to add a WHERE clause that checks for customers with a balance less than a certain amount, and a level equal to either BEG, INT, or ADV (that is, beginning, intermediate, or advanced). Instead of using PDO::query(), use PDO::prepare(). Before fetching results, you must then perform PDOStatement::execute(), supplying the values for balance and level:

$sql  = 'SELECT * FROM customer '
     . 'WHERE balance < :val AND level = :level '
     . 'ORDER BY id LIMIT 20';
$stmt = $pdo->prepare($sql);
$stmt->execute(['val' => 100, 'level' => 'BEG']);

Here is the resulting output:

How it works...

Instead of providing parameters when calling PDOStatement::execute(), you could alternatively bind parameters. This allows you to assign variables to placeholders. At the time of execution, the current value of the variable is used.

In this example, we bind the variables $min, $max, and $level to the prepared statement:

$min   = 0;
$max   = 0;
$level = '';

try {
  $dsn  = sprintf('mysql:host=%s;dbname=%s', $params['host'], $params['db']);
  $opts = [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION];
  $pdo  = new PDO($dsn, $params['user'], $params['pwd'], $opts);
  $sql  = 'SELECT * FROM customer '
      . 'WHERE balance > :min '
      . 'AND balance < :max AND level = :level '
      . 'ORDER BY id LIMIT 20';
  $stmt = $pdo->prepare($sql);
  $stmt->bindParam('min',   $min);
  $stmt->bindParam('max',   $max);
  $stmt->bindParam('level', $level);
  
  $min   =  5000;
  $max   = 10000;
  $level = 'ADV';
  $stmt->execute();
  showResults($stmt, $min, $max, $level);
  
  $min   = 0;
  $max   = 100;
  $level = 'BEG';
  $stmt->execute();
  showResults($stmt, $min, $max, $level);
  
} catch (PDOException $e) {
  echo $e->getMessage();
} catch (Throwable $e) {
  echo $e->getMessage();
}

When the values of these variables change, the next execution will reflect the modified criteria.

Tip

Best practice

Use PDO::query() for one-time database commands. Use PDO::prepare() and PDOStatement::execute() when you need to process the same statement multiple times but using different values.

See also

For information on the syntax and unique behavior associated with different vendor-specific PDO drivers, have a look this article:

For a summary of PDO predefined constants, including fetch modes, cursor orientation, and attributes, see the following article: