Мой сайт — моя визитная карточка

Динамический список (Динамический select) и MySQL через PDO

Уже давным-давно встроенные в PHP функции mysql_ для работы с СУБД MySQL были объявлены устаревшими, а начиная с PHP 7.0.0 их и вовсе исключили. Теперь нужно использовать либо функции mysqli_, либо использовать расширение PDO (Объекты данных PHP), встроенное в PHP.

Для тех, кто, в общем-то, понимает, о чём пойдет речь дальше, для тех, кому лень читать всё, а также для тех, кто просто хочет найти файлы с кодом готового решения, я предалагаю сразу же скачать подготовленный архив.

Взглянуть на рабочий пример всего того, что будет описано ниже, можете здесь.

Остановимся на втором варианте, так как он, на мой взгляд, прогрессивнее. В одной хорошей книге был дан совет программировать на уровне интерфейса, а не на уровне реализации. Значит нужно придерживаться объектно-ориентированных приемов написания собственного кода. А это значит, что использование PDO в наших PHP-сценариях для соединения с СУБД MySQL предпочтительнее.

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

Как уже описывалось в моей предыдущей статье для создания объекта подключения нам достаточно составить строку с параметрами подключения и используемым драйвером — драйвер MySQL в нашем случае.

Строка подключения может выглядеть так:

		<?php
		$dbh = new PDO( 'mysql:host=localhost;dbname=demo_pdo-mysql;charset=utf8;', 'demo', 'sadhiJ7slv!a' );
		?>
    

Лучше помещать любые обращения к СУБД в блоки try - catch, чтобы избегать прерывания выполнения кода сценария в случае возникновения ошибки, перехватывая исключения. Но вернемся к этому позже.

PDO — это класс. Значит мы можем унаследовать от него все доступные свойства и методы, чтобы использовать их в своих классах. Напишем свой собственный класс для подключения к СУБД MySQL, чтобы подключаться к нему удобным для нас способом.

Подключение к Базе данных может выглядеть так.

		<?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();		// Вызовет фатальную ошибку, так как конструктор класса нельзя вызывать таким образом
		?>
    

Код нашего класса достаточно большой, вынесем его в отдельный файл с названием db.php.

Ранее в предыдущей статье я утверждал, что лучше использовать конструкции для перехвата исключений, чтобы избажать прерывания исполнения кода PHP-сценария, но в приведенном примере кода нашего класса DB вы ничего такого не увидели. Пора исправить это. Добавим к коду в файле db.php в место, где происходит подключение к СУБД MySQL через вызов конструктора класса PDO, конструкцию try — catch.

Эта конструкция будет перехватывать исключения типа 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;
				
			}
			
		}
		?>
    

После этих манипуляций мы готовы к дальнейшей работе... а вы, наверное, уже забыли, с чего всё начиналось.

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

Взглянуть на рабочий пример всего того, что было описано выше, можете здесь.

Описанные выше файлы доступны для скачивания в виде архива.

Я разместил эту статью: 01.06.2019
Количество просмотров: 859
Яндекс.Метрика