PHP ООП MySQL — Пошаговое руководство

Сегодня познакомимся с объектно-ориентированным программированием (ООП) в PHP. Используя принципы ООП, MySQL создадим простое web-приложение.

  • 1. Введение
  • 2. Файловая структура
  • 3. Структура таблицы базы данных
  • 3.1 Создание базы данных и таблицы товаров
  • 3.2 Вставка демо-данных для таблицы «Товары»
  • 3.3 Создание таблицы с категориями
  • 3.4 Вставка демо-данных для таблицы «Категории»
  • 3.5 Результат
  • 4. Создание файлов шаблонов
  • 4.1 Создание файла шаблона «header»
  • 4.2 Создание файла шаблона «footer»
  • 4.3 Создание кастомного CSS файла
  • 4.4 Результат
  • 5. Создание записей в PHP методом ООП
  • 5.1 Создание файла create_product.php
  • 5.2 Создание кнопки «Просмотр всех товаров»
  • 5.3 Подключение к базе данных
  • 5.4 Создание класса конфигурации базы данных
  • 5.5 Создание формы в create_product.php
  • 5.6 Вывод категорий в выпадающем списке
  • 5.7 Создание класса для работы с категориями
  • 5.8 Создание метода readName()
  • 5.9 Выполнение php-кода при отправке формы
  • 5.10 Создание класса для работы с товарами
  • 5.11 Результат
  • 6. Вывод товаров + пагинация с помощью PHP ООП
  • 6.1 Создание файла index.php
  • 6.2 Добавление кнопки «Создание товара»
  • 6.3 Настройка переменных для создания пагинации
  • 6.4 Получение записей из базы данных
  • 6.5 Создание метода readAll() в файле product.php
  • 6.6 Вывод товаров из базы данных
  • 6.7 Добавление кнопок просмотра, редактирования и удаления
  • 6.8 Создание файла paging.php для пагинации
  • 6.9 Создание метода countAll() в objects/product.php
  • 6.10 Включение paging.php в index.php
  • 6.11 Результат
  • 7. Обновление записи в PHP методом ООП
  • 7.1 Создание файла update_product.php
  • 7.2 Создание кнопки «Просмотр всех товаров»
  • 7.3 Получение информации об одном товаре на основе получаемого ID
  • 7.4 Добавление метода readOne() в класс объекта Product
  • 7.5 Добавление значений в форму обновления товара
  • 7.6 Вывод категорий в выпадающем списке
  • 7.7 Код при отправке формы обновления товара
  • 7.8 Добавление метода update() в объекте класса Product
  • 7.9 Результат
  • 8. Чтение одной записи на PHP в ООП
  • 8.1 Создание страницы товара
  • 8.2 Чтение одной записи на основе полученного ID
  • 8.3 Отображение товара в HTML-таблице
  • 8.4 Результат
  • 9. Удаление записи на PHP в ООП
  • 9.1 Добавление JavaScript для удаления товара
  • 9.2 Создание файла delete_product.php
  • 9.3 Метод для удаления товара в классе Product
  • 9.4 Результат
  • 10. Поиск записей в PHP методом ООП
  • 10.1 Изменение index.php
  • 10.2 Создание read_template.php
  • 10.3 Создание core.php в папке config
  • 10.4 Изменение кода в paging.php
  • 10.5 Создание файла search.php
  • 10.6 Добавление методов search() и countAll_BySearch()
  • 10.7 Результат
  • 11. Загрузка файлов в PHP методом ООП
  • 11.1 Изменение HTML-формы
  • 11.2 Установка значения полю «Изображение»
  • 11.3 Изменение метода create()
  • 11.4 Вызов метода uploadPhoto()
  • 11.5 Создание метода uploadPhoto()
  • 11.6 Валидация отправляемого файла
  • 11.7 Сообщения об ошибках
  • 11.8 Показ загруженного изображения
  • 11.9 Результат

1. Введение

Данное учебное пособие о создании простого приложения с базой данных в стиле объектно-ориентированного программирования на PHP.

Мы будем использовать Bootstrap, чтобы не тратить время на вёрстку, а сосредоточимся на главном, а именно на ООП.

Мы хотим изучить правильную реализацию ООП в PHP. Есть PHP-фреймворки, такие как CakePHP, Laravel, Yii — они на один шаг выше. Сейчас мы изучим объектно-ориентированное программирование в PHP с MySQL-запросами. Осваивать PHP-фреймворки должно быть гораздо проще после изучения данного пособия.

2. Файловая структура

Файловая структура данного приложения.

  • php-oop-mysql/
    • config/
      • core.php
      • database.php
    • objects/
      • product.php
      • category.php
    • libs/
      • css/
        • custom.css
    • index.php
    • layout_header.php
    • layout_footer.php
    • create_product.php
    • read_product.php
    • update_product.php
    • delete_product.php
    • read_template.php
    • paging.php
    • search.php

3. Структура таблицы базы данных

Создадим базу данных и две таблицы: для категорий и для самих товаров.

3.1 Создание базы данных и таблицы товаров

Откройте PhpMyAdmin и создайте новую базу данных. Назовём её php_oop.

В этой базе данных создадим таблицу products. Перейдите во вкладку «SQL» и выполните следующий SQL-запрос.

-- Создание структуры таблицы `products`
CREATE TABLE IF NOT EXISTS `products` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `name` varchar(32) NOT NULL,
    `description` text NOT NULL,
    `price` int(11) NOT NULL,
    `image` varchar(255) NOT NULL,
    `category_id` int(11) NOT NULL,
    `created` datetime NOT NULL,
    `modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=38;

3.2 Вставка демо-данных для таблицы «Товары»

Выполните SQL-запрос для добавления демо-данных в таблицу «Товары».

-- Вставка данных для таблицы `products`
INSERT INTO `products` (`id`, `name`, `description`, `price`, `image`, `category_id`, `created`, `modified`) VALUES
(1, "LG P880 4X HD", "Мой первый классный телефон!", 336, "", 3, "2020-03-05 01:12:26", "2021-01-11 17:12:26"),
(2, "Google Nexus 4", "Самый крутой телефон 2013 года!", 299, "", 2, "2020-02-09 01:12:26", "2014-03-31 17:12:26"),
(3, "Samsung Galaxy S4", "Самые крутые умные часы!", 600, "", 3, "2014-06-01 01:12:26", "2014-06-31 17:12:26"),
(6, "Bench Shirt", "Лучшая рубашка!", 29, "", 1, "2018-06-02 01:12:26", "2020-03-21 02:12:21"),
(7, "Lenovo Laptop", "Мой бизнес партнер.", 399, "", 2, "2020-01-07 01:13:45", "2021-01-21 02:13:39"),
(8, "Samsung Galaxy Tab 10.1", "Хороший планшет.", 259, "", 2, "2018-06-01 01:14:13", "2019-05-31 02:14:08"),
(9, "Spalding Watch", "Мои спортивные часы.", 199, "", 1, "2019-05-03 01:18:36", "2020-05-31 02:18:31"),
(10, "Sony Smart Watch", "Как насчет нет?", 300, "", 2, "2020-06-06 17:10:01", "2021-01-05 18:09:51"),
(11, "Huawei Y300", "Для тестирования.", 100, "", 2, "2014-06-06 17:11:04", "2015-06-05 18:10:54"),
(12, "Abercrombie Lake Arnold Shirt", "Идеально как подарок!", 60, "", 1, "2017-06-06 17:12:21", "2018-06-05 18:12:11"),
(13, "Abercrombie Allen Brook Shirt", "Классная красная рубашка!", 70, "", 1, "2019-06-06 17:12:59", "2020-06-05 18:12:49"),
(25, "Abercrombie Allen Anew Shirt", "Классная новая рубашка!", 999, "", 1, "2020-11-22 18:42:13", "2021-01-21 19:42:13"),
(26, "Another product", "Потрясающий товар!", 555, "", 2, "2019-11-22 19:07:34", "2021-01-19 20:07:34"),
(27, "Bag", "Отличная сумка для тебя!", 999, "", 1, "2019-12-04 21:11:36", "2020-02-13 22:11:36"),
(30, "Wal-mart Shirt", "", 555, "", 2, "2018-12-13 00:52:29", "2019-12-12 01:52:29"),
(32, "Washing Machine Model PTRR", "Какой-то новый продукт.", 999, "", 1, "2020-01-08 22:44:15", "2021-01-09 23:44:15");

3.3 Создание таблицы для категорий товаров

Таблицу с категориями мы будем использовать для хранения категорий товаров.

Запустите следующую инструкцию SQL, используя ваш PhpMyAdmin.

-- Структура для таблицы `categories`
CREATE TABLE IF NOT EXISTS `categories` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `name` varchar(256) NOT NULL,
    `created` datetime NOT NULL,
    `modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;

