Строим свою 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:

class Article
{

Все, что следует за данным строками до закрывающей фигурной скобки в конце файла содержит код нашего класса Article.

Технически, такой тип класса, который содержит свойства соответствующие непосредственно полям базы данных и методы для хранения и получения записей, соответствует шаблону объектно-ориентированного проектирования, известному как 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();

Добавить комментарий