Skip to content

Instantly share code, notes, and snippets.

@martialboniou
Last active March 4, 2021 12:32
Show Gist options
  • Save martialboniou/9b08753440893946b874533737c2fb8b to your computer and use it in GitHub Desktop.
Save martialboniou/9b08753440893946b874533737c2fb8b to your computer and use it in GitHub Desktop.
PHP 7 tutorial

PHP7 DIGEST

Here's some tips/snippets for PHP7 (knowledge of PHP5 required).

Please install Github Mermaid extension for Firefox/Chrome to display the diagrams in this document.

Useful functions & vars

  • print_r();

  • implode(', ', $myArray);

  • return array_unique($myArray);;

  • base_convert($number, $fromBase, $toBase) returns a string;

  • unlink($file) returns true if the $file has been deleted;

  • move_uploaded_file($_FILES["photo"]["tmp_name"], "upload/" . $filename); to move an upload file from its temporary state on the server;

  • filter_var($field, FILTER_{SANITIZE_STRING,SANITIZE_EMAIL,VALIDATE_EMAIL}) is a very useful dummy tester. You also can try:

    $field = filter_var(trim($field), FILTER_SANITIZE_STRING);
    $okField = (filter_var($field, FILTER_VALIDATE_REGEXP,
          array("options"=>array("regexp" => "/^[a-zA-Z\s]+$/")))) ? $field : FALSE;
  • $_REQUEST is a superglobal variable containing the values of other global variables;

  • you can set your own custom error handler with set_error_handler(); REMINDER: trigger_error() throws the error;

  • class MyNewException extends Exception {};

  • mysqli_real_escape_string() to provide security against injections.

PHP Spaceship operator

<=> is a combined comparison operator, returns 0 if both operands are equal, 1 if the left is greater:

echo 1 <=> 1; // 0
echo 1.5 <=> 2.5; // -1
echo "y" <=> "x"; // 1

Null Coalescing operator

It's a shorthand where you need to use a ternary operator in conjunction with isset() function:

$name = isset($_GET['name']) ? $_GET['name'] : 'anonymous';
$name = $_GET['name'] ?? 'anonymous';

foreach loop

REMINDER: here's the syntax:

foreach($colors as $value){ {...} }
foreach($superhero as $key => $value) { {...} }

Arguments by reference

function selfMultiply(&$number){
$number *= $number;
return $number;
}

Require vs Include

It's recommended to use the require() statement if you're including the library files of files containing the functions and configuration variables that are essential for running your application, such as database configuration file.

Constructors/Destructors

class MyClass
{
  public function __construct(){
    echo __CLASS__ . " was initiated!<br>";
  }
  public function __destruct(){
    echo __CLASS__ . " was destroyed!<br>";
  }
}
$obj = new MyClass;
unset($obj);

Static properties and methods

class HelloClass
{
  public static $greeting = "Yo!";
  public static function sayHello(){
    echo self::$greeting;
  }
}
echo HelloClass::$greeting; // Strict Warning
$hello->sayHello();         // Yo!

SQL database

Prepared statement

NOTE: PDO supports named placeholders (not MySQLi).

$sql = "INSERT INTO persons (first_name, last_name, email) VALUES (?, ?, ?)";
if ($stmt = mysqli_prepare($link, $sql)){
  /* sss = string, string, string; type definition also accepts:
   * b: binary
   * d: double
   * i: integer */
  mysqli_stmt_bind_param($stmt, "sss", $first_name, $last_name, $email);
  $first_name = "Hermione";
  $last_name = "Granger";
  $email = "hermionegranger@mail.com";
  mysqli_stmt_execute($stmt);
} else {
  echo "ERROR: Could not prepare query: $sql . " . mysqli_error($link);
}
mysqli_stmt_close($stmt);
mysqli_close($link);