3.4 Вставка демо-данных для таблицы «Категории»

В нашем проекте в качестве категорий будут:

  1. «Мода»,
  2. «Электроника»,
  3. «Автомобили».

Выполните следующий SQL запрос, используя ваш PhpMyAdmin.

-- Данные для таблицы `categories`
INSERT INTO `categories` (`id`, `name`, `created`, `modified`) VALUES
(1, "Мода", "2014-06-01 00:35:07", "2014-05-30 17:34:33"),
(2, "Электроника", "2014-06-01 00:35:07", "2014-05-30 17:34:33"),
(3, "Автомобили", "2014-06-01 00:35:07", "2014-05-30 17:34:54");

3.5 Результат

В этом разделе мы смогли настроить нашу базу данных с помощью PhpMyAdmin.
У вас должно получиться как на изображении ниже.

Структура базы данных php oop mysql

Результатом данного раздела у вас должны быть:

  1. база данных php_oop;
  2. две таблицы categories и products, наполненные демо-контентом.

4. Создание файлов шаблонов

Чтобы уменьшить беспорядок в коде, мы создадим файлы макетов с нужным кодом и ресурсами.

Создайте папку для нашего проекта, пусть она будет называться php-oop-mysql.

4.1 Создание файла шаблона «header»

Этот файл layout_header.php будет включен в начало тех файлов PHP, которые ему понадобятся. Таким образом, нам не придется каждый раз писать один и тот же код.

Как уже говорилось ранее, мы будем использовать фреймворк Bootstrap, чтобы наш проект выглядел хорошо.

Подключим Bootstrap в теге head.

  1. Перейдите в папку проекта php-oop-mysql.
  2. Создайте в ней файл layout_header.php.
  3. Вставьте в него следующий код:
<!DOCTYPE html>
<html lang="ru">

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title><?= $page_title ?></title>

    <!-- bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />

    <!-- кастомный CSS -->
    <link rel="stylesheet" href="libs/css/custom.css" />
</head>

<body>

    <!-- container -->
    <div class="container">

        <!-- page header -->
        <div class="page-header">
            <h1><?= $page_title ?></h1>
        </div>

Шаблон layout_footer.php будем подключать в конец каждого php-файла, который в нём нуждается. Таким образом, нам не придётся каждый раз писать один и тот же код.

Подключим следующие компоненты/библиотеки:

  • jQuery — понадобится для Bootstrap JavaScript;
  • Bootstrap JavaScript — чтобы заставить работать крутые компоненты пользовательского интерфейса;
  • BootboxJS — для отображения красивых предупреждений или диалоговых окон подтверждения.

Можно было бы обойтись без сторонних библиотек, используя HTML, CSS и нативный JavaScript, но т.к. задача стоит разобраться в ООП, мы не будем много тратить времени на вёрстку.

Итак, создадим файл «подвала» и подключим его.

  1. Перейдите в папку проекта php-oop-mysql.
  2. Создайте файл layout_footer.php.
  3. Добавьте в него следующий код:
</div>
<!-- /container -->

<!-- jQuery (необходим для Bootstrap JavaScript) -->
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>

<!-- bootstrap JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>

<!-- bootbox JavaScript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/4.4.0/bootbox.min.js"></script>
</body>

</html>

4.3 Создание кастомного CSS файла

Этот файл будем использовать для добавления стилей веб-страницы и для переопределения дефолтных стилей Bootstrap.

  1. В корне проекта создайте папку libs.
  2. В ней папку css.
  3. Внутри папки css создайте файл custom.css.
  4. Добавьте в него следующие стили:
.left-margin {
    margin: 0 .5em 0 0;
}

.right-button-margin {
    margin: 0 0 1em 0;
    overflow: hidden;
}

/* modal */

.modal-body {
    padding: 20px 20px 0px 20px !important;
    text-align: center !important;
}

.modal-footer {
    text-align: center !important;
}

4.4 Результат

Файлы шаблонов, которые мы создали в этом разделе, предназначены для использования внутри другого файла PHP. Если мы попытаемся запустить файлы макета в одиночку, мы не получим желаемого результата.

Файл custom.css будет выглядеть так.

Кастомный css - руководство PHP ООП MySql

Шаблон layout_footer.php пуст. Давайте продолжим в следующем разделе, чтобы увидеть более значимый результат.

5. Создание записей в PHP методом ООП

5.1 Создание файла create_product.php

Вернитесь в корневую папку php-oop-mysql, создайте в ней файл create_product.php со следующим кодом:

<?php

// установка заголовка страницы
$page_title = "Создание товара";

require_once "layout_header.php";
?>

<!-- здесь будет контент -->

<?php // подвал
require_once "layout_footer.php";
?>

5.2 Создание кнопки «Просмотр всех товаров»

Следующий код отобразит кнопку. Замените комментарий <!— здесь будет контент —> предыдущего раздела следующим кодом:

<div class="right-button-margin">
    <a href="index.php" class="btn btn-default pull-right">Просмотр всех товаров</a>
</div>

<!-- здесь будет html-форма "create product" -->

5.3 Подключение к базе данных

Мы можем использовать его для получения категорий или сохранения новой записи о товаре позже.
Поместите следующий код перед комментарием // установка заголовка страницы в файле create_product.php.

// подключим файлы, необходимые для подключения к базе данных и файлы с объектами
include_once "config/database.php";
include_once "objects/product.php";
include_once "objects/category.php";

// получаем соединение с базой данных
$database = new Database();
$db = $database->getConnection();

// создадим экземпляры классов Product и Category
$product = new Product($db);
$category = new Category($db);

5.4 Создание класса конфигурации базы данных

