Строим свою CMS на PHP и MySQL. Часть 2
В предыдущем уроке серии мы создали базу данных и конфигурационный файл для нашей простой CMS. Теперь создадим основной класс нашего приложения - Article.
В нашей CMS Article
будет единственным классом PHP. Он будет обслуживать задачи сохранения статьи в базе данных и получения материалов для вывода на страницах проекта. Как только мы построим данный класс будет действительно легко создать другие скрипты для создания, обновления, вывода и удаления статей.
В нашей папке cms
создаем каталог classes
. В папке classes
создаем новый файл с именем Article.php
и копируем в него следующий код:
<?php /** * Класс для обработки статей */ class Article { // Свойства /** * @var int ID статей из базы данных */ public $id = null; /** * @var int Дата первой публикации статьи */ public $publicationDate = null; /** * @var string Полное название статьи */ public $title = null; /** * @var string Краткое описание статьи */ public $summary = null; /** * @var string HTML содержание статьи */ public $content = null; /** * Устанавливаем свойства с помощью значений в заданном массиве * * @param assoc Значения свойств */ public function __construct( $data=array() ) { if ( isset( $data['id'] ) ) $this->id = (int) $data['id']; if ( isset( $data['publicationDate'] ) ) $this->publicationDate = (int) $data['publicationDate']; if ( isset( $data['title'] ) ) $this->title = preg_replace ( "/[^\.\,\-\_\'\"\@\?\!\:\$ a-zA-Z0-9()]/", "", $data['title'] ); if ( isset( $data['summary'] ) ) $this->summary = preg_replace ( "/[^\.\,\-\_\'\"\@\?\!\:\$ a-zA-Z0-9()]/", "", $data['summary'] ); if ( isset( $data['content'] ) ) $this->content = $data['content']; } /** * Устанавливаем свойств с помощью значений формы редактирования записи в заданном массиве * * @param assoc Значения записи формы */ public function storeFormValues ( $params ) { // Сохраняем все параметры $this->__construct( $params ); // Разбираем и сохраняем дату публикации if ( isset($params['publicationDate']) ) { $publicationDate = explode ( '-', $params['publicationDate'] ); if ( count($publicationDate) == 3 ) { list ( $y, $m, $d ) = $publicationDate; $this->publicationDate = mktime ( 0, 0, 0, $m, $d, $y ); } } } /** * Возвращаем объект статьи соответствующий заданному ID статьи * * @param int ID статьи * @return Article|false Объект статьи или false, если запись не найдена или возникли проблемы */ public static function getById( $id ) { $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD ); $sql = "SELECT *, UNIX_TIMESTAMP(publicationDate) AS publicationDate FROM articles WHERE id = :id"; $st = $conn->prepare( $sql ); $st->bindValue( ":id", $id, PDO::PARAM_INT ); $st->execute(); $row = $st->fetch(); $conn = null; if ( $row ) return new Article( $row ); } /** * Возвращает все (или диапазон) объектов статей в базе данных * * @param int Optional Количество строк (по умолчанию все) * @param string Optional Столбец по которому производится сортировка статей (по умолчанию "publicationDate DESC") * @return Array|false Двух элементный массив: results => массив, список объектов статей; totalRows => общее количество статей */ public static function getList( $numRows=1000000, $order="publicationDate DESC" ) { $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD ); $sql = "SELECT SQL_CALC_FOUND_ROWS *, UNIX_TIMESTAMP(publicationDate) AS publicationDate FROM articles ORDER BY " . mysql_escape_string($order) . " LIMIT :numRows"; $st = $conn->prepare( $sql ); $st->bindValue( ":numRows", $numRows, PDO::PARAM_INT ); $st->execute(); $list = array(); while ( $row = $st->fetch() ) { $article = new Article( $row ); $list[] = $article; } // Получаем общее количество статей, которые соответствуют критерию $sql = "SELECT FOUND_ROWS() AS totalRows"; $totalRows = $conn->query( $sql )->fetch(); $conn = null; return ( array ( "results" => $list, "totalRows" => $totalRows[0] ) ); } /** * Вставляем текущий объект статьи в базу данных, устанавливаем его свойства. */ public function insert() { // Есть у объекта статьи ID? if ( !is_null( $this->id ) ) trigger_error ( "Article::insert(): Attempt to insert an Article object that already has its ID property set (to $this->id).", E_USER_ERROR ); // Вставляем статью $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD ); $sql = "INSERT INTO articles ( publicationDate, title, summary, content ) VALUES ( FROM_UNIXTIME(:publicationDate), :title, :summary, :content )"; $st = $conn->prepare ( $sql ); $st->bindValue( ":publicationDate", $this->publicationDate, PDO::PARAM_INT ); $st->bindValue( ":title", $this->title, PDO::PARAM_STR ); $st->bindValue( ":summary", $this->summary, PDO::PARAM_STR ); $st->bindValue( ":content", $this->content, PDO::PARAM_STR ); $st->execute(); $this->id = $conn->lastInsertId(); $conn = null; } /** * Обновляем текущий объект статьи в базе данных */ public function update() { // Есть ли у объекта статьи ID? if ( is_null( $this->id ) ) trigger_error ( "Article::update(): Attempt to update an Article object that does not have its ID property set.", E_USER_ERROR ); // Обновляем статью $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD ); $sql = "UPDATE articles SET publicationDate=FROM_UNIXTIME(:publicationDate), title=:title, summary=:summary, content=:content WHERE id = :id"; $st = $conn->prepare ( $sql ); $st->bindValue( ":publicationDate", $this->publicationDate, PDO::PARAM_INT ); $st->bindValue( ":title", $this->title, PDO::PARAM_STR ); $st->bindValue( ":summary", $this->summary, PDO::PARAM_STR ); $st->bindValue( ":content", $this->content, PDO::PARAM_STR ); $st->bindValue( ":id", $this->id, PDO::PARAM_INT ); $st->execute(); $conn = null; } /** * Удаляем текущий объект статьи из базы данных */ public function delete() { // Есть ли у объекта статьи ID? if ( is_null( $this->id ) ) trigger_error ( "Article::delete(): Attempt to delete an Article object that does not have its ID property set.", E_USER_ERROR ); // Удаляем статью $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD ); $st = $conn->prepare ( "DELETE FROM articles WHERE id = :id LIMIT 1" ); $st->bindValue( ":id", $this->id, PDO::PARAM_INT ); $st->execute(); $conn = null; } } ?>
Файл получается достаточно длинным, но код очень простой. Разберем его подробно:
1. Определение класса и его свойства
Сначала определим класс Article:
Все, что следует за данным строками до закрывающей фигурной скобки в конце файла содержит код нашего класса Article
.
После определения класса мы объявляем свойства класса : $id
, $publicationDate
и так далее. Каждый объект Article
, который мы создаем, будет хранить данные в объявленных свойствах. Обратите внимание, что имена свойств соответствуют именам полей в таблице articles
.
Технически, такой тип класса, который содержит свойства соответствующие непосредственно полям базы данных и методы для хранения и получения записей, соответствует шаблону объектно-ориентированного проектирования, известному как active record.
2. Конструктор
Затем мы создаем методы класса. Это функции, которые привязаны к классу и к объекту, создаваемому из класса. Наш основной код вызывает методы для манипулирования данными в объекте Article
.
Первый метод, __construct()
, является конструктором. Это специальный метод, который автоматически вызывается системой PHP каждый раз, когда создается новый объект Article
. Наш конструктор получает необязательный массив $data
, в котором содержатся данные для свойств нового объекта. Затем мы присваиваем данные свойствам в теле конструктора. Таким образом, получается удобный способ для создания и инициализации объекта в одно действие.
$this->propertyName
означает: "Свойство объекта this
с именем "$propertyName
".
Обратите внимание, что метод фильтрует данные, прежде чем присвоить их свойствам. Свойства id
и publicationDate
приводятся к типу int
с помощью (int)
, так данные значения должны быть типа int
. Свойства title
и summary
фильтруются с помощью регулярных выражений, так как в них допускает наличие символов из определенного набора. С точки зрения безопасности фильтрация данных ввода - отличная практика. Пропускаем только допустимые значения и символы.
Однако, мы не фильтруем свойство content
. Почему? Вероятно, администратор захочет использовать более широкий диапазон символов в содержании статьи - например, разметку HTML. Если мы ограничим диапазон доступных символов в содержании, то снизим полезность нашей системы для администратора.
Обычно, такие места могут оказаться дырой в системе безопасности, так как пользователь может вставить вредный код JavaScript или материалы с ошибками в содержание статьи. Однако, так как мы полагаем. что единственной персоной, которой доступно редактирование содержание, будет администратор системы, располагающий безграничным доверием, то вопрос с уязвимостью содержания остается за рамками нашего внимания. Если вы имеете дело с генерируем пользователями содержанием, например, комментариями или записями форума, то следует быть более осторожным и допускать только "безопасный" код HTML к использованию. Отличным инструментом для решения таких задач является HTML Purifier, который анализирует ввод кода HTML и удаляет все потенциальные угрозы.
Безопасность кода PHP выходит за рамки наших уроков. Вам следует посвятить определенное время для изучения данного вопроса.
3. storeFormValues()
Следующий метод storeFormValues()
похож на конструктор в том, что он сохраняет полученный массив данных в свойствах объекта. Основное отличие заключается в том, что storeFormValues()
может обрабатывать данные в формате, который используется в формах New Article (Новая статья) и Edit Article (Редактировать статью) (мы создадим их позже). В частности, он может обрабатывать дату публикации в формате YYYY-MM-DD
, конвертировать ее в формат времени UNIX, который отлично подходит для хранения в объекте.
Формат времени UNIX представляет собой целое значение, которое содержит количество секунд от полуночи 1 января 1970 до искомой даты. Датой в таком формате легко оперировать, и ее удобно хранить.
Назначение данного метода - облегчить реализацию скрипта для хранения дат, вводимых в формах.
Все члены (то есть свойства и методы) нашего класса Article
имеют ключевое слово public
перед определением, что означает доступность кода вне класса. Также можно создавать частные члены (директива private
) (их можно использовать только в классе) и защищенные члены (директива protected
) (которые можно использовать в классе и его подклассах).
4. getById()
Теперь перейдем к методам, реализующим доступ к базе данных MySQL. Первый из них - getById()
. Он принимает в качестве аргумента ID статьи ($id
) и возвращает запись с указанным ID из таблицы articles
, сохраняя данные в новом объекте Article
.
Обычно, когда вы вызываете метод, сначала нужно создать объект, а затем вызвать метод, принадлежащий объекту. Но, так как getById()
возвращает новый объект Article
, будет полезно вызывать его напрямую, а не через существующий объект. Иначе придется создавать новый объект-заглушку каждый раз, когда нужно вызвать вызвать метод и получить статью.
Для разрешения вызова метода без объекта мы добавляем декларацию static
к определению метода. Таким образом разрешается вызов метода непосредственно без определения объекта.
public static function getById( $id ) {
Метод использует PDO для соединения с базой данных, получает запись статьи с помощью запроса SQL SELECT
и сохраняет данные в новом объекте Article
, который возвращается в вызывающий код. PDO — PHP Data Objects —объектно-ориентированная библиотека, встроенная в PHP, которая облегчает связь скриптов PHP с базами данных.
Разберем метод подробнее:
-
Соединение с базой данных
$conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD );
Здесь выполняется соединение с базой данных MySQL с помощью данных из файла
config.php
. Дескриптор соединения сохраняется в переменной$conn
. Данный дескриптор используется в остальном коде для обмена данных с базой. -
Получаем запись статьи
$sql = "SELECT *, UNIX_TIMESTAMP(publicationDate) AS publicationDate FROM articles WHERE id = :id"; $st = $conn->prepare( $sql ); $st->bindValue( ":id", $id, PDO::PARAM_INT ); $st->execute(); $row = $st->fetch();
Выражение
SELECT
возвращает все поля (*
) из записи в таблицеarticles
, которые соответствуют заданному полюid
. Значение поляpublicationDate
возвращается в формате времени UNIX, вместо формата для дат MySQL, что упрощает процесс сохранения в нашем объекте.Вместо того, чтобы помещать наш параметр
$id
непосредственно в строкуSELECT
, что увеличивает риск нарушения системы безопасности, мы используем:id
. Такой параметр известен как placeholder (указатель места размещения). Далее мы вызываем метод PDO для привязывания значение$id
к указателю места размещения.Сразу после сохранения выражения
SELECT
в строке, мы подготавливаем его с помощью функции$conn->prepare()
, сохраняя полученный дескриптор в переменной$st
.Подготовка выражения используется для работы со многими базами данных. Она позволяет выполнять запросы быстрее и безопаснее.
Затем мы привязываем значение переменной
$id
( ID нужной статьи) к указателю места размещения:id
с помощью вызова методаbindValue()
.И вызываем метод
execute()
для выполнения запроса. После чего используем методfetch()
для перемещения полученной записи в ассоциированный массив с именами полей и соответствующими значениями, который хранится в переменной$row
. -
Закрываем соединение
Так ка нам больше не нужно соединение, мы закрываем его, присваивая значение
null
переменной$conn
. Закрывать соединение с базой данных как можно быстрее является хорошей практикой для освобождения памяти на сервере. -
Возвращаем объект
Article
if ( $row ) return new Article( $row ); }
Последним действием в нашем методе является создание объекта
Article
, который будет содержать запись из базы данных, и возвращение его вызывающему коду. Сначала проверяем наличие данных в переменной$row
после вызова методаfetch()
. Если данные есть, создаем новый объектArticle
передавая переменную ему$row
. Будет вызван конструктор класса, который наполнит объект данными из массива$row
. Затем возвращаем готовый объект и работа метода завершена.
5. getList()
Следующий метод getList()
во многом похож на метод getById()
. Основное отличие заключается в том, что метод getList()
возвращает несколько статей сразу. Его используют, когда нужно вывести список статей для пользователя или администратора.
getList()
принимает 2 аргумента:
$numRows
- Максимальное количество получаемых статей. По умолчанию установлено значение 1,000,000 (то есть, практически все статьи). Данный параметр позволяет нам получать только первые 5 статей для главной страницы.
$order
- Порядок сортировки получаемых статей. По умолчанию используется параметр
"publicationDate DESC"
, который означает "сортировка по дате публикации, новые статьи первые".
Большая часть кода метода похожа на код метода getById()
. Посмотрим на несколько строк:
$sql = "SELECT SQL_CALC_FOUND_ROWS *, UNIX_TIMESTAMP(publicationDate) AS publicationDate FROM articles ORDER BY " . mysql_escape_string($order) . " LIMIT :numRows";
Здесь запрос немного сложнее. Обратите внимание, что здесь не используется выражение WHERE
, так как мы хотим получить все статьи, а не одну с заданным ID.
Также добавлено выражение ORDER BY
для сортировки возвращаемых записей в определенном порядке. Используется выражение LIMIT
с параметром $numRows
(как указатель места размещения) для ограничения количества получаемых записей.
Специальное значение MySQL SQL_CALC_FOUND_ROWS
указывает базе данных, что нужно вернуть действительное количество возвращаемых записей. Такая информация полезна для информирования пользователя и организации других функций, например, постраничного вывода списка.
Вместо передачи значения переменной $order
в запрос через указатель места размещения, мы передаем его прямо в строку запроса, вызывая функцию mysql_escape_string()
, чтобы отбросить любые специальные символы (для безопасности). Если использовать указатель места размещения, то PDO поместит кавычки ('
) вокруг строки (например, ORDER BY 'publicationDate DESC'
), что является ошибкой синтаксиса.
$list = array(); while ( $row = $st->fetch() ) { $article = new Article( $row ); $list[] = $article; }
Так как мы возвращаем несколько строк, нужно создать массив $list
для размещения соответствующих объектов Article
. Затем используем цикл while
для получения следующей строки через fetch()
, создаем новый объект Article
, сохраняем строку в объекте и добавляем объект к массиву $list
. Когда строк не останется, метод fetch()
вернет false
, и цикл остановится.
// Теперь получаем общее число статей, которые соответствуют критерию $sql = "SELECT FOUND_ROWS() AS totalRows"; $totalRows = $conn->query( $sql )->fetch(); $conn = null; return ( array ( "results" => $list, "totalRows" => $totalRows[0] ) );
В завершении мы выполняем запрос, который использует функцию MySQL FOUND_ROWS()
для получения количества возвращаемых строк, вычисленного в предыдущей команде SQL_CALC_FOUND_ROWS
. В этот раз используем метод PDO query()
, который позволяет быстро выполнить запрос, если нет указателей места замещения. Мы вызываем метод fetch()
для получения результата. Затем возвращаем оба значения - список объектов Article
($list
) и общее количество строк - как ассоциированный массив.
6. insert()
Оставшиеся методы в нашем классе Article
работают с добавлением, изменением и удалением записей статей в базе данных.
insert()
добавляет новую статью в таблицу articles
, используя значения из текущего объекта Article
:
- Сначала метод проверяет, что объект не имеет установленного свойства
$id
. Если у объекта есть ID, то, вероятно, статья уже имеется в базе данных и ее добавлять не нужно. - Затем метод выполняет запрос SQL
INSERT
для вставки записи в таблицуarticles
, используя указатели места замещения для передачи значений свойств в базу данных. Обратите внимание, что мы используем функцию MySQLFROM_UNIXTIME()
для конвертации даты публикации в формат MySQL. - После выполнения запроса, метод возвращает ID новой статьи с помощью функции PDO
lastInsertId()
и сохраняет значение в свойстве$id
. Мы установили в таблицеarticles
для поляid
свойствоauto_increment
, поэтому MySQL генерирует уникальное значение ID для каждой новой записи.
Обратите внимание, что мы используем PDO::PARAM_INT
при привязке целых значений к указателям места замещения, и PDO::PARAM_STR
при привязке строк. Таким образом, PDO может правильно обрабатывать значения.
7. update ()
Данный метод похож на метод insert()
, за исключением того, что здесь происходит обновление записи в базе данных вместо создания новой записи.
Сначала проверяем наличие ID у объекта, так как обновить можно только запись с известным ID. Затем используем выражение SQL UPDATE
для обновления полей записи. Обратите внимание на передачу ID объекта в выражение UPDATE
, так как мы знаем, какую запись надо обновить.
8. delete ()
Метод delete()
использует выражение SQL DELETE
для удаления из таблицы articles
статьи, которая хранится в в объекте. Для идентификации записи задействуем свойство $id
объекта. Для безопасности мы добавили LIMIT 1
к запросу, чтобы ограничиться удалением только одной записи.
В следующем уроке мы созадаим скрипты для клиентской и серверной части нашего приложения.
Источник: http://feedproxy.google.com/~r/ruseller/CdHX/~3/aTByynLrhoY/lessons.php
Дайджест новых статей по интернет-маркетингу на ваш email
Новые статьи и публикации
- 2024-11-26 » Капитан грузового судна, или Как начать использовать Docker в своих проектах
- 2024-11-26 » Обеспечение безопасности ваших веб-приложений с помощью PHP OOP и PDO
- 2024-11-22 » Ошибки в Яндекс Вебмастере: как найти и исправить
- 2024-11-22 » Ошибки в Яндекс Вебмастере: как найти и исправить
- 2024-11-15 » Перенос сайта на WordPress с одного домена на другой
- 2024-11-08 » OSPanel 6: быстрый старт
- 2024-11-08 » Как установить PhpMyAdmin в Open Server Panel
- 2024-09-30 » Как быстро запустить Laravel на Windows
- 2024-09-25 » Next.js
- 2024-09-05 » OpenAI рассказал, как запретить ChatGPT использовать содержимое сайта для обучения
- 2024-08-28 » Чек-лист: как увеличить конверсию интернет-магазина на примере спортпита
- 2024-08-01 » WebSocket
- 2024-07-26 » Интеграция с Яндекс Еда
- 2024-07-26 » Интеграция с Эквайринг
- 2024-07-26 » Интеграция с СДЕК
- 2024-07-26 » Интеграция с Битрикс-24
- 2024-07-26 » Интеграция с Travelline
- 2024-07-26 » Интеграция с Iiko
- 2024-07-26 » Интеграция с Delivery Club
- 2024-07-26 » Интеграция с CRM
- 2024-07-26 » Интеграция с 1C-Бухгалтерия
- 2024-07-24 » Что такое сторителлинг: техники и примеры
- 2024-07-17 » Ошибка 404: что это такое и как ее использовать для бизнеса
- 2024-07-03 » Размещайте прайс-листы на FarPost.ru и продавайте товары быстро и выгодно
- 2024-07-01 » Профилирование кода в PHP
- 2024-06-28 » Изучаем ABC/XYZ-анализ: что это такое и какие решения с помощью него принимают
- 2024-06-17 » Зачем вам знать потребности клиента
- 2024-06-11 » Что нового в работе Яндекс Метрики: полный обзор обновления
- 2024-06-11 » Поведенческие факторы ранжирования в Яндексе
- 2024-06-11 » Скорость загрузки сайта: почему это важно и как влияет на ранжирование
Великие умы обсуждают идеи, средние - обсуждают поступки, а малые - людей Индийская пословица |
Мы создаем сайты, которые работают! Профессионально обслуживаем и продвигаем их , а также по всей России и ближнему зарубежью с 2006 года!
Как мы работаем
Заявка
Позвоните или оставьте заявку на сайте.
Консультация
Обсуждаем что именно Вам нужно и помогаем определить как это лучше сделать!
Договор
Заключаем договор на оказание услуг, в котором прописаны условия и обязанности обеих сторон.
Выполнение работ
Непосредственно оказание требующихся услуг и работ по вашему заданию.
Поддержка
Сдача выполненых работ, последующие корректировки и поддержка при необходимости.