Динамический SELECT
Почему именно через PDO.
В одной хорошей книге был дан совет программировать на уровне интерфейса, а не на уровне реализации. Значит нужно придерживаться объектно-ориентированных приемов написания собственного кода. А это значит, что использование PDO в наших PHP-сценариях для соединения с СУБД
Конечно же, скорее всего существует немалое число случаев, когда стоит просто воспользоваться функциями mysqli_, но сейчас мы поговорим не об этом.
Как уже описывалось в моей предыдущей статье для создания объекта подключения нам достаточно составить строку с параметрами подключения и используемым драйвером — драйвер
Подключаемся к MySQL через PDO.
Строка подключения может выглядеть так:
<?php
$dbh = new PDO( 'mysql:host=localhost;dbname=demo_pdo-mysql;charset=utf8;', 'demo', 'sadhiJ7slv!a' );
?>
Создаем собственный класс для подключения к СУБД
Лучше помещать любые обращения к СУБД в блоки try - catch, чтобы избегать прерывания выполнения кода сценария в случае возникновения ошибки, перехватывая исключения. Но вернемся к этому позже.
PDO — это класс. Значит мы можем унаследовать от него все доступные свойства и методы, чтобы использовать их в своих классах. Напишем свой собственный класс для подключения к СУБД
Подключение к Базе данных может выглядеть так.
<?php
// Подключаемся к СУБД MySQL через вызов конструктора собственного класса
// Этот класс наследует свойства и методы от класса PDO.
$dbh = new DB();
?>
А может выглядеть так:
<?php
$params = array(
'driver' => 'mysql',
'host' => 'localhost',
'dbname' => 'demo_pdo-mysql',
'charset' => 'utf8',
'username' => 'demo',
'password' => 'sadhiJ7slv!a'
);
/**
* Подключаемся к СУБД MySQL через вызов конструктора собственного класса
* Этот класс наследует свойства и методы от класса PDO.
*
* При вызове конструктора передаём параметры подключения в виде массива,
* с которыми внутри функции-конструктора нашего класса что-то уже делается
*/
$dbh = new DB( $params );
?>
Но нет никакой особой необходимости передавать конструктору класса при вызове параметры подключения. Более того, нет никакой необходимости вызывать саму функцию-конструктор.
В этой статье я не останавлиюваюсь на разъяснениях и описаниях таких понятий, как: классы в PHP, методы классов в PHP, свойства классов в PHP, наследование в PHP, функция-конструктор класса в PHP, функция-деструктор класса в PHP и других подобных этим. Принимаем за данность то, что если вы пришли к необходимости ознакомления с этой статьёй, озвученные выше базовые знания и понятия у вас есть.
Данные с параметрами подключения можно хранить в защищенном статическом свойстве класса. Но что произойдет, если сделать следующее?
<?php
/**
* Подключаемся к СУБД MySQL через вызов конструктора собственного класса
* Этот класс наследует свойства и методы от класса PDO.
*
* При вызове конструктора наш пользовательский класс устанавливает соединение
* с СУБД MySQL через те параметры, которые содержатся внутри класса
* в статическом свойстве.
*/
$dbh_1 = new DB();
$dbh_2 = new DB();
$dbh_3 = new DB();
$dbh_4 = new DB();
?>
Будут созданы четыре идентичных экземпляра одного и того же класса. А зачем это нужно? Ну, в общем-то, если вам именно такое поведение PHP-сценария не нужно, то оно и не нужно. Как защититься от такого? Легко! Нужно создавать экзепляр класса, используя следующую конструкцию.
<?php
/**
* Подключаемся к СУБД MySQL через вызов конструктора собственного класса
* Этот класс наследует свойства и методы от класса PDO.
*
* При вызове конструктора наш пользовательский класс устанавливает соединение
* с СУБД MySQL через те параметры, которые содержатся внутри класса
* в статическом свойстве.
*/
$dbh = DB::instance();
?>
Статический метод instance() вернет вызову либо уже существующее соединение с СУБД, либо при отсутствии такого установит соединение и опять-таки вернет его вызову.
Ну и к этому моменту у вас, скорее всего, назрел вопрос... а что вообще происходит и как это сделано? Давайте представим, как может выглядеть наш пользовательский класс DB, с помощью которого мы через PDO подключаемся к MySQL описанным выше способом.
<?php
/**
* Пользовательский класс для создания подключения к СУБД MySQL,
* наследующий свойства и методы встроенного в PHP класса PDO
*/
class DB extends PDO {
/**
* Параметры подключения к СУБД
*/
public static $_aParams = array(
'driver' => 'mysql', // Используемый драйвер для соединения
'host' => 'localhost', // Адрес СУБД
'dbname' => 'demo_pdo-mysql', // Имя базы данных MySQL
'charset' => 'utf8', // Кодировка соединения
'username' => 'demo', // Имя пользователя MySQL
'password' => 'sadhiJ7slv!a', // Пароль пользователя
'params' => array( // Дополнительные параметры подключения
PDO::ATTR_PERSISTENT => true, // Использовать постоянные подключения
)
);
/**
* Защищенное статическое свойство, в котором будет храниться установленное подключение к СУБД
*/
protected static $_dbh = NULL;
/**
* Защищенное статическое свойство, в котором хранится статус наличия созданного экзепляра объекта нашего класса
*/
protected static $_instance = FALSE;
/**
* @brief Конструктор класса подключения к СУБД
*
* @details Частный метод, который не даст создать экземпляр класса стандартным способом
* в целях защиты от создания нескольких идентичных экземпляров нашего класса
*/
private function __construct() {
// Нет необходимости нагружать функцию-конструктор лишними действиями
// Установку соединения доверим защищенному методу класса
$this->_connect();
++self::$count;
}
/**
* @brief Защищенный метод для установки подключения к СУБД
*/
protected function _connect() {
// Используя имеющиеся параметры подключения подключимся к СУБД
$dsn = self::$_aParams[ 'driver' ] . ':host=' .
self::$_aParams[ 'host' ] . ';dbname=' .
self::$_aParams[ 'dbname' ] . ';charset=' .
self::$_aParams[ 'charset' ];
/**
* Устанавливаем подключение с СУБД
*
* Строку с параметрами сервера мы сформировали выше
* Теперь добавляем еще имя пользователя, пароль пользователя
* и дополнителные параметры подключения
*
* Используется именно такая конструкция: parent::__construct(),
* которая вызывает конструктор родительского класса PDO
*/
parent::__construct( $dsn, self::$_aParams[ 'username' ], self::$_aParams[ 'password' ], self::$_aParams[ 'params' ] );
// В дальнейшем используем именно псевдопеременную $this, так как
// наш класс унаследовал все допустимые свойства и методы от класса
// PDO, и эта псевдопеременная ссылается именно на свойства и методы
// класса PDO, если они не определены в нашем классе
// Устанавливаем режим обработки ошибок,
// используя для этого метод setAttribute() объекта PDO
$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Устанавливаем режим выборки по умолчанию для объекта запроса,
// используя для этого метод setAttribute() объекта PDO
$this->setAttribute( PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC );
// После того, как будут выполнены все действия
// установить значение TRUE для свойства, в котором хранится информация
// о существовании созданного объекта нашего класса
self::$_instance = TRUE;
// Сохраняем в статическое свойство экземпляр объекта, подключенный к СУБД
self::$_dbh = $this;
}
/**
* @brief статический метод получения экзепляра объекта класса подключения к СУБД
*
* @return свойство, в котором хранится подключение к СУБД
*
*/
public static function instance() {
// Если ранее был создан экземпляр объекта этого класса
if ( self::$_instance !== FALSE ) {
// Вернуть этот экземпляр объекта
return self::$_dbh;
}
// Если такой экзепляр ещё не существует, создать его
return new self();
}
/**
* Статическое защищенное свойство, которое будет увеличиваться на 1
* при каждом создании экземпляра объекта нашего класса. Служит только
* для того, чтобы вы могли проверить, действительно ли нельзя создать
* несколько экземпляров объектов нашего класса
*/
protected static $count = 0;
/**
* @brief Получение значений счетчика экземпляров объекта класса
*
* @return число созданных экземпляров
*/
public static function getCount() {
return self::$count;
}
}
?>
Обратите внимание на так названный «счётчик» внутри нашего класса. Вы можете попытаться создать несколько экземпляров объекта класса PDO подключения к СУБД MySQL, а затем проверить значение счётчика, вызвав статический метод DB::getCount(). Наример, с помощью такого PHP-кода.
<?php
$dbh = DB::instance();
$dbh1 = DB::instance();
$dbh2 = DB::instance();
$dbh3 = DB::instance();
print DB::getCount(); // Выведет на экран 1
$dbh4 = new DB(); // Вызовет фатальную ошибку, так как конструктор класса нельзя вызывать таким образом
?>
Код нашего класса достаточно большой, вынесем его в отдельный файл с названием
Обработка исключения в собственном классе кодключения через PDO
Ранее в предыдущей статье я утверждал, что лучше использовать конструкции для перехвата исключений, чтобы избежать прерывания исполнения кода PHP-сценария, но в приведенном примере кода нашего класса DB вы ничего такого не увидели. Пора исправить это. Добавим к коду в файле db.php в место, где происходит подключение к СУБД MySQL через вызов конструктора класса PDO, конструкцию
Эта конструкция будет перехватывать исключения типа PDOException, но такого типа исключений мы добавим свой собственный класс MyPDOException, в котором определим отображение информации об ошибке, если она появится.
Согласно соответствующему разделу официальной документации PHP все объекты, «выбрасывающиеся» с помощью выражения throw, должны наследовать родительский интерфейс Throwable, например будучи наследниками подкласса Exception. Так и сделаем с нашим классом MyPDOException. Но сначала внесём изменения в код в файле db.php
<?php
/**
* файл db.php
*
* Пользовательский класс для создания подключения к СУБД MySQL,
* наследующий свойства и методы встроенного в PHP класса PDO
*/
class DB extends PDO {
/**
* Параметры подключения к СУБД
*/
public static $_aParams = array(
'driver' => 'mysql', // Используемый драйвер для соединения
'host' => 'localhost', // Адрес СУБД
'dbname' => 'demo_pdo-mysql', // Имя базы данных MySQL
'charset' => 'utf8', // Кодировка соединения
'username' => 'demo', // Имя пользователя MySQL
'password' => 'sadhiJ7slv!a1', // Пароль пользователя
'params' => array( // Дополнительные параметры подключения
PDO::ATTR_PERSISTENT => true, // Использовать постоянные подключения
)
);
/**
* Защищенное статическое свойство, в котором будет храниться установленное подключение к СУБД
*/
protected static $_dbh = NULL;
/**
* Защищенное статическое свойство, в котором хранится статус наличия созданного экзепляра объекта нашего класса
*/
protected static $_instance = FALSE;
/**
* @brief Конструктор класса подключения к СУБД
*
* @details Частный метод, который не даст создать экземпляр класса стандартным способом
* в целях защиты от создания нескольких идентичных экземпляров нашего класса
*/
private function __construct() {
// Нет необходимости нагружать функцию-конструктор лишними действиями
// Установку соединения доверим защищенному методу класса
$this->_connect();
++self::$count;
}
/**
* @brief Защищенный метод для установки подключения к СУБД
*/
protected function _connect() {
// Используя имеющиеся параметры подключения подключимся к СУБД
$dsn = self::$_aParams[ 'driver' ] . ':host=' .
self::$_aParams[ 'host' ] . ';dbname=' .
self::$_aParams[ 'dbname' ] . ';charset=' .
self::$_aParams[ 'charset' ];
/**
* Устанавливаем подключение с СУБД
*
* Строку с параметрами сервера мы сформировали выше
* Теперь добавляем еще имя пользователя, пароль пользователя
* и дополнителные параметры подключения
*
* Используется именно такая конструкция: parent::__construct(),
* которая вызывает конструктор родительского класса PDO
*/
// Пытаемся установить соединение с СУБД
try {
parent::__construct( $dsn, self::$_aParams[ 'username' ], self::$_aParams[ 'password' ], self::$_aParams[ 'params' ] );
}
// Если соединение установить не удастся, будет выброшено исключение типа PDOException,
// которое мы перехватим
catch ( PDOException $e ) {
// Передадим на обработку исключение нашему классу
MyPDOException::instance( $e );
// Так как подключиться к СУБД не удалось, в дальнейшем выполнении кода
// сценария нет никакого смысла
exit();
}
// Дальнейший код будет исполнен в случае успешной установки
// соединения с СУБД
// Ниже используем именно псевдопеременную $this, так как
// наш класс унаследовал все допустимые свойства и методы от класса
// PDO, и это псевдопеременная ссылается именно на свойства и методы
// класса PDO, если они не определены в нашем классе
// Устанавливаем режим обработки ошибок,
// используя для этого метод setAttribute() объекта PDO
$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Устанавливаем режим выборки по умолчанию для объекта запроса,
// используя для этого метод setAttribute() объекта PDO
$this->setAttribute( PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC );
// После того, как будут выполнены все действия
// установить значение TRUE для свойства, в котором хранится информация
// о существовании созданного объекта нашего класса
self::$_instance = TRUE;
// Сохраняем в статическое свойство экземпляр объекта, подключенный к СУБД
self::$_dbh = $this;
}
/**
* @brief статический метод получения экзепляра объекта класса подключения к СУБД
*
* @return свойство, в котором хранится подключение к СУБД
*
*/
public static function instance() {
// Если ранее был создан экземпляр объекта этого класса
if ( self::$_instance !== FALSE ) {
// Вернуть этот экземпляр объекта
return self::$_dbh;
}
// Если такой экзепляр ещё не существует, создать его
return new self();
}
/**
* Статическое защищенное свойство, которое будет увеличиваться на 1
* при каждом создании экземпляра объекта нашего класса. Служит только
* для того, чтобы вы могли проверить, действительно ли нельзя создать
* несколько экземпляров объектов нашего класса
*/
protected static $count = 0;
/**
* @brief Получение значений счетчика экземпляров объекта класса
*
* @return число созданных экземпляров
*/
public static function getCount() {
return self::$count;
}
}
?>
Теперь давайте взглянем, как мог бы выглядеть код нашего класса обработки исключений объекта типа PDOException. Сразу же сохраним код этого класса в отдельный файл mypdoexception.php
<?php
/**
* файл mypdoexception.php
*
* Пользовательский класс является потомком класса Exception
* Предназначен только лишь для вывода на экран сообщений
* об ошибках при работе с СУБД
*
*/
class MyPDOException extends Exception {
/**
* Защищенное свойство, в котором будет храниться
* экземпляр объекта класса PDO
*/
protected $_pdoObject = NULL;
/**
* @brief Частная (закрытая) функция-конструктор класса
*
* @param [in] $e Объект типа PDOException
* @details отображает сообщение об ошибке
*
*/
private function __construct( PDOException $e ) {
// Сохраняем ссылку на объект типа PDOException
$this->_pdoObject = $e;
// Формируем строку сообщения об ошибке
$this->_showMessage();
}
/**
* @brief Защищенный метод для формирования строки сообщения об ошибке
*
* @param [in] $string уточненное сообщение об ошибке
*
* @details формирует строку с html-кодом
*/
protected function _showMessage( $string = NULL ) {
// Начальный код строки
$str = "<hr style='color:red' />";
// Если было передано уточненное сообщение, добавляем его, иначе добавим сообщение по умолчанию
$str .= ( !is_null( $string ) ) ? $string : "При выполнении сценария произошла ошибка:";
// Добавляем сообщение об ошибке из объекта PDOException
$str .= " " . $this->_pdoObject->getMessage() . "";
// Добавляем информацию о строке файла, в которой произошла ошибка, и о самом файле
$str .= "в строке " . $this->_pdoObject->getLine() . " файла " . $this->_pdoObject->getFile() . "";
// Формируем блок информации со стеком вызовов
$str .= "
Стек вызовов.";
// Для каждого из таких элементов
foreach ( $this->_pdoObject->getTrace() as $array ) {
// Добавляем информацию о файле и номере строки в нем
$str .= "
Файл: " . $array[ 'file' ] . " строка: " . $array[ 'line' ];
// Если есть информация о названии функции (метода), класса и операторе доступа, добавляем и их
if ( !empty( $array[ 'function' ] ) && !empty( $array[ 'class' ] ) && !empty( $array[ 'type' ] ) ) {
$str .= ", в вызове функции (метода): " . $array[ 'class' ] . $array[ 'type' ] . $array[ 'function' ] . "()";
}
}
// Закрываем сообщение об ошибке горизонтальной чертой
$str .= "<hr style='color:red' />";
// Выводим сообщение на экран
print $str;
}
/**
* @brief Статический метод для получения экземпляра этого класса
*
* @param [in] $e Объект типа PDOException
* @return экземпляр этого класса
*
*/
public static function instance( PDOException $e ) {
return new self( $e );
}
}
?>
Хотелось бы проверить это всё... но как, да? Достаточно попытаться подключиться к MySQL с неверным паролем для пользователя. Код файла, в котором мы это проверим, будет выглядеть следующим образом.
<?php
// Подключаем файл с объявлением класса DB для работы с MySQL через PDO
require_once( 'db.php' );
// Подключаем файл с объявлением класса нашего обработчика исключений типа PDOException
require_once( 'mypdoexception.php' );
// Подключаемся к СУБД
$dbh = DB::instance( array( 'password' => '123456' ) );
?>
Но для того, чтобы этот код дал какой-то эффект, нам нужно внести изменения в наш класс DB. На текущий момент статический метод instance() не собирается принимать какие-либо аргументы от вызова. Мы не станем намеренно менять пароль пользователя на неверный в статическом свойстве-массиве $_aParams. Зачем это делать? Лучше дадим возможность изменять параметры подключения «на лету». Внесем изменения лишь в метод instance() класса DB и в его конструктор.
<?php
/**
* файл db.php
*
* Пользовательский класс для создания подключения к СУБД MySQL,
* наследующий свойства и методы встроенного в PHP класса PDO
*/
class DB extends PDO {
/**
* Параметры подключения к СУБД
*/
public static $_aParams = array(
'driver' => 'mysql', // Используемый драйвер для соединения
'host' => 'localhost', // Адрес СУБД
'dbname' => 'demo_pdo-mysql', // Имя базы данных MySQL
'charset' => 'utf8', // Кодировка соединения
'username' => 'demo', // Имя пользователя MySQL
'password' => 'sadhiJ7slv!a', // Пароль пользователя
'params' => array( // Дополнительные параметры подключения
PDO::ATTR_PERSISTENT => true, // Использовать постоянные подключения
)
);
/**
* Защищенное статическое свойство, в котором будет храниться установленное подключение к СУБД
*/
protected static $_dbh = NULL;
/**
* Защищенное статическое свойство, в котором хранится статус наличия созданного экзепляра объекта нашего класса
*/
protected static $_instance = FALSE;
/**
* @brief Конструктор класса подключения к СУБД
*
* @param [in] $aParams массив с параметрами подключения, изначально пустой
*
* @details Частный метод, который не даст создать экземпляр класса стандартным способом
* в целях защиты от создания нескольких идентичных экземпляров нашего класса
*/
private function __construct( $aParams = array() ) {
// Если были переданы параметры подключения, сохраним их
if( !empty( $aParams ) && is_array( $aParams ) ) {
// Подобное действие переопределит значения массива,
// если в массиве $aParams найдутся идентичные ключи
self::$_aParams = $aParams += self::$_aParams;
}
// Нет необходимости нагружать функцию-конструктор лишними действиями
// Установку соединения доверим защищенному методу класса
$this->_connect();
++self::$count;
}
/**
* @brief Защищенный метод для установки подключения к СУБД
*/
protected function _connect() {
// Используя имеющиеся параметры подключения подключимся к СУБД
$dsn = self::$_aParams[ 'driver' ] . ':host=' .
self::$_aParams[ 'host' ] . ';dbname=' .
self::$_aParams[ 'dbname' ] . ';charset=' .
self::$_aParams[ 'charset' ];
/**
* Устанавливаем подключение с СУБД
*
* Строку с параметрами сервера мы сформировали выше
* Теперь добавляем еще имя пользователя, пароль пользователя
* и дополнителные параметры подключения
*
* Используется именно такая конструкция: parent::__construct(),
* которая вызывает конструктор родительского класса PDO
*/
// Пытаемся установить соединение с СУБД
try {
parent::__construct( $dsn, self::$_aParams[ 'username' ], self::$_aParams[ 'password' ], self::$_aParams[ 'params' ] );
}
// Если соединение установить не удастся, будет выброшено исключение типа PDOException,
// которое мы перехватим
catch ( PDOException $e ) {
// Передадим на обработку исключение нашему классу
MyPDOException::instance( $e );
// Так как подключиться к СУБД не удалось, в дальнейшем выполнении кода
// сценария нет никакого смысла
exit();
}
// Дальнейший код будет исполнен в случае успешной установки
// соединения с СУБД
// Ниже используем именно псевдопеременную $this, так как
// наш класс унаследовал все допустимые свойства и методы от класса
// PDO, и это псевдопеременная ссылается именно на свойства и методы
// класса PDO, если они не определены в нашем классе
// Устанавливаем режим обработки ошибок,
// используя для этого метод setAttribute() объекта PDO
$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Устанавливаем режим выборки по умолчанию для объекта запроса,
// используя для этого метод setAttribute() объекта PDO
$this->setAttribute( PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC );
// После того, как будут выполнены все действия
// установить значение TRUE для свойства, в котором хранится информация
// о существовании созданного объекта нашего класса
self::$_instance = TRUE;
// Сохраняем в статическое свойство экземпляр объекта, подключенный к СУБД
self::$_dbh = $this;
}
/**
* @brief статический метод получения экзепляра объекта класса подключения к СУБД
*
* @return свойство, в котором хранится подключение к СУБД
*
*/
public static function instance() {
// Если ранее был создан экземпляр объекта этого класса
if ( self::$_instance !== FALSE ) {
// Вернуть этот экземпляр объекта
return self::$_dbh;
}
// Если методу были переданы какие-либо аргументы
if ( func_num_args() > 0 ) {
// Сохраним переданный аргумент в переменную
// Нас интересует лишь первый аргумент, если он был передан
$aParams = func_get_args()[ 0 ];
// Параметры должны передаваться в виде массива и никак иначе
if( is_array( $aParams ) ) {
// Передаем параметры конструктору класса, создаем экземпляр класса
return new self( $aParams );
}
}
// Создаем экземпляр класса
return new self();
// Всё остальное будет проигнорировано
}
/**
* Статическое защищенное свойство, которое будет увеличиваться на 1
* при каждом создании экземпляра объекта нашего класса. Служит только
* для того, чтобы вы могли проверить, действительно ли нельзя создать
* несколько экземпляров объектов нашего класса
*/
protected static $count = 0;
/**
* @brief Получение значений счетчика экземпляров объекта класса
*
* @return число созданных экземпляров
*/
public static function getCount() {
return self::$count;
}
}
?>
Обработка полей SELECT
После этих манипуляций мы готовы к дальнейшей работе... а вы, наверное, уже забыли, с чего всё начиналось.
Начиналось всё с того, чтобы мою же статью о динамических списках, информация в которые подгружается из базы данных MySQL, переделать на алгорит работы PHP — PDO — MySQL.
Вы скажете: «Так много действий, кода... и всё ради переделки?». Да, так много действий и кода. Это объектно-ориентированный подход к делу. Зато у нас теперь о многом голова болеть не будет. И вы потом поймете, что имеется в виду.
Файлов, в которые нам потребуется внести изменения, немного.
- index.php
- functions.php
- requests.php
Основное число изменений — подключение файлов с кодом наших двух классов: DB и MyPDOException. Ну и немного изменяется алгорит обработки результатов запросов к MySQL. Внимательно следите за комментариями внутри файлов. Файлы scripts.js и style.css вообще не претерпели изменений, как и таблицы с данными в MySQL.
Файл index.php
<?php
/**
* файл index.php
* Страница с html-формой, в которой находятся два поля Select.
*/
/** Обращайте внимание на комментарии, которые находятся в подобных блоках,
* а не в блоках с двумя слэшами в начале строке. В подобных блоках мы пишем
* о вносимых в код изменениях
*/
/***** Это фрагмент кода, который на сегодняшний момент устарел *********
// Подключаем файл для соединения с СУБД MySQL
require_once( 'database.php' );
/***** Это фрагмент кода, который на сегодняшний момент устарел *********/
// Подключаем файл, в котором будем объявлять пользовательские функции
/* Подключение к СУБД будет происходить непосредственно в функциях */
require_once( 'functions.php' );
?>
<!-- Пишем в рамках стандарта HTML5 -->
<!DOCTYPE html>
<html>
<head>
<title>Выбор марки и модели автомобиля</title>
<!-- Подключаем библиотеку jQuery -->
<script src="//libs.raltek.ru/libs/jquery/1.8.3/js/jquery-1.8.3.js"></script>
<!-- Подключаем таблицу стилей -->
<link href="style.css" rel="stylesheet" type="text/css" />
<!-- Подключаем JavaScript-файл с нашим сценарием, который и будет получать данные об автомобилях -->
<script src="scripts.js"></script>
</head>
<body>
<!-- Создаем контейнер-обертку для нашей формы -->
<div id="car_producers_wrapper">
<!-- Сама форма -->
<form name="car_producers" id="car_producers" >
<!-- Контейнер для поля выбора производителя -->
<div class="row">
<!-- Метка поля производителей автомобилей -->
<label for="producer">Производитель автомобилей:</label>
<!-- Раскрывающийся список производителей автомобилей -->
<select id="producer">
<option value="0">Выберите из списка</option>
<?php
// Получаем перечень производителей в виде массива
$aProducers = getProducers();
// Для каждого элемента массива производителей автомобилей...
foreach ( $aProducers as $aProducer ) {
// Создаем свой элемент раскрывающегося списка
print '<option value="' . $aProducer[ 'id' ] . '">' . $aProducer[ 'producer' ] . '</option>';
}
?>
</select>
</div>
<!-- Контейнер для поля выбора модели автомобиля выбранного производителя -->
<div class="row">
<!-- Метка поля выбора марки автомобиля -->
<label for="model">Марка автомобиля:</label>
<!-- Раскрывающийся список выбора марки автомобиля выбранного производителя -->
<!-- Изначально список пуст и неактивен -->
<!-- Данные в нем появятся полсле выбора производителя -->
<select id="model" disabled >
<option value="0">Выберите из списка</option>
</select>
</div>
</form>
</div>
</body>
</html>
Файл functions.php
<?php
/**
* файл functions.php
* Здесь размещены функции, получающие данные из MySQL для полей формы
*/
/**
* Подключаем файл с нашими объявлениями классов DB и MyPDOException
*/
require_once( 'db.php' );
require_once( 'mypdoexception.php' );
/**
* Функция для получения перечня производителей автомобилей
*/
function getProducers() {
/** Более этот код не актуален
* Мы будем подключаться к СУБД MySQL через класс DB
*/
// Подключаемся к СУБД MySQL
// connect();
$dbh = DB::instance();
// Выбираем всех производителей из таблицы
$sql = "SELECT * FROM `producers` ORDER BY `producer`";
/**
* Этот код более не актуален
*/
// Выполняем запрос
// $query = mysql_query( $sql ) or die ( mysql_error() );
// Поместим данные, которые будет возвращать функция, в массив
// Пока что он будет пустым
$array = array();
// Инициализируем счетчик
$i = 0;
/** Это устаревшая часть кода **/
/*while ( $row = mysql_fetch_assoc( $query ) ) {
$array[ $i ][ 'id' ] = $row[ 'id' ]; // Идентификатор производителя
$array[ $i ][ 'producer' ] = $row[ 'producer' ]; // Имя производителя
// После каждой итерации цикла увеличиваем счетчик
$i++;
}*/
try {
foreach( $dbh->query( $sql ) as $row ) {
$array[ $i ][ 'id' ] = $row[ 'id' ]; // Идентификатор производителя
$array[ $i ][ 'producer' ] = $row[ 'producer' ]; // Имя производителя
// После каждой итерации цикла увеличиваем счетчик
$i++;
}
}
catch ( PDOException $e ) {
MyPDOException::instance( $e );
}
// Возвращаем вызову функции массив с данными
return $array;
}
// Функция, которая выбирает модели автомобилей по переданному
// ей идентификатору производителя
function getModels( array $array ) {
// Сохраняем идентификатор производителя из переданного массива
$sProducerId = htmlspecialchars( trim ( $array[ 'producer_id' ] ) );
/** Это устаревшая часть кода **/
// Подключаемся к MySQL
// connect();
$dbh = DB::instance();
// Строка запроса из базы данных
$sql = "SELECT `id`, `model` FROM `models` WHERE `producer_id` = '" . $sProducerId . "' ORDER BY `model`";
/** Это устаревшая часть кода **/
// Выполняем запрос
// $query = mysql_query( $sql ) or die ( mysql_error() );
// Поместим данные, которые будет возвращать функция, в массив
// Пока что он будет пустым
$array = array();
// Инициализируем счетчик
$i = 0;
/** Это устаревшая часть кода **/
/*while ( $row = mysql_fetch_assoc( $query ) ) {
$array[ $i ][ 'id' ] = $row[ 'id' ]; // Идентификатор модели
$array[ $i ][ 'model' ] = $row[ 'model' ]; // Наименование модели
// После каждой итерации цикла увеличиваем счетчик
$i++;
}*/
foreach( $dbh->query( $sql ) as $row ) {
$array[ $i ][ 'id' ] = $row[ 'id' ]; // Идентификатор модели
$array[ $i ][ 'model' ] = $row[ 'model' ]; // Наименование модели
// После каждой итерации цикла увеличиваем счетчик
$i++;
}
// Возвращаем вызову функции массив с данными
return $array;
}
?>
Файл requests.php
<?php
/**
* файл requests.php
* Здесь обрабатываются AJAX-запросы
*/
/** Это устаревшая часть кода **/
// Подключаем файл для соединения с СУБД MySQL
// require_once( 'database.php' );
/**
* Подключаем файл с нашими объявлениями классов DB и MyPDOException
*/
require_once( 'db.php' );
require_once( 'mypdoexception.php' );
// Подключаем файл, в котором будем объявлять пользовательские функции
require_once( 'functions.php' );
/**
* Данные передаются методом POST
* Если массив POST, пустой, что-то идет не так
* Более того, переменная $_POST[ 'request' ] пустая, или её не существует,
* значит тоже что-то не так
*/
if ( empty( $_POST ) ) {
die( "Массив \$_POST пустой" );
}
elseif ( empty( $_POST[ 'request' ] ) ) {
die( "Не передан запрос" );
}
else {
// Очищаем строку с типом запроса от лишних пробелов и защищаемся от возможных SQL-инъекций
$request = htmlspecialchars( trim( $_POST[ 'request' ] ) );
// Убираем тип запроса из массива $_POST
unset( $_POST[ 'request' ] );
}
// В переменной $response будем возвращать данные AJAX-запросу
$response = NULL;
switch ( $request ) {
case "getModels":
$response = getModels( $_POST );
break;
}
echo json_encode( $response );
?>
Файлы scripts.js и style.css не претерпели вообще никаких изменений.
/**
* файл scripts.js
*/
(function($) {
// Включаем строгий режим ECMA-Script
"use strict";
/**
* В скрипте мы будем выполнять AJAX-запросы к СУБД MySQL
* Чтобы каждый раз не писать один и тот же код AJAX-запроса, создадим
* свой метод request в объекте jQuery
*/
$.extend({
request: function( options ) {
// В методе request будут различные опции (настройки)
// Это своего рода настройки по умолчанию, созданные
// в объекте options
options = $.extend({
type: "POST", // Метод передачи данных серверу
url: "requests.php", // Путь к файлу со сценарием обращения к СУБД
data: null, // Данные, которые мы будем передавать серверу
async: false, // Асинхронность выполнения AJAX-запроса
dataType: "json", // Тип данных, в котором они передаются
before: null, // Код, выполняемый перед AJAX-запросом
error: function() {}, // Код, выполняемый в случае какой-либо ошибки при AJAX-запросе
complete: options.callback, // Код, выполняемый после AJAX-запроса
success: function( result ) { // Код, выполняемый после получения ответа от сервера
$.response.result = result; // Помещаем ответ от сервера в отдельный объект
},
result: null, // Результат работы
callback: null // Функция обратного вызова
}, options );
options.before = function() {
alert( "ok before" );
};
// Тело AJAX-запроса
$.ajax({
type: options.type,
url: options.url,
data: options.data,
async: options.async,
dataType: options.dataType,
before: options.before,
error: options.error,
complete: options.complete,
success: options.success
});
return this;
},
// Объект, в котором хранится ответ от сервера, полученный через AJAX-запрос
response: {
result: {}
}
});
jQuery(function() {
/**
* При выборе производителя нужно сделать многое
* Сначала из списка моделей должны быть удалены все имеющиеся модели автомобилей
* Затем поле выбора модели автомобиля должно стать неактивным
*/
// Обработчик события выбора производителя
$( '#producer' ).change(function() {
var producer_id = $( this ).val(); // Идентификатор выбранного производителя
// Отключаем поле, установив значения свойства disabled
$( '#model' ).prop( 'disabled', true )
// Находим и удаляем все возможные модели автомобилей из раскрывающегося списка
.find( 'option:not( :first )' ).remove();
// Если был выбран конкретный производитель
if ( producer_id != 0 ) {
// Создаем AJAX-запрос, который вернет нам перечень моделей для выбранной марки
$.request({
data: "request=getModels&producer_id=" + producer_id,
});
// Успешный AJAX-запрос должен закончиться вставкой полученного перечня моделей
// в раскрывающийся список select#model
// Результат AJAX-запроса мы сохраняли в отдельном объекте
var i = 0, models = $.response.result;
for ( i; i < models.length; i++ ) {
$( '#model' ).append( '' + models[ i ].model + '' );
}
// Включаем поле со списком моделей
$( '#model' ).prop( 'disabled', false );
}
}); // Обработчик события выбора производителя
});
})(jQuery); // Используем немедленно вызываемую анонимную функцию
/********* Файл style.css ****************/
/**
* Просто внешний вид
*/
body {
font-size:12pt;
color:#333333;
font-family: Tahoma, Verdana, Arial, Sans-Serif;
}
.row {
margin-bottom:10px;
}
.row label {
margin-bottom:5px;
font-weight:bold;
width:280px;
display:block;
}
.row select {
width:300px;
font-size:1.1em;
border:1px solid #aaaaaa;
padding:3px 5px;
}
#car_producers_wrapper {
width: 310px;
margin: 100px auto 0 auto;
}
Работа над данным примером окончена. Напоминаю, что мы внесли изменения в алгоритм работы динамических списков (динамический Select-ов), данные в которые подгружались из MySQL, описанный в прежней статье. В связи с тем, что работа с функциями вида mysql_ исключена из PHP, тем, для кого тот пример реализации был полезен, приходилось самостоятельно решать проблему его неработоспособности, или уже использовать устаревшую версию PHP.
Теперь же код пригоден для использования в версиях PHP выше 5.5. Пользуйтесь!
Взглянуть на рабочий пример всего того, что было описано выше, можете здесь.
Описанные выше файлы доступны для скачивания в виде архива.
Комментарии
Привет
Robertfuh 0 0 0 14.08.2024 06:44:55 #
KRAKEN ссылка на даркнет маркетплейс
MichaleGon 0 0 0 30.09.2024 07:55:58 #
Услуги монтажа канализаций по МСК и МО.
BrentMeets 0 0 0 19.09.2024 05:38:23 #
Election
AnthonyMew 0 0 0 15.09.2024 06:43:04 #
Hello
AlexeyWed 0 0 0 03.09.2024 15:12:33 #
Transform Your Life with Just Our Help!
Ronaldflelm 0 0 0 30.08.2024 22:25:50 #
Онлайн кинотеатр
Davidmab 0 0 0 29.08.2024 22:07:43 #
Автосервис в Ярославле
Keithver 0 0 0 21.08.2024 13:19:00 #
Election
AnthonyMew 0 0 0 21.08.2024 00:07:18 #
Can I find here worthy man?
SoniaKix 0 0 0 16.08.2024 11:47:39 #
Thank you so much for registering, I am very pleased to see you on the site http://itmove.ru 7514895 verygoodplustime !
Thank you so much for registering, I am very pleased to see you on the site http://itmove.ru 5669301 verygoodplustime ! 0 0 0 13.10.2024 06:58:24 #
Test, just a XRumer 23 StrongAI test.
CharlesDal 0 0 0 24.10.2024 04:42:31 #
Скачать моды для андроид
XRumer23Jen 0 0 0 01.11.2024 03:46:19 #
bite
Tyronenaity 0 0 0 04.11.2024 03:55:21 #
Test, just a XRumer
EstherMUS 0 0 0 05.11.2024 07:17:35 #
Переработка отходов в полезные материалы и товары
utopil_qfOn 0 0 0 06.11.2024 15:04:40 #
Test, just a XRumer 23 StrongAI test...
CharlesDal 0 0 0 07.11.2024 23:19:28 #
резина на самосвал хово
Scootoverty 0 0 0 11.11.2024 12:14:10 #
На сайте Mostbet вы можете найти официальное зеркало для входа и начать делать ставки на спорт в удобное для вас время.
JaslycoP 0 0 0 16.11.2024 15:50:58 #
Получите займ безработным без отказа на карту - простое и удобное решение финансовых проблем без необходимости предоставления справок о доходах.
Annelwam 0 0 0 17.11.2024 03:27:02 #
Китайские шина на камаз
Scootoverty 0 0 0 29.11.2024 05:56:37 #
Получите доступ к рабочему зеркалу сайта букмекерской конторы Мостбет прямо сейчас и делайте ставки на спорт без ограничений.
Safiywaxy 0 0 0 01.12.2024 22:42:28 #
шины для самосвала 315 80 22.5
Scootoverty 0 0 0 03.12.2024 15:38:59 #
Где найти финики?
Arnoldseart 0 0 0 13.12.2024 12:18:42 #
Используйте зеркало Мостбет для входа на сайт в случае блокировки - надежный способ обхода ограничений и продолжения игры.
Elianber 0 0 0 13.12.2024 20:36:52 #