Без этого класса невозможно установить соединение с базой данных. Этот файл класса будет включен в большинство файлов PHP нашего руководства по PHP ОПП MySQL.

В корне проекта создайте папку config и в ней файл database.php со следующим содержимым:

<?php

class Database
{
    // укажите свои собственные учетные данные для базы данных
    private $host = "localhost";
    private $db_name = "php_oop";
    private $username = "root";
    private $password = "root";
    public $conn;

    // получение соединения с базой данных
    public function getConnection()
    {
        $this->conn = null;

        try {
            $this->conn = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->db_name, $this->username, $this->password);
        } catch (PDOException $exception) {
            echo "Ошибка соединения: " . $exception->getMessage();
        }

        return $this->conn;
    }
}

5.5 Создание формы в create_product.php

Следующий код отобразит HTML-форму. Откройте файл create_product.php.

Замените <!— здесь будет html-форма «create product» —> следующим кодом:

<!-- Здесь будет PHP код -->

<!-- HTML-формы для создания товара -->
<form action="<?= htmlspecialchars($_SERVER["PHP_SELF"]) ?>" method="post">
  
    <table class="table table-hover table-responsive table-bordered">
  
        <tr>
            <td>Название</td>
            <td><input type="text" name="name" class="form-control" /></td>
        </tr>
  
        <tr>
            <td>Цена</td>
            <td><input type="text" name="price" class="form-control" /></td>
        </tr>
  
        <tr>
            <td>Описание</td>
            <td><textarea name="description" class="form-control"></textarea></td>
        </tr>
  
        <tr>
            <td>Категория</td>
            <td>
                <!-- здесь будут категории из базы данных -->
            </td>
        </tr>
  
        <tr>
            <td></td>
            <td>
                <button type="submit" class="btn btn-primary">Создать</button>
            </td>
        </tr>
  
    </table>
</form>

С помощью глобальной переменной $_SERVER[«PHP_SELF»] мы получаем имя скрипта, который сейчас выполняется.

5.6 Вывод категорий в выпадающем списке

Следующий код будет извлекать категории, и помещать их в раскрывающийся список «Выбрать».

Замените комментарий <!— здесь будут категории из базы данных —> из предыдущего раздела следующим кодом:

<?php
// читаем категории товаров из базы данных
$stmt = $category->read();

// помещаем их в выпадающий список
echo "<select class='form-control' name='category_id'>";
echo "<option>Выбрать категорию...</option>";

while ($row_category = $stmt->fetch(PDO::FETCH_ASSOC)) {
    extract($row_category);
    echo "<option value='{$id}'>{$name}</option>";
}

echo "</select>";
?>

5.7 Создание класса для работы с категориями

Предыдущий раздел не будет работать без класса Category и метода read(). Создайте папку для объектов objects. В ней создайте файл category.php и добавьте следующий код:

<?php

class Category
{
    // подключение к базе данных и имя таблицы
    private $conn;
    private $table_name = "categories";

    // свойства объекта
    public $id;
    public $name;

    public function __construct($db)
    {
        $this->conn = $db;
    }

    // данный метод используется в раскрывающемся списке
    function read()
    {
        // запрос MySQL: выбираем столбцы в таблице «categories»
        $query = "SELECT
                    id, name
                FROM
                    " . $this->table_name . "
                ORDER BY
                    name";

        $stmt = $this->conn->prepare($query);
        $stmt->execute();

        return $stmt;
    }
}

5.8 Создание метода readName()

Мы будем получать название категории, а не просто идентификатор. Добавьте следующий код в наш category.php после метода read(), далее вы увидите, что этот метод используется в последующих нескольких разделах.

// получение названия категории по её ID
function readName()
{
    // запрос MySQL
    $query = "SELECT name FROM " . $this->table_name . " WHERE id = ? limit 0,1";

    $stmt = $this->conn->prepare($query);
    $stmt->bindParam(1, $this->id);
    $stmt->execute();

    $row = $stmt->fetch(PDO::FETCH_ASSOC);

    $this->name = $row["name"];
}

5.9 Выполнение php-кода при отправке формы

Пользователь будет вводить значения в HTML-форму, и при нажатии кнопки отправки, значения будут отправлены через запрос POST, а приведенный ниже код сохранит их в базе данных.

Откройте файл create_product.php. Замените комментарий <!— Здесь будет PHP код —> следующим кодом.

<?php
// если форма была отправлена
if ($_POST) 
{
    // установим значения свойствам товара
    $product->name = $_POST["name"];
    $product->price = $_POST["price"];
    $product->description = $_POST["description"];
    $product->category_id = $_POST["category_id"];

    // создание товара
    if ($product->create()) {
        echo '<div class="alert alert-success">Товар был успешно создан.</div>';
    }

    // если не удается создать товар, сообщим об этом пользователю
    else {
        echo '<div class="alert alert-danger">Невозможно создать товар.</div>';
    }
}
?>

5.10 Создание класса для работы с товарами

Предыдущий раздел не будет работать без класса Product. Откройте папку с объектами (objects). Создайте файл product.php. Откройте этот файл и добавьте следующий код.

<?php

class Product
{
    // подключение к базе данных и имя таблицы
    private $conn;
    private $table_name = "products";

    // свойства объекта
    public $id;
    public $name;
    public $price;
    public $description;
    public $category_id;
    public $timestamp;

    public function __construct($db)
    {
        $this->conn = $db;
    }

    // метод создания товара
    function create()
    {
        // запрос MySQL для вставки записей в таблицу БД «products»
        $query = "INSERT INTO
                    " . $this->table_name . "
                SET
                    name=:name, price=:price, description=:description, category_id=:category_id, created=:created";

        $stmt = $this->conn->prepare($query);

        // опубликованные значения
        $this->name = htmlspecialchars(strip_tags($this->name));
        $this->price = htmlspecialchars(strip_tags($this->price));
        $this->description = htmlspecialchars(strip_tags($this->description));
        $this->category_id = htmlspecialchars(strip_tags($this->category_id));

        // получаем время создания записи
        $this->timestamp = date("Y-m-d H:i:s");

        // привязываем значения
        $stmt->bindParam(":name", $this->name);
        $stmt->bindParam(":price", $this->price);
        $stmt->bindParam(":description", $this->description);
        $stmt->bindParam(":category_id", $this->category_id);
        $stmt->bindParam(":created", $this->timestamp);

        if ($stmt->execute()) {
            return true;
        } else {
            return false;
        }
    }
}

5.11 Результат

Форма для создания товара.

Изображение формы для создания товара в PHP ООП

Когда вы заполните форму и нажмёте кнопку «Создать».

Успешное добавление товара в PHP ООП

6. Вывод товаров + пагинация с помощью PHP ООП

В этой части нашего руководства PHP ООП MySQL мы выведем записи из базы данных.

6.1 Создание файла index.php

Создайте новый файл и назовите его index.php. Этот файл будет отображать главную страницу нашего веб-приложения. Поместите в него следующий код.

<?php

// установка заголовка страницы
$page_title = "Вывод товаров";

require_once "layout_header.php";
?>

<!-- здесь будет контент -->

<?php // подвал
require_once "layout_footer.php";

6.2 Добавление кнопки «Создание товара»

Следующий код отобразит кнопку. Когда эта кнопка будет нажата, она покажет нам страницу, на которой мы можем добавить новый товар. Замените комментарий <!— здесь будет контент —> в предыдущем разделе следующим кодом.

