Урок 4: Оптимизация кода путем добавления классов и объектов

This tutorial needs a review. You can open a JIRA issue, or edit it in GitHub following these contribution guidelines.

В этом уроке рассматриваются способы оптимизации кода, позволяющие упростить поддержку этого кода в дальнейшем. Данная процедура затрагивает файлы createNewWisher.php и wishlist.php . Кроме того, создается новый файл под названием db.php .

Код приложения содержит несколько похожих блоков с запросами к базе данных. Для упрощения чтения и поддержки кода в будущем можно извлечь эти блоки, реализовать их в качестве функций отдельного класса WishDB и поместить текст WishDB в файл db.php . Впоследствии можно включить файл db.php в любой файл PHP и использовать любую функцию класса WishDB без дублирования кода. Такой подход гарантирует, что любые изменения в запросах или функциях будут выполнены в одном местоположении, и анализировать весь код приложения не потребуется.

При использовании функции класса WishDB не следует изменять значения каких-либо переменных в классе WishDB. Вместо этого необходимо использовать класс в качестве концептуального проекта для создания объекта WishDB и изменять значения переменных в этом объекте. Объект уничтожается при завершении работы. Поскольку значения непосредственно класса WishDB никогда не изменяются, данный класс можно использовать повторно неограниченное число раз. В некоторых случаях может потребоваться одновременно несколько экземпляров класса, а в других случаях будет предпочтителен "одноэкземплярный" класс, имеющий только один экземпляр в любой момент времени. WishDB в данном руководстве представлен как одноэкземплярный класс.

Следует отметить, что создание объекта класса обозначается термином "создание экземпляра" этого класса и что объект в данном случае называется "экземпляром" класса. Общий термин, обозначающий программирование с использованием классов и объектов, – "объектно-ориентированное программирование" (ООП). В PHP 5 используется сложная модель ООП. См. php.net для получения дополнительной информации.

В данном руководстве вы перемещаете функциональность вызова базы данных из отдельных файлов РНР в класс WishDB. Пользователи MySQL также заменяют процедурные вызовы mysqli объектно-ориентированными. Это соответствует новой объектно-ориентированной структуре приложения.

Текущий документ является частью краткого учебного курса "Создание приложения CRUD в IDE NetBeans для PHP".

Исходный код приложения из предыдущего урока

Для пользователей MySQL: перейдите по этой ссылке для загрузки исходного кода, описывающего состояние проекта на момент завершения предыдущего урока.

Для пользователей Oracle Database: щелкните здесь для загрузки исходного кода, отражающего состояние проекта по завершении предыдущего урока.

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

Создайте новую подпапку в папке "Исходные файлы". Дайте папке имя Includes. Создайте новый файл под именем db.php и поместите его в папку Includes. Позже вы сможете добавлять в эту папку файлы, которые будут включаться в другие РНР-файлы.

Чтобы создать файл db.php в новой папке, сделайте следующее:

  1. Щелкните правой кнопкой мыши узел "Source Files" и выберите "New > Folder" в контекстном меню. Откроется диалоговое окно "Новая папка"

  2. В поле "Имя папки" введите Includes. Затем нажмите кнопку "Готово".

  3. Щелкните правой кнопкой мыши узел "Includes" и выберите "New > PHP File" в контекстном меню. Откроется диалоговое окно "Новый файл РНР".

  4. В поле "Имя файла" введите db. Затем нажмите кнопку "Готово".

Создание класса WishDB

Для создания класса WishDB необходимо инициализировать переменные класса и реализовать конструктор класса. Пользователи MySQL, обратите внимание, что класс WishDB расширяет mysqli . Это означает, что WishDB наследует функции и другие характеристики класса PHP mysqli. Вы убедитесь в важности этого при добавлении функций mysqli к классу.

Откройте файл db.php и создайте класс WishDB. В данном классе объявите переменные настройки базы данных для хранения имени и пароля собственника базы данных (пользователя), имени и машины размещения базы данных. Все объявления переменных являются закрытыми: это означает, что начальные значения в этих объявлениях недоступны вне класса WishDB (см. php.net). Вы также объявляете закрытую _ статическую переменную_ $instance , которая хранит экземпляр WishDB. Ключевое слово "статический" означает, что функции в классе имеют доступ к переменной даже при отсутствии экземпляра класса.

Для базы данных MySQL:

