Using PHP and MySQL to build web pages

Before we begin, let’s allow the user george to connect to MySQL/MariaDB from any host, including the server that hosts php and the web server.
We will use % to allow connection from all hosts:

MariaDB [(none)]> GRANT ALL PRIVILEGES ON `publications`.* TO "george"@"%";
Query OK, 0 rows affected (0.010 sec)

We will be creating the login.php file which we will be using in other phpfiles.
This code will help us connect to the MySQL database with the user and password, and the publications database will be selected.
This code is visible and executed only by the server, it is not visible by the web browser.

<?php // login.php
        $host = 'mariadb'; // Change as necessary
        $data = 'publications'; // Change as necessary
        $user = 'george'; // Change as necessary
        $pass = 'password'; // Change as necessary
        $chrs = 'utf8mb4';
        $attr = "mysql:host=$host;dbname=$data;charset=$chrs";
        $opts =
        [
                PDO::ATTR_ERRMODE               => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE    => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES      => false,
        ];
?>

To connect to MySQL, we create a new PDO connection.
An error message will be displayed if the connection is not successful.

<?php // query.php
        require_once 'login.php';

        try
        {
                $pdo = new PDO($attr, $user, $pass, $opts);
        }
        catch (PDOException $e)
        {
                throw new PDOException($e->getMessage(), (int)$e->getCode());
        }
?>

In the next step we will be doing an SQL query and placing its output in the $result object.

<?php
        $query = "SELECT * FROM classics";
        $result = $pdo->query($query);
?>

The final step will be displaying the results in a web page. Here is the final code:

<?php // query.php
        require_once 'login.php';

        try
        {
                $pdo = new PDO($attr, $user, $pass, $opts);
        }
        catch (PDOException $e)
        {
                throw new PDOException($e->getMessage(), (int)$e->getCode());
        }

        $query = "SELECT * FROM classics";
        $result = $pdo->query($query);

        while ($row = $result->fetch())
        {
                echo 'Author: ' . htmlspecialchars($row['author']) . "<br>";
                echo 'Title: ' . htmlspecialchars($row['title']) . "<br>";
                echo 'Category: ' . htmlspecialchars($row['category']) . "<br>";
                echo 'Year: ' . htmlspecialchars($row['year']) . "<br>";
                echo 'ISBN: ' . htmlspecialchars($row['isbn']) . "<br><br>";
        }
?>

The resulting html source of web page (<br> tags are for adding new lines):

Author: Charles Dickens<br>
Title: The Old Curiosity Shop<br>
Category: Classic Fiction<br>
Year: 1841<br>
ISBN: 9780099533474<br>
<br>Author: William Shakespeare<br>
Title: Romeo and Juliet<br>
Category: Play<br>Year: 1594<br>
ISBN: 9780192814968<br>

This is what the web browser will render:

Author: Charles Dickens  
Title: The Old Curiosity Shop  
Category: Classic Fiction  
Year: 1841  
ISBN: 9780099533474  
  
Author: William Shakespeare  
Title: Romeo and Juliet  
Category: Play  
Year: 1594  
ISBN: 9780192814968

Let’s look at a more complex example, where we can either add a record to the classics writers database, or delete existing ones.
We first connect via PDO to the database.
We define a function to validate ISBN numbers - 10 or 13 digits - called validate_isbn.
For this, we use the PHP function preg_match to do a regular expression search for ISBN numbers, explained below:

Regular expression - ^\d{10}$|^\d{13}$
1st Alternative ^\d{10}$
	^ asserts *position* at start of a line
	\d matches a digit (equivalent to [0-9])
	{10} matches the previous token exactly 10 times
	$ asserts position at the end of a line

2nd Alternative ^\d{13}$
	^ asserts *position* at start of a line
	\d matches a digit (equivalent to [0-9])
	{13} matches the previous token exactly 13 times
	$ asserts position at the end of a line

