Динамический 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. Пользуйтесь!
Взглянуть на рабочий пример всего того, что было описано выше, можете здесь.
Описанные выше файлы доступны для скачивания в виде архива.





Комментарии
https://clck.ru/33jCGs
https://clck.ru/33jDMn
как обойти капчу reCAPTCHA v.2 captcha
The way I perceive it
Test, just a test
Поиск исполнителей для работ на доске объявлений Оренбурга
Love Knows No Gender: A Look at Gay Men and Turtle-dove
Test, just a test
[url=https://youtu.be/nF3t8zstnvg]Cozy Fireplace Ambience[/url]
[url=https://youtu.be/8Te2LEi-fDc]Fireplace 4K Ultra HD Video[/url]