<div class="right-button-margin">
    <a href="create_product.php" class="btn btn-default pull-right">Добавить товар</a>
</div>

6.3 Настройка переменных для создания пагинации

Пагинация очень важна, если у вас есть тысячи товаров в базе данных. Поместите следующий код перед установкой заголовка страницы файла index.php.

// страница, указанная в параметре URL, страница по умолчанию - 1
$page = isset($_GET["page"]) ? $_GET["page"] : 1;

// устанавливаем ограничение количества записей на странице
$records_per_page = 5;

// подсчитываем лимит запроса
$from_record_num = ($records_per_page * $page) - $records_per_page;

// здесь будет получение товаров из БД

6.4 Получение записей из базы данных

Теперь мы извлечем товары из базы данных. Замените комментарий // здесь будет получение товаров из БД в файле index.php следующим кодом.

// включаем соединение с БД и файлы с объектами
include_once "config/database.php";
include_once "objects/product.php";
include_once "objects/category.php";

// создаём экземпляры классов БД и объектов
$database = new Database();
$db = $database->getConnection();

$product = new Product($db);
$category = new Category($db);

// запрос товаров
$stmt = $product->readAll($from_record_num, $records_per_page);
$num = $stmt->rowCount();

6.5 Создание метода readAll() в файле product.php

Без этого метода получение записей из предыдущего раздела не будет работать. Поместите следующий код в наш файл product.php, который находится в папке objects.

// метод для получения товаров
function readAll($from_record_num, $records_per_page)
{
    // запрос MySQL
    $query = "SELECT
                id, name, description, price, category_id
            FROM
                " . $this->table_name . "
            ORDER BY
                name ASC
            LIMIT
                {$from_record_num}, {$records_per_page}";

    $stmt = $this->conn->prepare($query);
    $stmt->execute();

    return $stmt;
}

6.6 Вывод товаров из базы данных

На этот раз мы покажем пользователю список товаров. HTML Таблица будет содержать наши данные. Поместите следующий код после кода раздела 6.2.

<?php
// отображаем товары, если они есть
if ($num > 0) {

    echo "<table class='table table-hover table-responsive table-bordered'>";
        echo "<tr>";
            echo "<th>Товар</th>";
            echo "<th>Цена</th>";
            echo "<th>Описание</th>";
            echo "<th>Категория</th>";
            echo "<th>Действия</th>";
        echo "</tr>";

        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {

            extract($row);

            echo "<tr>";
                echo "<td>{$name}</td>";
                echo "<td>{$price}</td>";
                echo "<td>{$description}</td>";
                echo "<td>";
                    $category->id = $category_id;
                    $category->readName();
                    echo $category->name;
                echo "</td>";
  
                echo "<td>";
                    // здесь будут кнопки для просмотра, редактирования и удаления
                echo "</td>";

            echo "</tr>";

        }

    echo "</table>";

    // здесь будет пагинация
}

// сообщим пользователю, что товаров нет
else {
    echo "<div class='alert alert-info'>Товары не найдены.</div>";
}
?>

6.7 Добавление кнопок просмотра, редактирования и удаления

Следующий код отобразит три кнопки (ссылки на соответствующие страницы): «Просмотр», «Редактировать» и «Удалить».

Внутри цикла while предыдущего раздела есть комментарий // здесь будут кнопки для просмотра, редактирования и удаления, замените его следующим кодом.

// ссылки/кнопки для просмотра, редактирования и удаления товара
echo "<a href='read_product.php?id={$id}' class='btn btn-primary left-margin'>
    <span class='glyphicon glyphicon-list'></span> Просмотр
</a>

<a href='update_product.php?id={$id}' class='btn btn-info left-margin'>
    <span class='glyphicon glyphicon-edit'></span> Редактировать
</a>

<a delete-id='{$id}' class='btn btn-danger delete-object'>
    <span class='glyphicon glyphicon-remove'></span> Удалить
</a>";

6.8 Создание файла paging.php для пагинации

Следующий код покажет наши кнопки пагинации. Создайте новый файл в корне проекта и назовите его paging.php. Откройте этот файл и введите следующий код.

<?php

echo "<ul class='pagination'>";

// ссылка для первой страницы
if ($page > 1) {
    echo "<li><a href='{$page_url}' title='Переход к первой странице'>";
    echo "Первая";
    echo "</a></li>";
}

// подсчёт общего количества страниц
$total_pages = ceil($total_rows / $records_per_page);

// диапазон ссылок для отображения
$range = 2;

// отображать ссылки на «диапазон страниц» вокруг «текущей страницы»
$initial_num = $page - $range;
$condition_limit_num = ($page + $range) + 1;

for ($x = $initial_num; $x < $condition_limit_num; $x++) {

    // убедимся, что "$x больше 0" и "меньше или равно $total_pages"
    if (($x > 0) && ($x <= $total_pages)) {

        // текущая страница
        if ($x == $page) {
            echo "<li class='active'><a href='#'>$x <span class='sr-only'>(current)</span></a></li>";
        }

        // НЕ текущая страница
        else {
            echo "<li><a href='{$page_url}page=$x'>$x</a></li>";
        }
    }
}

// ссылка на последнюю страницу
if ($page < $total_pages) {
    echo "<li><a href='{$page_url}page={$total_pages}' title='Переход к последней странице'>";
    echo "Последняя";
    echo "</a></li>";
}

echo "</ul>";

6.9 Создание метода countAll() в objects/product.php

Следующий код будет использоваться для подсчета общего количества записей в базе данных. Это будет использоваться для нумерации страниц.

Откройте файл product.php, который находится внутри папки objects. Добавьте в класс Product метод countAll().

// используется для пагинации товаров
public function countAll()
{
    // запрос MySQL
    $query = "SELECT id FROM " . $this->table_name . "";

    $stmt = $this->conn->prepare($query);
    $stmt->execute();

    $num = $stmt->rowCount();

    return $num;
}

6.10 Включение paging.php в index.php

Следующий код покажет пагинацию под нашим списком записей. Поместите следующий код после закрывающего тега table в разделе 6.6 вместо текста // здесь будет пагинация.

// страница, на которой используется пагинация
$page_url = "index.php?";

// подсчёт всех товаров в базе данных, чтобы подсчитать общее количество страниц
$total_rows = $product->countAll();

// пагинация
include_once "paging.php";

6.11 Результат

Запустите http://php-oop-mysql/index.php через ваш локальный сервер, вы должны увидеть что-то вроде этого:

Список товаров, страница 1.

Список товаров с пагинацией в PHP ООП

Список товаров, страница 2.

Список товаров 2 страница пагинации в PHP ООП

7. Обновление записи в PHP методом ООП

Я знаю, что наше руководство по PHP OOP MySQL довольно длинное. Пожалуйста, сделайте перерыв или выпейте кофе 🙂

7.1 Создание файла update_product.php

Создайте файл update_product.php и добавьте в него следующий код:

<?php

// здесь будет получение одного товара

// установка заголовка страницы
$page_title = "Обновление товара";

include_once "layout_header.php";
?>

<!-- здесь будет контент -->

<?php // подвал
require_once "layout_footer.php";