.

	<?php // sqltest.php
		require_once 'login.php';
	
		try {
			// Establish a database connection using PDO
			$pdo = new PDO($attr, $user, $pass, $opts);
		} catch (PDOException $e) {
			// Generic error handling
			echo "Database connection error.";
			exit;
		}
	
		// Function to validate ISBN
		function validate_isbn($isbn) {
			return preg_match('/^\d{10}$|^\d{13}$/', $isbn); // Match 10 or 13 digits
		}
	
		// Function to check if any field is empty
		function are_fields_empty(...$fields) {
			foreach ($fields as $field) {
				if (empty(trim($field))) {
					return true; // Return true if any field is empty
				}
			}
			return false; // Return false if all fields are filled
		}
	
		// Function to validate year
		function validate_year($year) {
			return preg_match('/^\d{4}$/', $year) && $year > 0; // Checks if year is a 4-digit positive integer
		}
	
		// Initialize an array to hold error messages
		$errors = [];
	
		// Handle deletion of records
		if (isset($_POST['delete']) && isset($_POST['isbn'])) {
			$isbn = get_post($pdo, 'isbn');
			// Use prepared statements to execute the deletion
			$stmt = $pdo->prepare("DELETE FROM classics WHERE isbn = :isbn");
			$stmt->execute(['isbn' => $isbn]);
		}
	
		// Handle insertion of records
		if ($_SERVER["REQUEST_METHOD"] == "POST") {
			$author = get_post($pdo, 'author');
			$title = get_post($pdo, 'title');
			$category = get_post($pdo, 'category');
			$year = get_post($pdo, 'year');
			$isbn = get_post($pdo, 'isbn');
	
			// Validate fields
			if (are_fields_empty($author, $title, $category, $year, $isbn)) {
				$errors[] = "All fields are required!";
			}
			if (!validate_isbn($isbn)) {
				$errors[] = "ISBN must be either 10 or 13 digits!";
			}
			if (!validate_year($year)) {
				$errors[] = "Year must be a valid 4-digit integer!";
			}
	
			// Check for duplicate ISBN
			if (empty($errors)) {
				$stmt = $pdo->prepare("SELECT COUNT(*) FROM classics WHERE isbn = :isbn");
				$stmt->execute(['isbn' => $isbn]);
				$exists = $stmt->fetchColumn();
				
				if ($exists) {
					$errors[] = "This ISBN already exists in the database!";
				}
			}
	
			// If no errors, insert the record
			if (empty($errors)) {
				// Use prepared statements for insertion
				$stmt = $pdo->prepare("INSERT INTO classics (author, title, category, year, isbn) VALUES (:author, :title, :category, :year, :isbn)");
				$stmt->execute(['author' => $author, 'title' => $title, 'category' => $category, 'year' => $year, 'isbn' => $isbn]);
			}
		}
	
		// Display error messages if any
		if (!empty($errors)) {
			echo "<section style='color:red;'>";
			foreach ($errors as $error) {
				echo "<p>$error</p>";
			}
			echo "</section>";
		}
	
		// Pre-fill form values to retain user input
		$form_author = $_POST['author'] ?? '';
		$form_title = $_POST['title'] ?? '';
		$form_category = $_POST['category'] ?? '';
		$form_year = $_POST['year'] ?? '';
		$form_isbn = $_POST['isbn'] ?? '';
	
		echo <<<_END
		<form action="sqltest.php" method="post"><pre>
			Author <input type="text" name="author" value="$form_author">
			 Title <input type="text" name="title" value="$form_title">
		  Category <input type="text" name="category" value="$form_category">
			  Year <input type="text" name="year" value="$form_year">
			  ISBN <input type="text" name="isbn" value="$form_isbn">
				   <input type="submit" value="ADD RECORD">
		</pre></form>
	_END;
	
		// Fetch and display records from the database
		$query = "SELECT * FROM classics";
		$result = $pdo->query($query);
	
		while ($row = $result->fetch()) {
			$r0 = htmlspecialchars($row['author']);
			$r1 = htmlspecialchars($row['title']);
			$r2 = htmlspecialchars($row['category']);
			$r3 = htmlspecialchars($row['year']);
			$r4 = htmlspecialchars($row['isbn']);
	
			echo <<<_END
		<pre>
			Author: $r0
			 Title: $r1
		  Category: $r2
			  Year: $r3
			  ISBN: $r4
		</pre>
		<form action='sqltest.php' method='post'>
			<input type='hidden' name='delete' value='yes'>
			<input type='hidden' name='isbn' value='$r4'>
			<input type='submit' value='DELETE RECORD'></form>
	_END;
		}
	
		function get_post($pdo, $var) {
			return htmlspecialchars(trim($_POST[$var] ?? ''));
		}
	?>

.