[[source,php]

class WishDB extends mysqli {

    // single instance of self shared among all instances
    private static $instance = null;

    // db connection config vars
    private $user = "phpuser";
    private $pass = "phpuserpw";
    private $dbName = "wishlist";
    private $dbHost = "localhost";

}

Для базы данных Oracle:

class WishDB {

    // single instance of self shared among all instances
    private static $instance = null;

    // db connection config vars
    private $user = "phpuser";
    private $pass = "phpuserpw";
    private $dbName = "wishlist";
    private $dbHost = "localhost/XE";
    private $con = null;

}

Создание экземпляров класса WishDB

При использовании функций класса WishDB в других файлах PHP должна быть вызвана функция, позволяющая создать объект ("создать экземпляр") класса WishDB. WishDB разработан в качестве одноэкземплярного класса; это означает, что в любой определенный момент времени может существовать только один экземпляр класса. Поэтому рекомендуется предотвращать создание экземпляра WishDB, которое осуществляется извне и способствует появлению дублирующихся экземпляров.

Внутри класса WishDB введите или вставьте следующий код:

// This method must be static, and must return an instance of the object if the object
// does not already exist.

public static function getInstance() {

  if (!self::$instance instanceof self) {
    self::$instance = new self;
  }

  return self::$instance;
}

// The clone and wakeup methods prevents external instantiation of copies of the Singleton class,
// thus eliminating the possibility of duplicate objects.

public function __clone() {
  trigger_error('Clone is not allowed.', E_USER_ERROR);
}

public function __wakeup() {
  trigger_error('Deserializing is not allowed.', E_USER_ERROR);
}

Функция getInstance является общедоступной и статической. Общедоступность означает возможность свободного доступа извне класса. Статическая функция доступна даже в том случае, если для класса не было создано экземпляров. Поскольку функция getInstance вызывается для создания экземпляров класса, она является статической. Обратите внимание, что эта функция имеет доступ к статической переменной $instance и устанавливает ее значение как экземпляр класса.

Двойное двоеточие (::), или "оператор разрешения диапазона" (Scope Resolution Operator), и ключевое слово self используются для получения доступа к статическим функциям. Self в рамках определения класса используется в качестве ссылки на данный класс. Если двойное двоеточие находится вне определения класса, вместо self используется имя класса. См. ресурс php.net для получения информации об операции разрешения диапазона.

Добавление конструктора к классу WishDB

Класс может содержать в себе специальный метод, известный как "конструктор", который выполняется автоматически каждый раз при создании экземпляра этого класса. В данном руководстве рассматривается добавление к классу WishDB конструктора, который подключается к базе данных каждый раз при создании экземпляра WishDB.

Добавьте к WishDB следующий код:

Для базы данных MySQL

// private constructor
private function __construct() {

  parent::__construct($this->dbHost, $this->user, $this->pass, $this->dbName);

  if (mysqli_connect_error()) {
    exit('Connect Error (' . mysqli_connect_errno() . ') '. mysqli_connect_error());
  }

  parent::set_charset('utf-8');
}

Для базы данных Oracle

// private constructor
private function __construct() {

    $this->con = oci_connect($this->user, $this->pass, $this->dbHost);

    if (!$this->con) {
        $m = oci_error();
        echo $m['message'], "\n";
        exit;
    }
}

Следует учитывать, что вместо переменных $con , $dbHost , $user или $pass используется псевдопеременная $this . Псевдопеременная $this используется при вызове метода внутри контекста объекта. Она ссылается на значение переменной внутри этого объекта.

Функции класса WishDB

В этом уроке рассматривается реализация следующих функций класса WishDB:

  • get_wisher_id_by_name для извлечения идентификатора пользователя на основе имени

  • get_wishes_by_wisher_id для извлечения списка пожеланий "Wish list", принадлежащего определенному пользователю с соответствующим идентификатором

  • create_wisher для добавления нового пользователя в таблицу "Wishers".

Функция get_wisher_id_by_name

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

После функции WishDB введите или вставьте следующую функцию в класс WishDB:

Для базы данных MySQL

[[source,php]

public function get_wisher_id_by_name($name) {

  $name = $this->real_escape_string($name);
  $wisher = $this->query("SELECT id FROM wishers WHERE name = '" . $name . "'");

  if ($wisher->num_rows > 0){
    $row = $wisher->fetch_row();
    return $row[0];
  } else {
    return null;
  }
}

Для базы данных Oracle

public function get_wisher_id_by_name($name) {

    $query = "SELECT id FROM wishers WHERE name = :user_bv";
    $stid = oci_parse($this->con, $query);

    oci_bind_by_name($stid, ':user_bv', $name);
    oci_execute($stid);

    //Because user is a unique value I only expect one row
    $row = oci_fetch_array($stid, OCI_ASSOC);

    if ($row) {
      return $row["ID"];
    } else {
      return null;
    }
}

Блок кода выполняет запрос SELECT ID FROM wishers WHERE name = [переменная для имени пожелания] . Результат запроса - массив идентификаторов из записей, соответствующих запросу. Если массив не пустой, это по умолчанию означает, что он содержит один элемент, поскольку при создании таблицы имя поля было определено как UNIQUE. В этом случае функция возвращает первый элемент массива $result (элемент под номером ноль). Если массив пуст, функция возвращает значение "null".

Примечание к безопасности. Для базы данных MySQL строка $name используется с с escape-символом для предотвращения атак SQL-инъекций. См. статью энциклопедии Wikipedia о введении SQL +] и link:http://us3.php.net/mysql_real_escape_string[+документацию mysql_real_escape_string. Несмотря на то, что в контексте этого руководства риск возникновения опасных атак внедрения SQL маловероятен, рекомендуется исключить из участия в запросах MySQL такие строки, которые могли бы быть подвержены подобной атаке. База данных Oracle избегает данной проблемы, используя связанные переменные.

Функция get_wishes_by_wisher_id

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

Введите следующий блок кода:

Для базы данных MySQL

[[source,php]

public function get_wishes_by_wisher_id($wisherID) {
  return $this->query("SELECT id, description, due_date FROM wishes WHERE wisher_id=" . $wisherID);
}

Для базы данных Oracle

public function get_wishes_by_wisher_id($wisherID) {

  $query = "SELECT id, description, due_date FROM wishes WHERE wisher_id = :id_bv";
  $stid = oci_parse($this->con, $query);

  oci_bind_by_name($stid, ":id_bv", $wisherID);
  oci_execute($stid);

  return $stid;
}

Блок кода выполняет запрос "SELECT id, description, due_date FROM wishes WHERE wisherID=" . $wisherID и возвращает набор результатов, который является массивом записей, соответствующих запросу. (База данных Oracle использует связанную переменную для повышения производительности базы данных и уровня безопасности). Выделение выполняется с помощью wisherID, который является внешним ключом для таблицы wishes .

Примечание. Значение идентификатора не требуется до занятия 7.

Функция create_wisher

Функция создает новую запись в таблице "Wishers". Эта функция не возвращает каких-либо данных, и в качестве входных параметров для ее выполнения требуется имя и пароль нового пользователя.

Введите следующий блок кода:

Для базы данных MySQL

public function create_wisher ($name, $password) {

  $name = $this->real_escape_string($name);
  $password = $this->real_escape_string($password);

  return $this->query("INSERT INTO wishers (name, password) VALUES ('" . $name . "', '" . $password . "')");
}

Для базы данных Oracle

public function create_wisher($name, $password) {

  $query = "INSERT INTO wishers (name, password) VALUES (:user_bv, :pwd_bv)";
  $stid = oci_parse($this->con, $query);

  oci_bind_by_name($stid, ':user_bv', $name);
  oci_bind_by_name($stid, ':pwd_bv', $password);
  oci_execute($stid);

  return $stid;
}

Блок кода выполняет запрос "INSERT wishers (Name, Password) VALUES ([переменные представляющие имя и пароль нового пожелания]) . При выполнении запроса добавляется новая запись в таблицу "Wishers" с полями "name" и "password", заполненными значениями $name и $password соответственно.

Реорганизация кода приложения

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

Реорганизация файла wishlist.php

Начнем с файла wishlist.php, поскольку он небольшой и дает возможность представить оптимизацию более иллюстративно.

  1. В верхней части блока <? php? > введите следующую строку, делающую возможным использование файла db.php :

require_once("Includes/db.php");
  1. Замените код, который подключается к базе данных и получает идентификатор пожелания, вызовом функции get_wisher_id_by_name .

Для базы данных MySQL вы заменяете следующий код:

// to remove

 $con = mysqli_connect("localhost", "phpuser", "phpuserpw");
if (!$con) {
  exit('Connect Error (' . mysqli_connect_errno() . ') '
          . mysqli_connect_error());
}
//set the default client character set
mysqli_set_charset($con, 'utf-8');

mysqli_select_db($con, "wishlist");
$user = mysqli_real_escape_string($con, $_GET['user']);
$wisher = mysqli_query($con, "SELECT id FROM wishers WHERE name='" . $user . "'");
if (mysqli_num_rows($wisher) < 1) {
  exit("The person " . $_GET['user'] . " is not found. Please check the spelling and try again");
}
$row = mysqli_fetch_row($wisher);
$wisherID = $row[0];
mysqli_free_result($wisher);

// to replace

$wisherID = WishDB::getInstance()->get_wisher_id_by_name($_GET["user"]);

if (!$wisherID) {
  exit("The person " .$_GET["user"]. " is not found. Please check the spelling and try again" );
}

Для *базы данных Oracle * вы заменяете следующий код:

// to remove

$con = oci_connect("phpuser", "phpuserpw", "localhost/XE");
if (!$con) {
  $m = oci_error();
  echo $m['message'], "\n";
  exit;
}
$query = "SELECT ID FROM wishers WHERE name = :user_bv";
$stid = oci_parse($con, $query);
$user = $_GET['user'];

oci_bind_by_name($stid, ':user_bv', $user);
oci_execute($stid);

//Because user is a unique value I only expect one row
$row = oci_fetch_array($stid, OCI_ASSOC);
if (!$row) {
  echo("The person " . $user . " is not found. Please check the spelling and try again" );
  exit;
}
$wisherID = $row['ID'];

// to replace

$wisherID = WishDB::getInstance()->get_wisher_id_by_name($_GET["user"]);

if (!$wisherID) {
  exit("The person " .$_GET["user"]. " is not found. Please check the spelling and try again" );
}

Новый код сначала вызывает функцию getInstance в WishDB. Функция getInstance возвращает экземпляр WishDB, а код вызывает функцию get_wisher_id_by_name в пределах данного экземпляра. Если требуемое пожелание в базе данных не найдено, код завершает процесс и отображает сообщение об ошибке.

Для открытия подключения к базе данных наличие кода не является необходимым. Открытие подключения выполняется конструктором класса WishDB. Если имя и/или пароль изменяются, необходимо обновить только соответствующие переменные класса WishDB.

  1. Замените код, который получает пожелания для автора пожеланий, идентифицированного с помощью кода, кодом, который вызывает функцию get_wishes_by_wisher_id .

Для *базы данных MySQL * вы заменяете следующий код:

// to remove

$result = mysqli_query($con, "SELECT description, due_date FROM wishes WHERE wisher_id=" . $wisherID);

// to replace

$result = WishDB::getInstance()->get_wishes_by_wisher_id($wisherID);

Для *базы данных Oracle * вы заменяете следующий код:

// to remove

$query = "SELECT description, due_date FROM wishes WHERE wisher_id = :id_bv";
$stid = oci_parse($con, $query);
oci_bind_by_name($stid, ":id_bv", $wisherID);
oci_execute($stid);

// to replace

$stid = WishDB::getInstance()->get_wishes_by_wisher_id($wisherID);
  1. Удалите строку, которая закрывает подключение к базе данных.

// For MYSQL database
mysqli_close($con);

// For Oracle database
oci_close($con);

Код не нужен, потому что подключение к базе данных автоматически закрывается при уничтожении объекта WishDB. Однако рекомендуем сохранять код, освобождающий ресурс. Вам необходимо освободить все ресурсы, которые используют подключение, чтобы убедиться в том, что оно закрыто, даже при вызове функции close или уничтожении экземпляра с подключением к базе данных.

Реорганизация файла createNewWisher.php

Реорганизация не оказывает воздействия на форму ввода HTML или код для вывода на экран соответствующих сообщений об ошибках.

  1. В верхней части блока <? php? > введите следующий код, делающий возможным использование файла db.php :

require_once("Includes/db.php");
  1. Удалите подтверждения подключения к базе данных ( $dbHost и пр.). Теперь они находятся в db.php .

  2. Замените код, который подключается к базе данных и получает идентификатор пожелания, вызовом функции get_wisher_id_by_name .

Для *базы данных MySQL * вы заменяете следующий код:

// to remove

$con = mysqli_connect("localhost", "phpuser", "phpuserpw");
if (!$con) {
  exit('Connect Error (' . mysqli_connect_errno() . ') '
          . mysqli_connect_error());
}
//set the default client character set
mysqli_set_charset($con, 'utf-8');

/** Check whether a user whose name matches the "user" field already exists */
mysqli_select_db($con, "wishlist");
$user = mysqli_real_escape_string($con, $_POST['user']);
$wisher = mysqli_query($con, "SELECT id FROM wishers WHERE name='".$user."'");
$wisherIDnum=mysqli_num_rows($wisher);
if ($wisherIDnum) {
  $userNameIsUnique = false;
}

// to replace

$wisherID = WishDB::getInstance()->get_wisher_id_by_name($_POST["user"]);

if ($wisherID) {
  $userNameIsUnique = false;
}

Для *базы данных Oracle * вы заменяете следующий код:

// to remove

$con = oci_connect("phpuser", "phpuserpw", "localhost/XE", "AL32UTF8");
if (!$con) {
  $m = oci_error();
  exit('Connect Error ' . $m['message']);
}
$query = "SELECT id FROM wishers WHERE name = :user_bv";
$stid = oci_parse($con, $query);
$user = $_POST['user'];

oci_bind_by_name($stid, ':user_bv', $user);
oci_execute($stid);

//Each user name should be unique. Check if the submitted user already exists.
$row = oci_fetch_array($stid, OCI_ASSOC);
if ($row) {
  $userNameIsUnique = false;
}

// to replace

$wisherID = WishDB::getInstance()->get_wisher_id_by_name($_POST["user"]);
if ($wisherID) {
  $userNameIsUnique = false;
}

Объект WishDB существует до тех пор, пока обрабатывается текущая страница. Если обработка завершена или прервана, этот объект уничтожается. Код для открытия подключения к базе данных не является необходимым, поскольку подключение выполняется посредством функции WishDB. Код для закрытия подключения также не является необходимым, поскольку подключение будет закрыто сразу же после уничтожения объекта WishDB .

  1. Замените код, который вставляет новых авторов пожеланий в базу данных, кодом, который вызывает функцию create_wisher .

Для *базы данных MySQL * вы заменяете следующий код:

// to remove

if (!$userIsEmpty && $userNameIsUnique && !$passwordIsEmpty && !$password2IsEmpty && $passwordIsValid) {
  $password = mysqli_real_escape_string($con, $_POST['password']);
  mysqli_select_db($con, "wishlist");
  mysqli_query($con, "INSERT wishers (name, password) VALUES ('" . $user . "', '" . $password . "')");
  mysqli_free_result($wisher);
  mysqli_close($con);
  header('Location: editWishList.php');
  exit;
}

// to replace

if (!$userIsEmpty && $userNameIsUnique && !$passwordIsEmpty && !$password2IsEmpty && $passwordIsValid) {

  WishDB::getInstance()->create_wisher($_POST["user"], $_POST["password"]);

  header('Location: editWishList.php' );
  exit;
}

Для *базы данных Oracle * вы заменяете следующий код:

// to remove

if (!$userIsEmpty && $userNameIsUnique && !$passwordIsEmpty && !$password2IsEmpty && $passwordIsValid) {

  $query = "INSERT INTO wishers (name, password) VALUES (:user_bv, :pwd_bv)";
  $stid = oci_parse($con, $query);
  $pwd = $_POST['password'];
  oci_bind_by_name($stid, ':user_bv', $user);
  oci_bind_by_name($stid, ':pwd_bv', $pwd);
  oci_execute($stid);
  oci_free_statement($stid);
  oci_close($con);
  header('Location: editWishList.php');
  exit;
}

// to replace

if (!$userIsEmpty && $userNameIsUnique && !$passwordIsEmpty && !$password2IsEmpty && $passwordIsValid) {

  WishDB::getInstance()->create_wisher($_POST["user"], $_POST["password"]);

  header('Location: editWishList.php' );
  exit;
}

Исходный код приложения на момент завершения текущего урока

Для пользователей MySQL: щелкните сюда для загрузки исходного кода, отражающего состояние проекта по завершении данного урока.

Для пользователей Oracle Database: щелкните здесь для загрузки исходного кода, отражающего состояние проекта по завершении данного урока.

Что дальше?

Полезные ссылки

Дополнительная информация об использовании классов в PHP:

Дополнительная информация о реорганизации кода PHP:

Для отправки комментариев и предложений, получения поддержки и новостей о последних разработках, связанных с PHP IDE NetBeans присоединяйтесь к списку рассылки users@php.netbeans.org.