7.2 Создание кнопки «Просмотр всех товаров»

Следующий код отобразит кнопку. По нажатию на эту кнопку мы вернемся к списку товаров. Замените комментарий <!— здесь будет контент —> в предыдущем разделе следующим кодом.

<div class="right-button-margin">
    <a href="index.php" class="btn btn-default pull-right">Просмотр всех товаров</a>
</div>

<!-- здесь будет форма обновления товара -->

7.3 Получение информации об одном товаре на основе получаемого ID.

Следующий код получит текущие данные, которыми будет заполнена наша HTML-форма. Это важно, потому что это позволит пользователю узнать, какую именно запись он обновляет.

В файле update_product.php замените комментарий // здесь будет получение одного товара следующим кодом.

// получаем ID редактируемого товара
$id = isset($_GET["id"]) ? $_GET["id"] : die("ERROR: отсутствует ID.");

// подключаем файлы для работы с базой данных и файлы с объектами
include_once "config/database.php";
include_once "objects/product.php";
include_once "objects/category.php";

// получаем соединение с базой данных
$database = new Database();
$db = $database->getConnection();

// подготавливаем объекты
$product = new Product($db);
$category = new Category($db);

// устанавливаем свойство ID товара для редактирования
$product->id = $id;

// получаем информацию о редактируемом товаре
$product->readOne();

7.4 Добавление метода readOne() в класс Product

Метод readOne(), использованный в предыдущем разделе, не будет работать без следующего кода внутри файла /objects/product.php.

// метод для получения товара
function readOne()
{
    // запрос MySQL
    $query = "SELECT
                name, price, description, category_id
            FROM
                " . $this->table_name . "
            WHERE
                id = ?
            LIMIT
                0,1";

    $stmt = $this->conn->prepare($query);
    $stmt->bindParam(1, $this->id);
    $stmt->execute();

    $row = $stmt->fetch(PDO::FETCH_ASSOC);

    $this->name = $row["name"];
    $this->price = $row["price"];
    $this->description = $row["description"];
    $this->category_id = $row["category_id"];
}

7.5 Добавление значений в форму обновления товара

Теперь мы можем поместить текущие значения в каждый элемент формы. Замените комментарий <!— здесь будет форма обновления товара —> в update_product.php следующим кодом.

<!-- здесь будет контент -->

<form action="<?= htmlspecialchars($_SERVER["PHP_SELF"] . "?id={$id}"); ?>" method="post">
    <table class="table table-hover table-responsive table-bordered">

        <tr>
            <td>Название</td>
            <td><input type="text" name="name" value="<?= $product->name; ?>" class="form-control" /></td>
        </tr>

        <tr>
            <td>Цена</td>
            <td><input type="text" name="price" value="<?= $product->price; ?>" class="form-control" /></td>
        </tr>

        <tr>
            <td>Описание</td>
            <td><textarea name="description" class="form-control"><?= $product->description; ?></textarea></td>
        </tr>

        <tr>
            <td>Категория</td>
            <td>
                <!-- здесь будет раскрывающийся список для выбора категории -->
            </td>
        </tr>

        <tr>
            <td></td>
            <td>
                <button type="submit" class="btn btn-primary">Обновить</button>
            </td>
        </tr>

    </table>
</form>

7.6 Вывод категорий в выпадающем списке

Следующий код перечислит категории в раскрывающемся списке.

Обратите внимание, что мы помещаем if ($product->category_id == $category_id) { … внутрь цикла while. Это необходимо для предварительного выбора опции текущей записи.

Замените комментарии в предыдущем разделе <!— здесь будет раскрывающийся список для выбора категории —> следующим кодом.

<?php
$stmt = $category->read();

// помещаем категории в выпадающий список
echo "<select class='form-control' name='category_id'>";
    echo "<option selected disabled value=''>Выберите категорию</option>";
    while ($row_category = $stmt->fetch(PDO::FETCH_ASSOC)) {
        $category_id = $row_category["id"];
        $category_name = $row_category["name"];

        // необходимо выбрать текущую категорию товара
        if ($product->category_id == $category_id) {
            echo "<option value='$category_id' selected>";
        } else {
            echo "<option value='$category_id'>";
        }
        echo "$category_name</option>";
    }
echo "</select>";
?>

7.7 Код при отправке формы обновления товара

Следующий код присваивает «опубликованные» значения свойствам объекта. После назначения он обновит базу данных этими значениями с помощью метода update().

Откройте файл update_product.php. Замените комментарий <!— здесь будет контент —> следующим кодом.

<?php
// если форма была отправлена (submit)
if ($_POST) {

    // устанавливаем значения свойствам товара
    $product->name = $_POST["name"];
    $product->price = $_POST["price"];
    $product->description = $_POST["description"];
    $product->category_id = $_POST["category_id"];

    // обновление товара
    if ($product->update()) {
        echo "<div class='alert alert-success alert-dismissable'>";
        echo "Товар был обновлён.";
        echo "</div>";
    }

    // если не удается обновить товар, сообщим об этом пользователю
    else {
        echo "<div class='alert alert-danger alert-dismissable'>";
        echo "Невозможно обновить товар.";
        echo "</div>";
    }
}
?>

7.8 Добавление метода update() в класс товара

Следующий код заставит работать метод $product->update() из предыдущего раздела. Откройте product.php, который находится внутри папки objects, и добавьте следующий код.

// метод для обновления товара
function update()
{
    // MySQL запрос для обновления записи (товара)
    $query = "UPDATE
                " . $this->table_name . "
            SET
                name = :name,
                price = :price,
                description = :description,
                category_id  = :category_id
            WHERE
                id = :id";

    // подготовка запроса
    $stmt = $this->conn->prepare($query);

    // очистка
    $this->name = htmlspecialchars(strip_tags($this->name));
    $this->price = htmlspecialchars(strip_tags($this->price));
    $this->description = htmlspecialchars(strip_tags($this->description));
    $this->category_id = htmlspecialchars(strip_tags($this->category_id));
    $this->id = htmlspecialchars(strip_tags($this->id));

    // привязка значений
    $stmt->bindParam(":name", $this->name);
    $stmt->bindParam(":price", $this->price);
    $stmt->bindParam(":description", $this->description);
    $stmt->bindParam(":category_id", $this->category_id);
    $stmt->bindParam(":id", $this->id);

    // выполняем запрос
    if ($stmt->execute()) {
        return true;
    }

    return false;
}

7.9 Результат

Щелкните любую кнопку «Редактировать» на главной странице приложения. Форма обновления товара должна выглядеть следующим образом.

Изменение товара в PHP ООП

Когда вы отправите форму, будет показано сообщение.

Сообщение об изменении товара - PHP ООП MySQL

В базе данных будет изменена запись.

8.0 Чтение одной записи на PHP в ООП

Ранее мы сделали страницу для редактирования товара и код для «обновления записи». Теперь нам необходимо создать страницу для отображения товара. Этот раздел для чтения одной записи из базы данных будет сделать проще.

8.1 Создание страницы товара

На этой странице будут отображаться данные одной записи. Создайте новый файл и назовите его read_product.php, откройте этот файл и добавьте следующий код.

<?php

// установка заголовка страницы
$page_title = "Страница товара (чтение одного товара)";

require_once "layout_header.php";
?>

<!-- ссылка на все товары -->
<div class="right-button-margin">
    <a href="index.php" class="btn btn-primary pull-right">
        <span class="glyphicon glyphicon-list"></span> Просмотр всех товаров
    </a>
</div>

<?php // подвал
require_once "layout_footer.php";

8.2 Чтение одной записи на основе полученного ID

Следующий код считывает одну запись из базы данных. Поместите код перед комментарием // установка заголовка страницы в предыдущем разделе.

// получаем ID товара
$id = isset($_GET["id"]) ? $_GET["id"] : die("ERROR: отсутствует ID.");

// подключаем файлы для работы с базой данных и файлы с объектами
include_once "config/database.php";
include_once "objects/product.php";
include_once "objects/category.php";

// получаем соединение с базой данных
$database = new Database();
$db = $database->getConnection();
 
// подготавливаем объекты
$product = new Product($db);
$category = new Category($db);

// устанавливаем свойство ID товара для чтения
$product->id = $id;

// получаем информацию о товаре
$product->readOne();

8.3 Отображение товара в HTML-таблице

На этот раз мы отобразим информацию о товаре в таблице HTML. Поместите следующий код под закрывающим тегом div кнопки Просмотр всех товаров.

<!-- HTML-таблица для отображения информации о товаре -->
<table class="table table-hover table-responsive table-bordered">
    <tr>
        <td>Название</td>
        <td><?= $product->name; ?></td>
    </tr>
    <tr>
        <td>Цена</td>
        <td><?= $product->price; ?></td>
    </tr>
    <tr>
        <td>Описание</td>
        <td><?= $product->description; ?></td>
    </tr>
    <tr>
        <td>Категория</td>
        <td>
            <?php // выводим название категории
            $category->id = $product->category_id;
            $category->readName();

            echo $category->name;
            ?>
        </td>
    </tr>
</table>

8.4 Результат

Нажмите любую кнопку Просмотр на главной странице, вы должны увидеть что-то вроде изображения ниже.

Страница товара PHP ООП

9. Удаление записи на PHP в ООП

9.1 Добавление JavaScript для удаления товара

Поместите следующий код JavaScript перед закрывающим тегом body в файле layout_footer.php. Мы используем Bootbox.js и AJAX для создания диалогового окна подтверждения в стиле Bootstrap.

<script>
    // JavaScript для удаления товара
    $(document).on("click", ".delete-object", function() {
        const id = $(this).attr("delete-id");

        bootbox.confirm({
            message: "<h4>Вы уверены?</h4>",
            buttons: {
                confirm: {
                    label: "<span class='glyphicon glyphicon-ok'></span> Да",
                    className: "btn-danger"
                },
                cancel: {
                    label: "<span class='glyphicon glyphicon-remove'></span> Нет",
                    className: "btn-primary"
                }
            },
            callback: function(result) {
                if (result == true) {
                    $.post("delete_product.php", {
                        object_id: id
                    }, function(data) {
                        location.reload();
                    }).fail(function() {
                        alert("Невозможно удалить.");
                    });
                }
            }
        });

        return false;
    });
</script>

9.2 Создание файла delete_product.php

Создайте новый файл delete_product.php. Этот файл принимает ID, полученный в JavaScript в предыдущем разделе. Запись будет удалена из базы данных на основе полученного идентификатора (ID записи).

Откройте delete_product.php и добавьте в него следующий код:

<?php

// проверим, было ли получено значение в $_POST
if ($_POST) {

    // подключаем файлы для работы с базой данных и файлы с объектами
    include_once "config/database.php";
    include_once "objects/product.php";

    // получаем соединение с базой данных
    $database = new Database();
    $db = $database->getConnection();

    // подготавливаем объект Product
    $product = new Product($db);

    // устанавливаем ID товара для удаления
    $product->id = $_POST["object_id"];

    // удаляем товар
    if ($product->delete()) {
        echo "Товар был удалён";
    }

    // если невозможно удалить товар
    else {
        echo "Невозможно удалить товар";
    }
}

9.3 Метод для удаления товара в классе Product

Предыдущий раздел не будет работать с методом delete(), т.к. мы его ещё не создали. Откройте product.php, который находится внутри папки objects, и добавьте следующий код.

// метод для удаления товара
function delete()
{
    // запрос MySQL для удаления
    $query = "DELETE FROM " . $this->table_name . " WHERE id = ?";

    $stmt = $this->conn->prepare($query);
    $stmt->bindParam(1, $this->id);

    if ($result = $stmt->execute()) {
        return true;
    } else {
        return false;
    }
}

9.4 Результат

Щелкните любую кнопку Удалить на главной странице. Появится всплывающее окно подтверждения.

Подтверждение на удаление товара в PHP ООП

Если пользователь нажимает Да, то запись удаляется и исчезает из таблицы и базы данных.

Удаление товара в PHP ООП MySQL

10. Поиск записей в PHP методом ООП

Следующие разделы являются бонусными, поэтому если вы не разобрались с кодом выше, лучше сначала разберитесь, а затем продолжайте изучение дальше.

И так, продолжим, добавим функцию поиска. Это ответит на вопрос: «Как искать данные в базе данных на php?» Это очень полезная функция, потому что вы позволяете пользователям легко искать определенные данные в нашей базе данных MySQL.

10.1 Изменение index.php

Нам нужно изменить index.php, потому что мы добавляем функцию «поиска» и хотим, чтобы наш код был коротким и лаконичным. Наш index.php теперь будет выглядеть так.

<?php

// содержит переменные пагинации
include_once "config/core.php";

// файлы для работы с БД и файлы с объектами
include_once "config/database.php";
include_once "objects/product.php";
include_once "objects/category.php";

// получение соединения с БД
$database = new Database();
$db = $database->getConnection();

$product = new Product($db);
$category = new Category($db);

$page_title = "Список товаров";

include_once "layout_header.php";

// получение товаров
$stmt = $product->readAll($from_record_num, $records_per_page);

// укажем страницу, на которой используется пагинация
$page_url = "index.php?";

// подсчёт общего количества строк (используется для разбивки на страницы)
$total_rows = $product->countAll();

// контролирует, как будет отображаться список продуктов
include_once "read_template.php";

// содержит наш JavaScript и закрывающие теги html
include_once "layout_footer.php";

10.2 Создание read_template.php

Зачем нам этот шаблон? Он нам нужен, потому что точно такой же код может использоваться и в index.php и в search.php для отображения списка записей. Использование шаблона означает меньший объем кода.

Создайте read_template.php с формой поиска.

<?php

// форма поиска
echo "<form role='search' action='search.php'>";
echo "<div class='input-group col-md-3 pull-left margin-right-1em'>";
$search_value = isset($search_term) ? "value='{$search_term}'" : "";
echo "<input type='text' class='form-control' placeholder='Введите название или описание продукта ...' name='s' required {$search_value} />";
echo "<div class='input-group-btn'>";
echo "<button class='btn btn-primary' type='submit'><i class='glyphicon glyphicon-search'></i></button>";
echo "</div>";
echo "</div>";
echo "</form>";

// кнопка создания товара
echo "<div class='right-button-margin'>";
echo "<a href='create_product.php' class='btn btn-primary pull-right'>";
echo "<span class='glyphicon glyphicon-plus'></span> Создать товар";
echo "</a>";
echo "</div>";

// показать товары, если они есть
if ($total_rows > 0) {

    echo "<table class='table table-hover table-responsive table-bordered'>";
    echo "<tr>";
    echo "<th>Товар</th>";
    echo "<th>Цена</th>";
    echo "<th>Описание</th>";
    echo "<th>Категория</th>";
    echo "<th>Действия</th>";
    echo "</tr>";

    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {

        extract($row);

        echo "<tr>";
        echo "<td>{$name}</td>";
        echo "<td>{$price}</td>";
        echo "<td>{$description}</td>";
        echo "<td>";
        $category->id = $category_id;
        $category->readName();
        echo $category->name;
        echo "</td>";

        echo "<td>";

        // кнопка просмотра товара
        echo "<a href='read_product.php?id={$id}' class='btn btn-primary left-margin'>";
        echo "<span class='glyphicon glyphicon-list'></span> Просмотр";
        echo "</a>";

        // кнопка редактирования товара
        echo "<a href='update_product.php?id={$id}' class='btn btn-info left-margin'>";
        echo "<span class='glyphicon glyphicon-edit'></span> Редактировать";
        echo "</a>";

        // кнопка удаления товара
        echo "<a delete-id='{$id}' class='btn btn-danger delete-object'>";
        echo "<span class='glyphicon glyphicon-remove'></span> Удалить";
        echo "</a>";

        echo "</td>";

        echo "</tr>";
    }

    echo "</table>";

    // пагинация
    include_once "paging.php";
}

// сообщить пользователю, что товаров нет
else {
    echo "<div class='alert alert-danger'>Товаров не найдено.</div>";
}

10.3 Создание core.php в папке config

В папке config создайте новый файл core.php.

Этот файл будет содержать наши переменные пагинации страниц. Использование файла core.php является хорошей практикой, его можно использовать для хранения других значений конфигурации, которые могут вам понадобиться в будущем.

Откройте core.php и добавьте следующий код:

<?php

// страница, указанная в параметре URL, страница по умолчанию: 1
$page = isset($_GET["page"]) ? $_GET["page"] : 1;

// укажем число записей на странице
$records_per_page = 5;

// вычисление лимита запроса
$from_record_num = ($records_per_page * $page) - $records_per_page;

10.4 Изменение кода в paging.php

Новый код paging.php будет выглядеть следующим образом.

<?php

echo "<ul class='pagination'>";

// button for first page
if ($page > 1) {
    echo "<li><a href='{$page_url}' title='Перейти к первой странице'>Первая</a></li>";
}

// подсчёт всех товаров в БД, чтобы подсчитать общее количество страниц
$total_pages = ceil($total_rows / $records_per_page);

// диапазон ссылок для показа
$range = 2;

// отображаем ссылки на "диапазон страниц" вокруг "текущей страницы"
$initial_num = $page - $range;
$condition_limit_num = ($page + $range)  + 1;

for ($x = $initial_num; $x < $condition_limit_num; $x++) {

    // убедимся, что "$x больше 0" И "меньше или равно $total_pages"
    if (($x > 0) && ($x <= $total_pages)) {

        // текущая страница
        if ($x == $page) {
            echo "<li class='active'><a href='#'>$x <span class='sr-only'>(current)</span></a></li>";
        }
  
        // НЕ текущая страница
        else {
            echo "<li><a href='{$page_url}page=$x'>$x</a></li>";
        }
    }
}

// ссылка по последнюю страницу
if ($page < $total_pages) {
    echo "<li><a href='{$page_url}page={$total_pages}' title='Перейти к последней странице из {$total_pages}'>";
        echo "Последняя";
    echo "</a></li>";
}

echo "</ul>";

10.5 Создание файла search.php

Это самый важный файл в этом разделе. В этом файле будут отображаться записи на основе поискового запроса пользователя.

Создайте новый файл и назовите его search.php. Откройте этот файл и добавьте следующий код.

<?php

// содержит переменные пагинации
include_once "config/core.php";

// для подключения к БД и файлы с объектами
include_once "config/database.php";
include_once "objects/product.php";
include_once "objects/category.php";

// создание экземпляра класса базы данных и товара
$database = new Database();
$db = $database->getConnection();

$product = new Product($db);
$category = new Category($db);

// получение поискового запроса
$search_term = isset($_GET["s"]) ? $_GET["s"] : "";

$page_title = "Вы искали \"{$search_term}\"";
require_once "layout_header.php";

// запрос товаров
$stmt = $product->search($search_term, $from_record_num, $records_per_page);

// указываем страницу, на которой используется пагинация
$page_url = "search.php?s={$search_term}&";

// подсчитываем общее количество строк - используется для разбивки на страницы
$total_rows = $product->countAll_BySearch($search_term);

// шаблон для отображения списка товаров
include_once "read_template.php";

// содержит наш JavaScript и закрывающие теги html
require_once "layout_footer.php";

10.6 Добавление методов search() и countAll_BySearch()

Откройте файл product.php, который находится внутри папки objects. Добавьте в класс Product следующие методы.

// выбираем товары по поисковому запросу
public function search($search_term, $from_record_num, $records_per_page)
{
    // запрос к БД
    $query = "SELECT
            c.name as category_name, p.id, p.name, p.description, p.price, p.category_id, p.created
        FROM
            " . $this->table_name . " p
            LEFT JOIN
                categories c
                    ON p.category_id = c.id
        WHERE
            p.name LIKE ? OR p.description LIKE ?
        ORDER BY
            p.name ASC
        LIMIT
            ?, ?";

    // подготавливаем запрос
    $stmt = $this->conn->prepare($query);

    // привязываем значения переменных
    $search_term = "%{$search_term}%";
    $stmt->bindParam(1, $search_term);
    $stmt->bindParam(2, $search_term);
    $stmt->bindParam(3, $from_record_num, PDO::PARAM_INT);
    $stmt->bindParam(4, $records_per_page, PDO::PARAM_INT);

    // выполняем запрос
    $stmt->execute();

    // возвращаем значения из БД
    return $stmt;
}

// метод для подсчёта общего количества строк
public function countAll_BySearch($search_term)
{
    // запрос
    $query = "SELECT
            COUNT(*) as total_rows
        FROM
            " . $this->table_name . " p 
        WHERE
            p.name LIKE ? OR p.description LIKE ?";

    // подготовка запроса
    $stmt = $this->conn->prepare($query);

    // привязка значений
    $search_term = "%{$search_term}%";
    $stmt->bindParam(1, $search_term);
    $stmt->bindParam(2, $search_term);

    $stmt->execute();
    $row = $stmt->fetch(PDO::FETCH_ASSOC);

    return $row["total_rows"];
}

10.7 Результат

Выполним поиск по слову «телефон».

Поиск товара PHP ООП

11. Загрузка файлов в PHP методом ООП

Это последняя часть нашего руководства по PHP ООП MySQL. Наслаждайтесь каждой строчкой кода! 🙂

В этом разделе мы добавим функцию «загрузки файлов» для товаров.

11.1 Изменение HTML-формы

Откройте create_product.php и найдите тег form. Измените эту строку на следующий код. Enctype позволяет форме отправлять файл на сервер.

<form
    action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']);?>"
    method="post"
    enctype="multipart/form-data"
>

В той же форме, в HTML-таблице найдите закрывающий тег tr поля Категория. Это добавит поле загрузки файла, где пользователь может просмотреть файл, который он хочет загрузить. Добавьте следующий код.

<tr>
    <td>Изображение</td>
    <td><input type="file" name="image" /></td>
</tr>

11.2 Установка значения полю «Изображение»

Откройте create_product.php и добавьте новое поле «изображение». Значением будет имя отправленного файла. Мы использовали встроенную функцию sha1_file(), чтобы сделать имя файла уникальным.

Откройте файл create_product.php.
Поместите следующий код под $product->category_id = $ _POST[«category_id»];.

$image = !empty($_FILES["image"]["name"])
    ? sha1_file($_FILES["image"]["tmp_name"]) . "-" . basename($_FILES["image"]["name"]) : "";
$product->image = $image;

11.3 Изменение метода create()

Откройте папку objects и откройте в ней файл product.php. Найдите метод create().

Добавьте поле «Изображение», изменив запрос на:

// запрос
$query = "INSERT INTO " . $this->table_name . "
    SET name=:name, price=:price, description=:description,
        category_id=:category_id, image=:image, created=:created";

В разделе очистки добавьте:

$this->image = htmlspecialchars(strip_tags($this->image));

Затем привяжем значение.

$stmt->bindParam(":image", $this->image);

Добавьте свойство image в начало класса, после public $category_id;

public $image;

Используя PhpMyAdmin, добавьте поле image в таблицу товаров. Установите тип VARCHAR с длиной 512.

11.4 Вызов метода uploadPhoto()

Откройте create_product.php и найдите эту строку:

echo "<div class='alert alert-success'>Товар был успешно создан.</div>";

Поместите следующий код под приведенный код выше. Это вызовет метод uploadPhoto(), который будет загружать файл на сервер.

// пытаемся загрузить отправленный файл
// метод uploadPhoto() вернет сообщение об ошибке, в случае неудачи
echo $product->uploadPhoto();

11.5 Создание метода uploadPhoto()

Предыдущий раздел не будет работать без метода uploadPhoto().

Откройте папку objects и в ней файл product.php. Добавьте в класс следующий метод.

// загрузка файла изображения на сервер
function uploadPhoto()
{
    $result_message = "";

    // если изображение не пустое, пробуем загрузить его
    if ($this->image) {

        // функция sha1_file() используется для создания уникального имени файла
        $target_directory = "uploads/";
        $target_file = $target_directory . $this->image;
        $file_type = pathinfo($target_file, PATHINFO_EXTENSION);

        // сообщение об ошибке пусто
        $file_upload_error_messages = "";
    }

    return $result_message;
}

11.6 Валидация отправляемого файла

Теперь мы проверим отправленный файл:

  • определение подлинности изображения;
  • ограничим разрешенные типы файлов;
  • предотвратим несколько файлов на сервере;
  • запретим загрузку файлов большого размера;
  • убедимся, что папка «uploads» существует.

Добавьте следующий код после $file_upload_error_messages = «»; из предыдущего раздела.

// убеждаемся, что файл - изображение
$check = getimagesize($_FILES["image"]["tmp_name"]);

if ($check !== false) {
    // отправленный файл является изображением
} else {
    $file_upload_error_messages .= "<div>Отправленный файл не является изображением.</div>";
}

// проверяем, разрешены ли определенные типы файлов
$allowed_file_types = array("jpg", "jpeg", "png", "gif");

if (!in_array($file_type, $allowed_file_types)) {
    $file_upload_error_messages .= "<div>Разрешены только файлы JPG, JPEG, PNG, GIF.</div>";
}

// убеждаемся, что файл не существует
if (file_exists($target_file)) {
    $file_upload_error_messages .= "<div>Изображение уже существует. Попробуйте изменить имя файла.</div>";
}

// убедимся, что отправленный файл не слишком большой (не может быть больше 1 МБ)
if ($_FILES["image"]["size"] > (1024000)) {
    $file_upload_error_messages .= "<div>Размер изображения не должен превышать 1 МБ.</div>";
}

// убедимся, что папка uploads существует, если нет, то создаём
if (!is_dir($target_directory)) {
    mkdir($target_directory, 0777, true);
}

11.7 Показ сообщений об ошибках

Если файл прошел нашу валидацию, мы загрузим его на сервер в папку uploads. Если есть ошибка, мы вернем ее, чтобы показать пользователю.

Поместите следующий код после кода предыдущего раздела.

// если $file_upload_error_messages всё ещё пуст
if (empty($file_upload_error_messages)) {

    // ошибок нет, пробуем загрузить файл
    if (move_uploaded_file($_FILES["image"]["tmp_name"], $target_file)) {
        // фото загружено
    } else {
        $result_message .= "<div class='alert alert-danger'>";
            $result_message .= "<div>Невозможно загрузить фото.</div>";
            $result_message .= "<div>Обновите запись, чтобы загрузить фото снова.</div>";
        $result_message .= "</div>";
    }
}

// если $file_upload_error_messages НЕ пусто
else {

    // это означает, что есть ошибки, поэтому покажем их пользователю
    $result_message .= "<div class='alert alert-danger'>";
        $result_message .= "{$file_upload_error_messages}";
        $result_message .= "<div>Обновите запись, чтобы загрузить фото.</div>";
    $result_message .= "</div>";
}

11.8 Показ загруженного изображения

Откройте папку objects и в ней файл product.php. Найдите метод readOne(). Добавьте в метод поле image. Обновлённый метод должен выглядеть следующим образом.

// метод для получения товара
function readOne()
{
    // MySQL запрос
    $query = "SELECT name, price, description, category_id, image
        FROM " . $this->table_name . "
        WHERE id = ?
        LIMIT 0,1";

    $stmt = $this->conn->prepare($query);
    $stmt->bindParam(1, $this->id);
    $stmt->execute();

    $row = $stmt->fetch(PDO::FETCH_ASSOC);

    $this->name = $row["name"];
    $this->price = $row["price"];
    $this->description = $row["description"];
    $this->category_id = $row["category_id"];
    $this->image = $row["image"];
}

Откройте файл read_product.php и найдите закрывающий тег tr поля Категория в HTML-таблице. Добавьте следующий код, это покажет загруженное изображение.

<tr>
    <td>Изображение</td>
    <td>
        <?= $product->image ? "<img src='uploads/{$product->image}' style='width:300px;' />" : "Изображение не найдено."; ?>
    </td>
</tr>

11.9 Результат

Нажмите кнопку Создать товар, вы увидите что-то вроде изображения ниже.

Добавление изображения товара PHP ООП

Если вы попытаетесь загрузить недопустимый файл изображения, например файл PDF. Появится сообщение об ошибке. Если всё хорошо, то появится сообщение Товар был успешно создан..

Зайдите на страницу созданного товара и должны увидеть изображение.

Отправка на сервер изображения товара PHP ООП

Весь представленный код протестирован с PHP 7.4 и MySQL 5.7.

Если вам понравилась данная статья, рекомендую к прочтению:

  1. jQuery + AJAX + JSON + PHP (Часть 1 — Фронтенд).
  2. Простой REST API в PHP (Часть 2 — Бэкенд).