CRUD application

  • config.php:
    • database heuristics;
    • open a connection with mysqli_connect() (with a die on false $link with a mysqli_connect_error() stack);
  • index.php: landing page with using Bootstrap:
    • require_once "config.php" (this line must be at the beginning of each CRUD files);
    • a .page-header with a .btn link to create.php;
    • a table from:
      • mysqli_query($link, "SELECT * FROM employees"); (otherwise display an error mysqli_error($link));
      • and mysqli_num_rows($result) not zero;
      • using mysqli_fetch_array($result) to create the cells of a row while there's data;
      • at the end of each row, three icons like "<a href='RUD.php?id=".$row['id']."' data-toggle='tooltip'>" where RUD is read, update, delete respectively;
      • at the end of the table, IMPORTANT: don't forget to free the result set mysqli_free_result($result);
    • and finally, a mysqli_close($link) (this line must be at the end of each CRUD files).
  • create.php:
    • start the processing form data with if ($_SERVER["REQUEST_METHOD"] == "POST"):
      • open connection;
      • for each input:
        • trim data string from $_POST;
        • set error variable if empty();
        • (optional) set error variable if not valid input (check with filter_var() or ctype_digit());
        • set variable from input if valid.
      • if not error variable detected:
        • prepare statement for INSERT INTO;
        • bind variables to the statement and set the parameters;
        • attempt to execute the statement and header("location: index.php");exit(); to redirect to the landing page on success;
        • close the statement.
      • close connection;
    • in the HTML code, create a <form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>" method="POST"> and for each <input> or <textarea>:
      • .form-group;
      • <label>;
      • <input> with .form-control;
      • <span> with the error variable.
    • add a Cancel link to index.php after the <input type="submit">.
  • the RUD files called from each row in the table in index.php and the row index is passed in the URL (GET method):
    • read.php:
      • header("location: error.php");exit(); if the row index in $_GET["id"] is empty;
      • trim the index;
      • open connection (require_once "config.php";);
      • prepare statement SELECT * FROM employees WHERE id = ?;
      • bind variables to the statement and set the parameters;
      • attempt to execute the statement;
      • retrieve field value if mysqli_num_rows($result) == 1 (otherwise error.php redirect);
      • close the statement;
      • close the connection;
      • in the HTML code, create a simple form to display the fields:
        • .form-group;
        • <label> with the name of the column;
        • .form-control-static with the field value.
      • at the end of the form, add a Back .btn to aim index.php.
    • update.php (inspired by index.php and read.php):
      • header("location: error.php");exit(); if the row index in $_{POST,GET}["id"] is empty;
      • POST case:
        • open connection (require_once "config.php";);
        • for each input:
          • set error variable if empty();
          • (optional) set error variable if not valid input (check with filter_var() or ctype_digit());
          • set variable from input if valid.
        • if not error variable detected:
          • prepare statement for UPDATE employees SET name=?, address=?, salary=? WHERE id=?;
          • bind variables to the statement (in a sssi pattern) and set the parameters;
          • attempt to execute the statement and header("location: index.php");exit(); to redirect to the landing page on success;
          • close the statement.
      • close the connection.
      • GET case:
        • trim the index;
        • open connection (require_once "config.php";);
        • prepare statement SELECT * FROM employees WHERE id = ?;
        • bind variables to the statement and set the parameters;
        • attempt to execute the statement;
        • retrieve field value if mysqli_num_rows($result) == 1 (otherwise error.php redirect);
        • close the statement;
        • close connection.
      • in the HTML code, create a <form action="<?php echo htmlspecialchars(basename($_SERVER['REQUEST_URI']));?>" method="POST"> and for each <input> or <textarea>:
        • .form-group;
        • <label>;
        • <input> with .form-control;
        • <span> with the error variable.
      • add a Cancel link to index.php after the <input type="submit">.
    • delete.php (inspired by create.php but GET catch id to pass unto the POST'd form via a hidden input):
      • header("location: error.php");exit(); if the row index in $_{POST|GET}["id"] is empty;
      • open connection (require_once "config.php";);
      • prepare statement DELETE FROM employees WHERE id = ?;
      • bind variables to the statement and set the parameters (BEWARE: i for id);
      • attempt to execute the statement;
      • in the HTML code:
        • create a <form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>" method="POST">:
          • <div class="alert alert-danger fade in">;
            • <input type="hidden" name="id" value"<?php echo trim($_GET["id"]);?>"/>;
            • .btn to index.php as No or <input type="submit"> as Yes.
    • error.php: just a go back to index.php.

REMEMBER: open the connection with require_once(config.php) AND manually close it in each page of the CRUD application.

NOTE: no need to prepare a SQL statement in index.php as the SELECT * as no variables. Since the result set may contain only one row, we don't need a while loop but a mysqli_fetch_array($result, MYSQLI_ASSOC).

Login system

  • config.php;
  • register.php:
    • start the processing form data with if ($_SERVER["REQUEST_METHOD"] == "POST"):
      • open connection (require_once "config.php");
      • for username:
        • trim data string from $_POST;
        • set error variable if empty();
        • in a SELECT statement, mysqli_stmt_store_result($stmt) and check if the row exists:
          • set error variable if 1;
          • set variable from input if valid.
      • for password and confirm_password:
        • trim data strings from $_POST;
        • set error variable if password is empty() or too short;
        • set error variable if confirm_password is:
          • empty() or;
          • different from password when no password error;
      • if not error variable detected:
        • prepare statement for INSERT INTO;
        • bind variables to the statement and set the parameters: BEWARE: use the password_hash($password, PASSWORD_DEFAULT) to create a password hash;
        • attempt to execute the statement and header("location: login.php") to redirect to the login page on success;
        • close the statement.
      • close connection.
    • in the HTML code, create a <form action"<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>" method="POST"> and for each <input>s (ie username, password and confirm_password):
      • .form-group;
      • <label>;
      • <input> with .form-control;
      • <span> with the error variable.
    • add a link to login.php after the <input type="{submit|reset}">.
  • login.php:
    • session_start();
    • header("location: welcome.php");exit; if the user is already logged in ie isset($_SESSION["loggedin"]) && $_SESSION["loggedin"] === true);
    • start the processing form data with if ($_SERVER["REQUEST_METHOD"] == "POST"):
      • open connection (require_once "config.php");
      • for each input:
        • trim data string from $_POST;
        • set error variable if empty();
        • set variable from input if valid.
      • if not error variable detected:
        • prepare statement for SELECT id, username, password FROM users WHERE username = ?;
        • bind variables to the statement and set the parameters ($hashed_password is bound to the password from the table);
        • attempt to execute the statement; here's 3 cases:
          • "No account found" as username error if not mysqli_stmt_fetch($stmt);
          • "No valid password" as password error if password_verify($password, $hashed_password);
          • on success:
            • session_start();
            • $_SESSION["loggedin"] = true;
            • $_SESSION["id"] = $id;
            • `$_SESSION["username"] = $username;
            • header("location: welcome.php").
        • close the statement.
      • close connection.
    • in the HTML code, create a <form action"<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>" method="POST"> and for each <input>s (ie username and password):
      • .form-group;
      • <label>;
      • <input> with .form-control;
      • <span> with the error variable.
    • add a link to register.php after the <input type="submit">.
  • welcome.php (dummy hello page):
    • session_start();
    • header("location: login.php");exit; if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true);
    • in the HTML code:
      • use echo htmlspecialchars($_SESSION["username"]) to print the logged user's name;
      • set a link to reset-password.php;
      • set a link to logout.php.
  • logout.php (to kill the session and redirect to the login.php page):
    • session_start()
    • reset all the variables with $_SESSION = array();
    • session_destroy();
    • header("location: login.php");exit;.
  • reset-password.php:
    • session_start();
    • header("location: login.php");exit; if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true);
    • start the processing form data with if ($_SERVER["REQUEST_METHOD"] == "POST"):
      • open connection (require_once "config.php");
      • for new_password and confirm_password:
        • trim data string from $_POST;
        • set error variable if new_password is empty() or too short;
        • set error variable if confirm_password is:
          • empty() or;
          • different from new_password when no new_password error;
        • set variable from input if valid.
      • if not error variable detected:
        • prepare statement for UPDATE users SET password = ? WHERE id = ?;
        • bind variables to the statement and set the parameters: BEWARE: use the password_hash($new_password, PASSWORD_DEFAULT) to create a password hash and use the session id as id ie $_SESSION["id"];
        • attempt to execute the statement, on success:
          • session_destroy();
          • header("location: login.php");exit() to redirect to the login page.
        • close the statement.
      • close connection.
    • in the HTML code, create a <form action"<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>" method="POST"> and for each <input>s (ie new_password and confirm_password):
      • .form-group;
      • <label>;
      • <input> with .form-control;
      • <span> with the error variable.
    • add a link to welcome.php after the <input type="submit">.

Use session_start(); at the beginning of the files you want to make private (welcome.php, reset_password.php...); don't use it on public pages like register.php except login.php to redirect to the welcome.php page if logged in (so you need to be able to check the $_SESSION["loggedin"] status); NOTE: the session variables are set when the login.php POST processing is a success (account found, valid password).

Object Oriented MySQLi

Let's see what's new in a CRUD application or a Login system if we use the mysqli OO interface:

  • mysqli_connect(): new mysqli as $mysqli;
  • mysqli_connect_error(): $mysqli->connect_error;
  • mysqli_query(): $mysqli->query() as $result;
  • mysqli_num_rows(): $result->num_rows;
  • mysqli_fetch_array($result, MYSQLI_ASSOC): $result->fetch_array(MYSQLI_ASSOC);
  • mysqli_free_result(): $result->free();
  • mysqli_error(): $mysqli->error;
  • mysqli_close(): $mysqli->close();
  • mysqli_prepare(): $mysqli->prepare() as $stmt;
  • mysqli_stmt_bind_param(): $stmt->bind_param();
  • mysqli_stmt_execute(): $stmt->execute();
  • mysqli_stmt_store_result(): $stmt->store_result();
  • mysqli_stmt_get_result(): $stmt->get_result() as $result;
  • mysqli_stmt_close(): $stmt->close().

PDO

Let's see what's new in our previous examples when you use PDO. First, here's the new config file:

<?php
define('DB_SERVER', 'localhost');
define('DB_USERNAME', 'root');
define('DB_PASSWORD', 'root');
define('DB_NAME', 'crud_demo'); // or whatever

/* Attempt to connect to MySQL database */
try{
    $pdo = new PDO("mysql:host=" . DB_SERVER . ";dbname=" . DB_NAME, DB_USERNAME, DB_PASSWORD);
    // Set the PDO error mode to exception
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e){
    die("ERROR: Could not connect. " . $e->getMessage());
}
?>

Here's what changes in the core of the program with our $pdo instance (compared to the OO format):

  • $mysqli->query() as $result: $pdo->query() as $result;
  • $result->num_rows: $result->rowCount();
  • $result->fetch_array(MYSQLI_ASSOC): $result->fetch(PDO::FETCH_ASSOC);
  • $result->free(): unset($result);
  • $mysqli->error: use a catch(PDOException $e) after a try block;
  • $mysqli->close(): unset($pdo);
  • $mysqli->prepare() as $stmt: $pdo->prepare() as $stmt;
  • $stmt->bind_param(): $stmt->bindParam(":name", $param_name); BEWARE: : format as a prepared statement use :name instead of a ?;
  • $stmt->execute(): $stmt->execute();
  • $stmt->store_result(): OBSOLETE: there's absolutely no point in using neither store_result() nor bind_result() with PDO;
  • $stmt->get_result() as $result : OBSOLETE: you get the $stmt->rowCount() directly after $stmt_execute();
  • $stmt->close(): unset($stmt).

Digest by Martial BONIOU, 2021

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment