Что такое REST API
Я использую Docker, поэтому привожу ссылку на готовый образ в GitFlic, образ содержит все необходимое для удобной работы.
Во многих приложениях REST API
необходим, потому что это самый легкий способ создания, чтения, обновления или удаления информации между различными приложениями через Интернет или протокол HTTP
. Эта информация представляется пользователю в одно мгновение, особенно если вы используете JavaScript
для отображения данных на веб-странице.
REST API
может использоваться любым приложением, которое может подключаться к Интернету. Если данные из приложения могут быть созданы, прочитаны, обновлены или удалены с помощью другого приложения, это обычно означает, что используется REST API
.
В итоге у нас должно появится приложение, которое будет работать через REST API
:
Файловая структура
- Папка
public_html
-
Папка
api
-
Папка
config
- Файл
database.php
подключение к базе данных - Файл
core.php
базовая конфигурация
- Файл
-
Папка
objects
- Файл
product.php
чтение данных из базы продуктов - Файл
category.php
чтение данных из базы категорий
- Файл
-
Папка
product
- Файл
create.php
создание товара - Файл
delete.php
удаление товара - Файл
read.php
получение товаров - Файл
read_paging.php
получение товаров с пагинацией - Файл
read_one.php
получение одного товара - Файл
update.php
обновление товара - Файл
search.php
поиск по товарам
- Файл
-
Папка
category
- Файл
read.php
создание категории
- Файл
-
Папка
shared
- Файл
utilities.php
массив для пагинации
- Файл
-
Папка
-
Папка
app
-
Папка
assets
-
Папка
css
- Файл
style.css
стили приложения
- Файл
-
Папка
js
- Файл
bootbox.min.js
всплывающие окна (CDN) - Файл
jquery.min.js
Jquery (CDN)
- Файл
-
Папка
-
Папка
product
- Файл
read-one-prod.js
обработчик нажатия кнопки «Просмотр товара» - Файл
create-prod.js
обработчик нажатия кнопки «Создать товар» - Файл
delete-prod.js
обработчик нажатия кнопки «Удалить товар» - Файл
read-prod.js
показывает товары на странице при загрузки - Файл
update-prod.js
обработчик нажатия кнопки «Обновления товара» - Файл
create-prod.js
обработчик нажатия кнопки «Создать товар» - Файл
products.js
функции для других компонентов
- Файл
- Файл
app.js
основные функции HTML и JavaScript
-
Папка
- Файл
index.html
исполняемый файл
База данных api/config/database.php
Используя PhpMyAdmin
, создаем новую базу данных api_db
. После этого выполняем следующие SQL-запросы, чтобы создать новые таблицы с образцами данных.
Таблица категорий
CREATE TABLE IF NOT EXISTS `categories` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(256) NOT NULL,
`description` text NOT NULL,
`created` datetime NOT NULL,
`modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=19 ;
Наполнение таблицы категорий
INSERT INTO `categories` (`id`, `name`, `description`, `created`, `modified`) VALUES
(1, "Fashion", "Category for anything related to fashion.", "2014-06-01 00:35:07", "2014-05-30 17:34:33"),
(2, "Electronics", "Gadgets, drones and more.", "2014-06-01 00:35:07", "2014-05-30 17:34:33"),
(3, "Motors", "Motor sports and more", "2014-06-01 00:35:07", "2014-05-30 17:34:54"),
(5, "Movies", "Movie products.", "2019-05-20 10:22:05", "2019-08-20 10:30:15"),
(6, "Books", "Kindle books, audio books and more.", "2018-03-14 08:05:25", "2019-05-20 11:29:11"),
(13, "Sports", "Drop into new winter gear.", "2016-01-09 02:24:24", "2016-01-09 01:24:24");
Таблица товаров
CREATE TABLE IF NOT EXISTS `products` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL,
`description` text NOT NULL,
`price` decimal(10,0) NOT NULL,
`category_id` int(11) NOT NULL,
`created` datetime NOT NULL,
`modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=65 ;
Наполнение таблицы товаров
INSERT INTO `products` (`id`, `name`, `description`, `price`, `category_id`, `created`, `modified`) VALUES
(1, "LG P880 4X HD", "My first awesome phone!", "336", 3, "2014-06-01 01:12:26", "2014-05-31 17:12:26"),
(2, "Google Nexus 4", "The most awesome phone of 2013!", "299", 2, "2014-06-01 01:12:26", "2014-05-31 17:12:26"),
(3, "Samsung Galaxy S4", "How about no?", "600", 3, "2014-06-01 01:12:26", "2014-05-31 17:12:26"),
(6, "Bench Shirt", "The best shirt!", "29", 1, "2014-06-01 01:12:26", "2014-05-31 02:12:21"),
(7, "Lenovo Laptop", "My business partner.", "399", 2, "2014-06-01 01:13:45", "2014-05-31 02:13:39"),
(8, "Samsung Galaxy Tab 10.1", "Good tablet.", "259", 2, "2014-06-01 01:14:13", "2014-05-31 02:14:08"),
(9, "Spalding Watch", "My sports watch.", "199", 1, "2014-06-01 01:18:36", "2014-05-31 02:18:31"),
(10, "Sony Smart Watch", "The coolest smart watch!", "300", 2, "2014-06-06 17:10:01", "2014-06-05 18:09:51"),
(11, "Huawei Y300", "For testing purposes.", "100", 2, "2014-06-06 17:11:04", "2014-06-05 18:10:54"),
(12, "Abercrombie Lake Arnold Shirt", "Perfect as gift!", "60", 1, "2014-06-06 17:12:21", "2014-06-05 18:12:11"),
(13, "Abercrombie Allen Brook Shirt", "Cool red shirt!", "70", 1, "2014-06-06 17:12:59", "2014-06-05 18:12:49"),
(26, "Another product", "Awesome product!", "555", 2, "2014-11-22 19:07:34", "2014-11-21 20:07:34"),
(28, "Wallet", "You can absolutely use this one!", "799", 6, "2014-12-04 21:12:03", "2014-12-03 22:12:03"),
(31, "Amanda Waller Shirt", "New awesome shirt!", "333", 1, "2014-12-13 00:52:54", "2014-12-12 01:52:54"),
(42, "Nike Shoes for Men", "Nike Shoes", "12999", 3, "2015-12-12 06:47:08", "2015-12-12 05:47:08"),
(48, "Bristol Shoes", "Awesome shoes.", "999", 5, "2016-01-08 06:36:37", "2016-01-08 05:36:37"),
(60, "Rolex Watch", "Luxury watch.", "25000", 1, "2016-01-11 15:46:02", "2016-01-11 14:46:02");
Подключение к базе данных
Приведенный ниже код показывает учетные данные базы данных и метод для получения подключения к базе данных с помощью PDO
:
api/config/database.php<?
// подключение к базе данных
class Database
{
// укажите свои учетные данные базы данных
private $host = "db";
private $db_name = "api_db";
private $username = "root";
private $password = "root123";
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);
$this->conn->exec("set names utf8");
} catch (PDOException $exception) {
echo "Ошибка подключения: " . $exception->getMessage();
}
return $this->conn;
}
}
Базовые настройки api/config/core.php
Файл содержит нашу базовую конфигурацию, такую как базовый URL и переменные пагинации:
api/config/core.php<?
// показывать сообщения об ошибках
ini_set("display_errors", 1);
error_reporting(E_ALL);
// URL домашней страницы
$home_url = "http://site.loc/api/";
// страница указана в параметре URL, страница по умолчанию одна
$page = isset($_GET["page"]) ? $_GET["page"] : 1;
// установка количества записей на странице
$records_per_page = 5;
// расчёт для запроса предела записей
$from_record_num = ($records_per_page * $page) - $records_per_page;
Работа с продуктами api/objects/product.php
Класс для работы с продуктами в базе данных:
api/objects/product.php<?
// получение продуктов
class Product
{
// подключение к базе данных и таблице "products"
private $conn;
private $table_name = "products";
// свойства объекта
public $id;
public $name;
public $description;
public $price;
public $category_id;
public $category_name;
public $created;
// конструктор для соединения с базой данных
public function __construct($db)
{
$this->conn = $db;
}
// метод для получения товаров
function read()
{
// выбираем все записи
$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
ORDER BY p.created DESC";
// подготовка запроса
$stmt = $this->conn->prepare($query);
// выполняем запрос
$stmt->execute();
return $stmt;
}
// метод для создания товаров
function create()
{
// запрос для вставки (создания) записей
$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->created = htmlspecialchars(strip_tags($this->created));
// свяжем значения переменных
$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->created);
// выполняем запрос
if ($stmt->execute()) {
return true;
}
return false;
}
// метод для получения конкретного товара по ID
function readOne()
{
// запрос для чтения одной записи (товара)
$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.id = ?
LIMIT 0,1";
// подготовка запроса
$stmt = $this->conn->prepare($query);
// привязываем id товара, который будет получен
$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->category_name = $row["category_name"];
}
// метод для обновления товара
function update()
{
// запрос для обновления записи (товара)
$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;
}
// метод для удаления товара
function delete()
{
// запрос для удаления записи (товара)
$query = "DELETE FROM " . $this->table_name . " WHERE id = ?";
// подготовка запроса
$stmt = $this->conn->prepare($query);
// очистка
$this->id = htmlspecialchars(strip_tags($this->id));
// привязываем id записи для удаления
$stmt->bindParam(1, $this->id);
// выполняем запрос
if ($stmt->execute()) {
return true;
}
return false;
}
// метод для поиска товаров
function search($keywords)
{
// поиск записей (товаров) по "названию товара", "описанию товара", "названию категории"
$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 ? OR c.name LIKE ?
ORDER BY p.created DESC";
// подготовка запроса
$stmt = $this->conn->prepare($query);
// очистка
$keywords = htmlspecialchars(strip_tags($keywords));
$keywords = "%{$keywords}%";
// привязка значений переменных
$stmt->bindParam(1, $keywords);
$stmt->bindParam(2, $keywords);
$stmt->bindParam(3, $keywords);
// выполняем запрос
$stmt->execute();
return $stmt;
}
// получение товаров с пагинацией
public function readPaging($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
ORDER BY p.created DESC
LIMIT ?, ?";
// подготовка запроса
$stmt = $this->conn->prepare($query);
// свяжем значения переменных
$stmt->bindParam(1, $from_record_num, PDO::PARAM_INT);
$stmt->bindParam(2, $records_per_page, PDO::PARAM_INT);
// выполняем запрос
$stmt->execute();
// вернём значения из базы данных
return $stmt;
}
// данный метод возвращает кол-во товаров
public function count()
{
$query = "SELECT COUNT(*) as total_rows FROM " . $this->table_name . "";
$stmt = $this->conn->prepare($query);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row["total_rows"];
}
}
Работа с категориями api/objects/category.php
Класс для работы с категориями в базе данных:
api/objects/category.php<?
class Category
{
// подключение к базе данных и таблице "categories"
private $conn;
private $table_name = "categories";
// свойства объекта
public $id;
public $name;
public $description;
public $created;
public function __construct($db)
{
$this->conn = $db;
}
// метод для получения всех категорий товаров
public function readAll()
{
$query = "SELECT id, name, description
FROM " . $this->table_name . "
ORDER BY name";
$stmt = $this->conn->prepare($query);
$stmt->execute();
return $stmt;
}
}
Получение товаров api/product/create.php
Файл для создания новых продуктов:
api/product/create.php<?
// необходимые HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
// получаем соединение с базой данных
include_once "../config/database.php";
// создание объекта товара
include_once "../objects/product.php";
$database = new Database();
$db = $database->getConnection();
$product = new Product($db);
// получаем отправленные данные
$data = json_decode(file_get_contents("php://input"));
// убеждаемся, что данные не пусты
if (
!empty($data->name) &&
!empty($data->price) &&
!empty($data->description) &&
!empty($data->category_id)
) {
// устанавливаем значения свойств товара
$product->name = $data->name;
$product->price = $data->price;
$product->description = $data->description;
$product->category_id = $data->category_id;
$product->created = date("Y-m-d H:i:s");
// создание товара
if ($product->create()) {
// установим код ответа - 201 создано
http_response_code(201);
// сообщим пользователю
echo json_encode(array("message" => "Товар был создан."), JSON_UNESCAPED_UNICODE);
}
// если не удается создать товар, сообщим пользователю
else {
// установим код ответа - 503 сервис недоступен
http_response_code(503);
// сообщим пользователю
echo json_encode(array("message" => "Невозможно создать товар."), JSON_UNESCAPED_UNICODE);
}
}
// сообщим пользователю что данные неполные
else {
// установим код ответа - 400 неверный запрос
http_response_code(400);
// сообщим пользователю
echo json_encode(array("message" => "Невозможно создать товар. Данные неполные."), JSON_UNESCAPED_UNICODE);
}
Удаление товара api/product/delete.php
Файл для удаления продуктов:
api/product/delete.php<?
// HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
// подключим файл для соединения с базой и объектом Product
include_once "../config/database.php";
include_once "../objects/product.php";
// получаем соединение с БД
$database = new Database();
$db = $database->getConnection();
// подготовка объекта
$product = new Product($db);
// получаем id товара
$data = json_decode(file_get_contents("php://input"));
// установим id товара для удаления
$product->id = $data->id;
// удаление товара
if ($product->delete()) {
// код ответа - 200 ok
http_response_code(200);
// сообщение пользователю
echo json_encode(array("message" => "Товар был удалён"), JSON_UNESCAPED_UNICODE);
}
// если не удается удалить товар
else {
// код ответа - 503 Сервис не доступен
http_response_code(503);
// сообщим об этом пользователю
echo json_encode(array("message" => "Не удалось удалить товар"));
}
Чтение товара api/product/read.php
Файл для чтения продуктов:
api/product/read.php<?
// необходимые HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
// подключение базы данных и файл, содержащий объекты
include_once "../config/database.php";
include_once "../objects/product.php";
// получаем соединение с базой данных
$database = new Database();
$db = $database->getConnection();
// инициализируем объект
$product = new Product($db);
// запрашиваем товары
$stmt = $product->read();
$num = $stmt->rowCount();
// проверка, найдено ли больше 0 записей
if ($num > 0) {
// массив товаров
$products_arr = array();
$products_arr["records"] = array();
// получаем содержимое нашей таблицы
// fetch() быстрее, чем fetchAll()
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
// извлекаем строку
extract($row);
$product_item = array(
"id" => $id,
"name" => $name,
"description" => html_entity_decode($description),
"price" => $price,
"category_id" => $category_id,
"category_name" => $category_name
);
array_push($products_arr["records"], $product_item);
}
// устанавливаем код ответа - 200 OK
http_response_code(200);
// выводим данные о товаре в формате JSON
echo json_encode($products_arr);
} else {
// установим код ответа - 404 Не найдено
http_response_code(404);
// сообщаем пользователю, что товары не найдены
echo json_encode(array("message" => "Товары не найдены."), JSON_UNESCAPED_UNICODE);
}
Чтение товара с пагинацией api/product/read_paging.php
Файл для чтения продуктов с пагинацией:
api/product/read_paging.php<?
// установим HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
// подключение файлов
include_once "../config/core.php";
include_once "../shared/utilities.php";
include_once "../config/database.php";
include_once "../objects/product.php";
// utilities
$utilities = new Utilities();
// создание подключения
$database = new Database();
$db = $database->getConnection();
// инициализация объекта
$product = new Product($db);
// запрос товаров
$stmt = $product->readPaging($from_record_num, $records_per_page);
$num = $stmt->rowCount();
// если больше 0 записей
if ($num > 0) {
// массив товаров
$products_arr = array();
$products_arr["records"] = array();
$products_arr["paging"] = array();
// получаем содержимое нашей таблицы
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
// извлечение строки
extract($row);
$product_item = array(
"id" => $id,
"name" => $name,
"description" => html_entity_decode($description),
"price" => $price,
"category_id" => $category_id,
"category_name" => $category_name
);
array_push($products_arr["records"], $product_item);
}
// подключим пагинацию
$total_rows = $product->count();
$page_url = "{$home_url}product/read_paging.php?";
$paging = $utilities->getPaging($page, $total_rows, $records_per_page, $page_url);
$products_arr["paging"] = $paging;
// установим код ответа - 200 OK
http_response_code(200);
// вывод в json-формате
echo json_encode($products_arr);
} else {
// код ответа - 404 Ничего не найдено
http_response_code(404);
// сообщим пользователю, что товаров не существует
echo json_encode(array("message" => "Товары не найдены"), JSON_UNESCAPED_UNICODE);
}
Чтение товара с пагинацией api/product/read_one.php
Файл для получения одного продукта:
api/product/read_one.php<?
// необходимые HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: access");
header("Access-Control-Allow-Methods: GET");
header("Access-Control-Allow-Credentials: true");
header("Content-Type: application/json");
// подключение файла для соединения с базой и файл с объектом
include_once "../config/database.php";
include_once "../objects/product.php";
// получаем соединение с базой данных
$database = new Database();
$db = $database->getConnection();
// подготовка объекта
$product = new Product($db);
// установим свойство ID записи для чтения
$product->id = isset($_GET["id"]) ? $_GET["id"] : die();
// получим детали товара
$product->readOne();
if ($product->name != null) {
// создание массива
$product_arr = array(
"id" => $product->id,
"name" => $product->name,
"description" => $product->description,
"price" => $product->price,
"category_id" => $product->category_id,
"category_name" => $product->category_name
);
// код ответа - 200 OK
http_response_code(200);
// вывод в формате json
echo json_encode($product_arr);
} else {
// код ответа - 404 Не найдено
http_response_code(404);
// сообщим пользователю, что такой товар не существует
echo json_encode(array("message" => "Товар не существует"), JSON_UNESCAPED_UNICODE);
}
Обновление товара api/product/update.php
Файл для обновления продукта:
api/product/update.php<?
// HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
// подключаем файл для работы с БД и объектом Product
include_once "../config/database.php";
include_once "../objects/product.php";
// получаем соединение с базой данных
$database = new Database();
$db = $database->getConnection();
// подготовка объекта
$product = new Product($db);
// получаем id товара для редактирования
$data = json_decode(file_get_contents("php://input"));
// установим id свойства товара для редактирования
$product->id = $data->id;
// установим значения свойств товара
$product->name = $data->name;
$product->price = $data->price;
$product->description = $data->description;
$product->category_id = $data->category_id;
// обновление товара
if ($product->update()) {
// установим код ответа - 200 ok
http_response_code(200);
// сообщим пользователю
echo json_encode(array("message" => "Товар был обновлён"), JSON_UNESCAPED_UNICODE);
}
// если не удается обновить товар, сообщим пользователю
else {
// код ответа - 503 Сервис не доступен
http_response_code(503);
// сообщение пользователю
echo json_encode(array("message" => "Невозможно обновить товар"), JSON_UNESCAPED_UNICODE);
}
Поиск по товарам api/product/search.php
Файл для поиска продукта:
api/product/search.php<?
// HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
// подключение необходимых файлов
include_once "../config/core.php";
include_once "../config/database.php";
include_once "../objects/product.php";
// создание подключения к БД
$database = new Database();
$db = $database->getConnection();
// инициализируем объект
$product = new Product($db);
// получаем ключевые слова
$keywords = isset($_GET["s"]) ? $_GET["s"] : "";
// запрос товаров
$stmt = $product->search($keywords);
$num = $stmt->rowCount();
// проверяем, найдено ли больше 0 записей
if ($num > 0) {
// массив товаров
$products_arr = array();
$products_arr["records"] = array();
// получаем содержимое нашей таблицы
// fetch() быстрее чем fetchAll()
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
// извлечём строку
extract($row);
$product_item = array(
"id" => $id,
"name" => $name,
"description" => html_entity_decode($description),
"price" => $price,
"category_id" => $category_id,
"category_name" => $category_name
);
array_push($products_arr["records"], $product_item);
}
// код ответа - 200 OK
http_response_code(200);
// покажем товары
echo json_encode($products_arr);
} else {
// код ответа - 404 Ничего не найдено
http_response_code(404);
// скажем пользователю, что товары не найдены
echo json_encode(array("message" => "Товары не найдены."), JSON_UNESCAPED_UNICODE);
}
Получение категорий api/category/read.php
Файл для получения категорий продуктов:
api/category/read.php<?
// установим HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
// подключение файлов для соединения с БД и файл с объектом Category
include_once "../config/database.php";
include_once "../objects/category.php";
// создание подключения к базе данных
$database = new Database();
$db = $database->getConnection();
// инициализация объекта
$category = new Category($db);
// получаем категории
$stmt = $category->readAll();
$num = $stmt->rowCount();
// проверяем, найдено ли больше 0 записей
if ($num > 0) {
// массив для записей
$categories_arr = array();
$categories_arr["records"] = array();
// получим содержимое нашей таблицы
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
// извлекаем строку
extract($row);
$category_item = array(
"id" => $id,
"name" => $name,
"description" => html_entity_decode($description)
);
array_push($categories_arr["records"], $category_item);
}
// код ответа - 200 OK
http_response_code(200);
// покажем данные категорий в формате json
echo json_encode($categories_arr);
} else {
// код ответа - 404 Ничего не найдено
http_response_code(404);
// сообщим пользователю, что категории не найдены
echo json_encode(array("message" => "Категории не найдены"), JSON_UNESCAPED_UNICODE);
}
Получение массива для пагинации api/shared/utilities.php
Файл для получения массива пагинации продуктов:
api/shared/utilities.php<?
class Utilities
{
public function getPaging($page, $total_rows, $records_per_page, $page_url)
{
// массив пагинации
$paging_arr = array();
// кнопка для первой страницы
$paging_arr["first"] = $page > 1 ? "{$page_url}page=1" : "";
// подсчёт всех товаров в базе данных для подсчета общего количества страниц
$total_pages = ceil($total_rows / $records_per_page);
// диапазон ссылок для показа
$range = 1;
// отображать диапазон ссылок вокруг текущей страницы
$initial_num = $page - $range;
$condition_limit_num = ($page + $range) + 1;
$paging_arr["pages"] = array();
$page_count = 0;
for ($x = $initial_num; $x < $condition_limit_num; $x++) {
// убедимся, что $x > 0 И $x <= $total_pages
if (($x > 0) && ($x <= $total_pages)) {
$paging_arr["pages"][$page_count]["page"] = $x;
$paging_arr["pages"][$page_count]["url"] = "{$page_url}page={$x}";
$paging_arr["pages"][$page_count]["current_page"] = $x == $page ? "yes" : "no";
$page_count++;
}
}
// кнопка для последней страницы
$paging_arr["last"] = $page < $total_pages ? "{$page_url}page={$total_pages}" : "";
// формат json
return json_encode($paging_arr);
}
}
Дальше мы будем использовать REST API
, созданный на PHP. В моем случае, есть два адреса, где можно получить доступ к REST API
.
На странице http://site.loc/api/product/read.php
выводится json
строка всех товаров без пагинации:
На странице http://site.loc/api/product/read_paging.php
выводится json
строка всех товаров с пагинацией:
Не все скрипты и стили мы подгружаем с сервера, что-то подгружается с CDN
.
Стартовая страница приложения index.html
index.html<!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>Получение товаров</title>
<!-- bootstrap CSS -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous" />
<!-- основной CSS -->
<link href="app/assets/css/style.css" rel="stylesheet" />
</head>
<body>
<!-- здесь будет выводиться наше приложение -->
<div id="app"></div>
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<!-- bootstrap JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<!-- JS для всплывающих окон -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/4.4.0/bootbox.min.js"></script>
<!-- основной файл скриптов -->
<script src="app/app.js"></script>
<!-- product scripts -->
<script src="app/product/read-prod.js"></script>
<script src="app/product/create-prod.js"></script>
<script src="app/product/read-one-prod.js"></script>
<script src="app/product/update-prod.js"></script>
<script src="app/product/delete-prod.js"></script>
<script src="app/product/products.js"></script>
<script src="app/product/search-prod.js"></script>
</body>
</html>
Стили для приложения app/assets/css/style.css
app/assets/css/style.css.m-r-10px {
margin-right: 10px;
}
.m-b-10px {
margin-bottom: 10px;
}
.m-b-15px {
margin-bottom: 15px;
}
.m-b-20px {
margin-bottom: 20px;
}
.w-5-pct {
width: 5%;
}
.w-10-pct {
width: 10%;
}
.w-15-pct {
width: 15%;
}
.w-20-pct {
width: 20%;
}
.w-25-pct {
width: 25%;
}
.w-30-pct {
width: 30%;
}
.w-35-pct {
width: 35%;
}
.w-40-pct {
width: 40%;
}
.w-45-pct {
width: 45%;
}
.w-50-pct {
width: 50%;
}
.w-55-pct {
width: 55%;
}
.w-60-pct {
width: 60%;
}
.w-65-pct {
width: 65%;
}
.w-70-pct {
width: 70%;
}
.w-75-pct {
width: 75%;
}
.w-80-pct {
width: 80%;
}
.w-85-pct {
width: 85%;
}
.w-90-pct {
width: 90%;
}
.w-95-pct {
width: 95%;
}
.w-100-pct {
width: 100%;
}
.display-none {
display: none;
}
.padding-bottom-2em {
padding-bottom: 2em;
}
.width-30-pct {
width: 30%;
}
.width-40-pct {
width: 40%;
}
.overflow-hidden {
overflow: hidden;
}
.margin-right-1em {
margin-right: 1em;
}
.right-margin {
margin: 0 0.5em 0 0;
}
.margin-bottom-1em {
margin-bottom: 1em;
}
.margin-zero {
margin: 0;
}
.text-align-center {
text-align: center;
}
.pagination a {
cursor: pointer;
}
Обработчик кнопки «Просмотр товара» app/product/read-one-prod.js
app/product/read-one-prod.jsjQuery(($) => {
// Обрабатываем нажатие кнопки «Просмотр товара»
$(document).on("click", ".read-one-product-button", function () {
// Получаем ID товара
const id = $(this).attr("data-id");
// Чтение записи товара на основе данного идентификатора
$.getJSON("http://site.loc/api/product/read_one.php?id=" + id, (data) => {
// Начало HTML
let read_one_product_html =`
<!-- При нажатии будем отображать список товаров -->
<div id="read-products" class="btn btn-primary pull-right m-b-15px read-products-button"><span class="glyphicon glyphicon-list"></span> Все товары</div>
<!-- Полные данные о товаре будут показаны в этой таблице -->
<table class="table table-bordered table-hover">
<tr>
<td class="w-30-pct">Название</td>
<td class="w-70-pct">`+ data.name +`</td>
</tr>
<tr>
<td>Цена</td>
<td>`+ data.price +`</td>
</tr>
<tr>
<td>Описание</td>
<td>`+ data.description +`</td>
</tr>
<tr>
<td>Категория</td>
<td>` + data.category_name +`</td>
</tr>
</table>`;
// Вставка HTML в «page-content» нашего приложения
$("#page-content").html(read_one_product_html);
// Изменяем заголовок страницы
changePageTitle("Просмотр товара");
});
});
});
Обработчик кнопки «Создать товар» app/product/create-prod.js
app/product/create-prod.jsjQuery(($) => {
// Показать html форму при нажатии кнопки «создать товар»
$(document).on("click", ".create-product-button", () => {
// Загрузка списка категорий
$.getJSON("http://site.loc/api/category/read.php", (data) => {
// Перебор возвращаемого списка данных и создание списка выбора
let categories_options_html = `<select name="category_id" class="form-control">`;
$.each(data.records, (key, val) => {
categories_options_html += `<option value="` + val.id + `">` + val.name + `</option>`;
});
categories_options_html += `</select>`;
let create_product_html =`
<!-- Кнопка для показа всех товаров -->
<div id="read-products" class="btn btn-primary pull-right m-b-15px read-products-button"><span class="glyphicon glyphicon-list"></span> Все товары</div>
<!-- html форма «Создание товара» -->
<form id="create-product-form" action="#" method="post" border="0">
<table class="table table-hover table-responsive table-bordered">
<tr>
<td>Название</td>
<td><input type="text" name="name" class="form-control" required /></td>
</tr>
<tr>
<td>Цена</td>
<td><input type="number" min="1" name="price" class="form-control" required /></td>
</tr>
<tr>
<td>Описание</td>
<td><textarea name="description" class="form-control" required></textarea></td>
</tr>
<!-- Список выбора категории -->
<tr>
<td>Категория</td>
<td>` + categories_options_html + `</td>
</tr>
<!-- Кнопка отправки формы -->
<tr>
<td></td>
<td><button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-plus"></span> Создать товар</button></td>
</tr>
</table>
</form>`;
// Вставка html в «page-content» нашего приложения
$("#page-content").html(create_product_html);
// Изменяем тайтл
changePageTitle("Создание товара");
});
});
// Будет работать, если создана форма товара
$(document).on("submit", "#create-product-form", function () {
// Получение данных формы
let form_data = JSON.stringify($(this).serializeObject());
// Отправка данных формы в API
$.ajax({
url: "http://site.loc/api/product/create.php",
type: "POST",
contentType: "application/json",
data: form_data,
success: (result) => {
// Товар был создан, вернуться к списку товаров
showProducts();
},
error: (xhr, resp, text) => {
// Вывести ошибку в консоль
console.log(xhr, resp, text);
},
});
return false;
});
});
Обработчик кнопки «Удалить товар» app/product/delete-prod.js
app/product/delete-prod.jsjQuery(($) => {
// Будет работать, если была нажата кнопка удаления
$(document).on("click", ".delete-product-button", function () {
// Получение ID товара
const product_id = $(this).attr("data-id");
// Bootbox для подтверждения во всплывающем окне
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: (result) => {
if (result == true) {
// Отправим запрос на удаление в API / удаленный сервер
$.ajax({
url: "http://site.loc/api/product/delete.php",
type: "POST",
dataType: "json",
data: JSON.stringify({ id: product_id }),
success: (result) => {
// Покажем список всех товаров
showProducts();
},
error: (xhr, resp, text) => {
console.log(xhr, resp, text);
},
});
}
},
});
});
});
Показывает товары на странице при загрузки app/product/read-prod.js
Логика нашего тестового приложения позволяет выводить товары с пагинацией и без.
С пагинацией
app/product/read-prod.jsjQuery(($) => {
// Показать список товаров при первой загрузке
showProducts();
// Когда была нажата кнопка «Все товары»
$(document).on("click", ".read-products-button", function () {
showProducts();
});
// Когда была нажата кнопка пагинации
$(document).on("click", ".pagination li", function () {
// Получаем JSON URL
const json_url = $(this).find("a").attr("data-page");
// Покажем список товаров с пагинацией
showProducts(json_url);
});
});
// Функция для отображения списка товаров
function showProducts(json_url = "http://site.loc/api/product/read_paging.php") {
// Получаем список товаров из API
$.getJSON(json_url, function (data) {
// HTML для перечисления товаров
readProductsTemplate(data, "");
// Изменим заголовок страницы
changePageTitle("Все товары");
});
}
Без пагинации
app/product/read-prod.jsjQuery(($) => {
// Показать список товаров при первой загрузке
showProducts();
// При нажатии кнопки
$(document).on("click", ".read-products-button", () => {
showProducts();
});
});
// Функция для отображения списка товаров
function showProducts() {
// Получаем список товаров из API
$.getJSON("http://site.loc/api/product/read.php", (data) => {
// HTML для перечисления товаров
readProductsTemplate(data, "");
// Изменяем заголовок страницы
changePageTitle("Все товары");
});
}
// Функция для показа списка товаров
function showProducts() {
// Получить список товаров из API
$.getJSON("http://site.loc/api/product/read.php", (data) => {
// HTML для списка товаров
let read_products_html = `
<!-- При нажатии загружается форма создания товара -->
<div id="create-product" class="btn btn-primary pull-right m-b-15px create-product-button"><span class="glyphicon glyphicon-plus"></span> Создание товара</div>
<!-- Таблица товаров -->
<table class="table table-bordered table-hover">
<!-- Создание заголовков таблицы -->
<tr>
<th class="w-15-pct">Название</th>
<th class="w-10-pct">Цена</th>
<th class="w-15-pct">Категория</th>
<th class="w-25-pct text-align-center">Действие</th>
</tr>`;
// Перебор списка возвращаемых данных
$.each(data.records, function (key, val) {
// Создание новой строки таблицы для каждой записи
read_products_html +=`
<tr>
<td>`+ val.name +`</td>
<td>`+ val.price +`</td>
<td>`+ val.category_name +`</td>
<!-- Кнопки "действий" -->
<td>
<!-- Кнопка чтения товара -->
<button class="btn btn-primary m-r-10px read-one-product-button" data-id="`+ val.id +`">
<span class="glyphicon glyphicon-eye-open"></span> Просмотр
</button>
<!-- Кнопка редактирования -->
<button class="btn btn-info m-r-10px update-product-button" data-id="`+ val.id +`">
<span class="glyphicon glyphicon-edit"></span> Редактирование
</button>
<!-- Кнопка удаления товара -->
<button class="btn btn-danger delete-product-button" data-id="`+ val.id +`">
<span class="glyphicon glyphicon-remove"></span> Удаление
</button>
</td>
</tr>`;
});
read_products_html += `</table>`;
// Вставка в "page-content" нашего приложения
$("#page-content").html(read_products_html);
// Изменяем заголовок страницы
changePageTitle("Все товары");
});
}
Обработчик кнопки «Обновления товара» app/product/update-prod.js
app/product/update-prod.jsjQuery(($) => {
// Показывать HTML форму при нажатии кнопки «Обновить товар»
$(document).on("click", ".update-product-button", function () {
// Получаем ID товара
const id = $(this).attr("data-id");
// Читаем одну запись на основе данного идентификатора товара
$.getJSON("http://site.loc/api/product/read_one.php?id=" + id, (data) => {
// Значения будут использоваться для заполнения нашей формы
const name = data.name;
const price = data.price;
const description = data.description;
const category_id = data.category_id;
const category_name = data.category_name;
// Загрузка списка категорий
$.getJSON("http://site.loc/api/category/read.php", (data) => {
// Строим список выбора
// Перебор полученного списка данных
let categories_options_html = `<select name="category_id" class="form-control">`;
$.each(data.records, (key, val) => {
// Опция предварительного выбора - это идентификатор категории
if (val.id == category_id) {
categories_options_html += `<option value="` + val.id + `" selected>` + val.name + `</option>`;
} else {
categories_options_html += `<option value="` + val.id + `">` + val.name + `</option>`;
}
});
categories_options_html += `</select>`;
// Сохраним html в переменной «update product»
let update_product_html =`
<div id="read-products" class="btn btn-primary pull-right m-b-15px read-products-button"><span class="glyphicon glyphicon-list"></span> Все товары</div>
<!-- Построение формы для изменения товара -->
<!-- Мы используем свойство "required" html5 для предотвращения пустых полей -->
<form id="update-product-form" action="#" method="post" border="0">
<table class="table table-hover table-responsive table-bordered">
<tr>
<td>Название</td>
<td><input value=\"`+ name +`\" type="text" name="name" class="form-control" required /></td>
</tr>
<tr>
<td>Цена</td>
<td><input value=\"`+ price +`\" type="number" min="1" name="price" class="form-control" required /></td>
</tr>
<tr>
<td>Описание</td>
<td><textarea name="description" class="form-control" required>`+ description +`</textarea></td>
</tr>
<tr>
<td>Категория</td>
<td>` + categories_options_html +`</td>
</tr>
<tr>
<!-- Скрытый «идентификатор товара, чтобы определить, какую запись удалить -->
<td><input value=\"`+ id +`\" name="id" type="hidden" /></td>
<!-- Кнопка отправки формы -->
<td>
<button type="submit" class="btn btn-info">
<span class="glyphicon glyphicon-edit"></span> Обновить товар
</button>
</td>
</tr>
</table>
</form>`;
// Добавим в «page-content» нашего приложения
$("#page-content").html(update_product_html);
// Изменим title страницы
changePageTitle("Обновление товара");
});
});
});
// Будет запущен при отправке формы обновления товара
$(document).on("submit", "#update-product-form", function () {
// Получаем данные формы
const form_data = JSON.stringify($(this).serializeObject());
// Отправка данных формы в API
$.ajax({
url: "http://site.loc/api/product/update.php",
type: "POST",
contentType: "application/json",
data: form_data,
success: (result) => {
// Товар был успешно обновлён, возврат к списку товаров
showProducts();
},
error: (xhr, resp, text) => {
// Вывод ошибки в консоль
console.log(xhr, resp, text);
},
});
return false;
});
});
Обработчик кнопки «Создать товар» app/product/create-prod.js
app/product/create-prod.jsjQuery(($) => {
// Показать html форму при нажатии кнопки «создать товар»
$(document).on("click", ".create-product-button", () => {
// Загрузка списка категорий
$.getJSON("http://site.loc/api/category/read.php", (data) => {
// Перебор возвращаемого списка данных и создание списка выбора
let categories_options_html = `<select name="category_id" class="form-control">`;
$.each(data.records, (key, val) => {
categories_options_html += `<option value="` + val.id + `">` + val.name + `</option>`;
});
categories_options_html += `</select>`;
let create_product_html =`
<!-- Кнопка для показа всех товаров -->
<div id="read-products" class="btn btn-primary pull-right m-b-15px read-products-button"><span class="glyphicon glyphicon-list"></span> Все товары</div>
<!-- html форма «Создание товара» -->
<form id="create-product-form" action="#" method="post" border="0">
<table class="table table-hover table-responsive table-bordered">
<tr>
<td>Название</td>
<td><input type="text" name="name" class="form-control" required /></td>
</tr>
<tr>
<td>Цена</td>
<td><input type="number" min="1" name="price" class="form-control" required /></td>
</tr>
<tr>
<td>Описание</td>
<td><textarea name="description" class="form-control" required></textarea></td>
</tr>
<!-- Список выбора категории -->
<tr>
<td>Категория</td>
<td>`+ categories_options_html +`</td>
</tr>
<!-- Кнопка отправки формы -->
<tr>
<td></td>
<td><button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-plus"></span> Создать товар</button></td>
</tr>
</table>
</form>`;
// Вставка html в «page-content» нашего приложения
$("#page-content").html(create_product_html);
// Изменяем тайтл
changePageTitle("Создание товара");
});
});
// Будет работать, если создана форма товара
$(document).on("submit", "#create-product-form", function () {
// Получение данных формы
let form_data = JSON.stringify($(this).serializeObject());
// Отправка данных формы в API
$.ajax({
url: "http://site.loc/api/product/create.php",
type: "POST",
contentType: "application/json",
data: form_data,
success: (result) => {
// Товар был создан, вернуться к списку товаров
showProducts();
},
error: (xhr, resp, text) => {
// Вывести ошибку в консоль
console.log(xhr, resp, text);
},
});
return false;
});
});
Функции других компонентов app/product/create-prod.js
app/product/create-prod.js// HTML список товаров
function readProductsTemplate(data, keywords) {
let read_products_html =`
<!-- Форма для поиска товаров -->
<form id="search-product-form" action="#" method="post">
<div class="input-group pull-left w-30-pct"><input type="text" value="`+ keywords +`" name="keywords" class="form-control product-search-keywords" placeholder="Поиск товаров..." />
<span class="input-group-btn">
<button type="submit" class="btn btn-default" type="button">
<span class="glyphicon glyphicon-search"></span>
</button>
</span>
</div>
</form>
<!-- При нажатии загружается форма создания товара -->
<div id="create-product" class="btn btn-primary pull-right m-b-15px create-product-button">
<span class="glyphicon glyphicon-plus"></span> Создать товар
</div>
<!-- Начало таблицы -->
<table class="table table-bordered table-hover">
<!-- Создание заголовков колонок -->
<tr>
<th class="w-25-pct">Название</th>
<th class="w-10-pct">Цена</th>
<th class="w-10-pct">Категория</th>
<th class="w-25-pct text-align-center">Действие</th>
</tr>`;
// Перебор возвращаемого списка данных
$.each(data.records, (key, val) => {
// Создание новой строки таблицы для каждой записи
read_products_html +=`
<tr>
<td>`+ val.name +`</td>
<td>$`+ val.price +`</td>
<td>`+ val.category_name +`</td>
<!-- Кнопки "действий" -->
<td>
<!-- Кнопка для просмотра товара -->
<button class="btn btn-primary m-r-10px read-one-product-button" data-id="`+ val.id +`">
<span class="glyphicon glyphicon-eye-open"></span> Просмотр
</button>
<!-- Кнопка для изменения товара -->
<button class="btn btn-info m-r-10px update-product-button" data-id="`+ val.id +`">
<span class="glyphicon glyphicon-edit"></span> Редактировать
</button>
<!-- Кнопка для удаления товара -->
<button class="btn btn-danger delete-product-button" data-id="`+ val.id +`">
<span class="glyphicon glyphicon-remove"></span> Удалить
</button>
</td>
</tr>`;
});
// Конец таблицы
read_products_html += `</table>`;
// Если есть пагинация
if (data.paging) {
read_products_html += `<ul class="pagination pull-left margin-zero padding-bottom-2em">`;
// Первая страница
if ($.parseJSON(data.paging).first != "") {
read_products_html += `<li><a data-page="${$.parseJSON(data.paging).first}">Первая страница</a></li>`;
}
// Перебор страниц
$.each($.parseJSON(data.paging).pages, (key, val) => {
//debugger;
const active_page = val.current_page == "yes" ? "class='active'" : "";
read_products_html += `<li ${active_page}><a data-page="${val.url}">${val.page}</a></li>`;
});
// Последняя страница
if ($.parseJSON(data.paging).last != "") {
read_products_html += `<li><a data-page="${$.parseJSON(data.paging).last}">Последняя страница</a></li>`;
}
read_products_html += "</ul>";
}
// Добавим в «page-content» нашего приложения
$("#page-content").html(read_products_html);
}
Основные функции HTML и JavaScript app/app.js
app/app.jsjQuery(($) => {
// HTML приложения
let app_html = `
<div class="container">
<div class="page-header"><h1 id="page-title">Все товары</h1></div>
<!-- Здесь будет показано содержимое страницы -->
<div id="page-content"></div>
</div>`;
// Вставка кода на страницу
$("#app").html(app_html);
});
// Изменение заголовка страницы
function changePageTitle(page_title) {
// Изменение заголовка страницы
$("#page-title").text(page_title);
// Изменение заголовка вкладки браузера
document.title = page_title;
}
// Функция для создания значений формы в формате json
$.fn.serializeObject = function () {
var o = {};
var a = this.serializeArray();
$.each(a, function () {
if (o[this.name] !== undefined) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || "");
} else {
o[this.name] = this.value || "";
}
});
return o;
};