Динамический SELECT

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

Почему именно через PDO.

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

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

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

Подключаемся к MySQL через PDO.

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

		
<?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.

Обработка исключения в собственном классе кодключения через PDO

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

Обработка полей 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. Пользуйтесь!

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

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

Средний рейтинг статьи:

Комментарии


Hot new pictures each day

Hot photo galleries blogs and pictures
http://williams.granny.porn.topanasex.com/?myah
octopus squid streaming porn video horn porn desperate housewives pictures porn porn star minka big titit porn


charliedj60 0 0 0 18.05.2022 02:51:43 #


Warning! мошеници!

Динамический SELECT. PHP+JavaScript+MySQL
-
Warning!
лъжат клиентите
sex
porno
child porno
spam

https://nani.bg - мошеници
https://nani.bg - спамери
https://nani.bg - лъжат клиентите
https://nani.bg - sex
https://nani.bg - porno

LucilleGab 0 0 0 22.05.2022 07:21:22 #


How can I protect myself from DDoS?

Ramonemerm 0 0 0 21.05.2022 22:54:11 #


Test, just a test

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

Amelialog 0 0 0 21.05.2022 14:39:36 #


Супер көрінеді ұзақ уақыт бойы

Пост үшін рахмет
_________________
vivarobet онлайн-спорт түрлері - казинолық дүкені дижон джунот - 5 букмекерлік кеңсе

FrankiSluct 0 0 0 21.05.2022 12:33:25 #


Подключайте домашний интернет от Билайн выгодно

Beelinepoili 0 0 0 21.05.2022 08:32:13 #


смотреть русское гей порно

порно с 18 летними девушками Порнухино смотреть качественное порно

порно молодые сперма порно онлайн двойное проникновение нарезка смотреть онлайн без регистрации порно домашних

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

Irvingcit 0 0 0 21.05.2022 04:39:22 #


порно красиво кончает

порно зрелые групповуха https://bananchik.top/ скачать ольга порно

молодые порно модели порно инцест с тещей порно анал горло

порно зрелая дрочит молодому
порно девушка обильно
порно трахают мужика домашнее
порно видео молодые групповое
смотреть домашнее порно бесплатно и регистрации
красивое порно замужней
порно русское молодой пацан
фурри девушки порно
немецкое порно зрелых анал
секс порно девушка трахает девушку
a4d48db

WesleyBob 0 0 0 21.05.2022 00:24:12 #


Sexy photo galleries, daily updated pics

Hot photo galleries blogs and pictures
http://lesbian.video.hotblognetwork.com/?anita

drunk female porn hot xxx indian porn movies porn urethra play horney mother fucks son porn ka cute face porn

jaimerl16 0 0 0 20.05.2022 16:57:49 #


Caboose Cabinets Subservient

Caboose installations явантроп on nicety, creditable handling of overpriced furnishings, and the well-connected camouflage not our congress can provide.
window replacement brookline ma|

After assembling hundreds of non-standard kitchens, we aspect unerringly how to find and from time to time non-standard appropriate to both prime and complex larder interplay projects.

Our kitchenette inauguration in saintly regularly consists of two people: a maestro and an assistant.

RussellAlutt 0 0 0 19.05.2022 08:20:55 #


Про массаж

Все про массаж и нетолько. Техника массажа. Методики массажа
Как сделать спортивный массаж, косметический, лечебный.
Посетить эротический массажа
Тантрический и сексуальный массаж посетить в Саратове, любовный массажа

Все о методиках массажа, красота за 30 дней:
freshrelax.ru
Все способы массажа, методики массажа и техника. Рекомендации. Показания и противопоказания. противопоказания:
freshrelax.ru

Химия для дезинфекции и уборки Для клининга и мойки вагонов

Все самое интересное

uliyantreb 0 0 0 23.05.2022 03:24:45 #


Do not miss

Bеst оnlinе cаsino and sports betting
deposit bоnus up to 500
Slоts, Frееspins, Роker, and many gаmes.
get your bоnus right now
https://tinyurl.com/5n8dkrm7

AnthonyneN 0 0 0 23.05.2022 09:21:20 #


ставки на спорт

как выглядит ставках на спорт
как разбогатеть на ставках на спорт
интересные стратегии в ставках на спорт
двойной шанс в ставках на спорт
прибыль на ставках на спорт
все о ставках на спорт как ставить
статьи и о ставках на спорт

http://support.goal.ru/includes/cmp/bonusu_bukmekerskih_kontor_4.html

DavidMt 0 0 0 24.05.2022 08:31:03 #


parker карандаш механический jotter stainless steel

купить шариковую ручку паркер или ручка паркер купить калининград

https://www.parkerrussia.ru/pens/vector/PR13B-VIO1C/

ZrniLen 0 0 0 24.05.2022 08:29:32 #


скачать руской порно

гей порно молодые жестко порно 777 порно русское анал орет

скачать порно видео инцест paprrika17 приват записи

русское анальное порно молодых
смотреть порно фильмы 2019
смотреть порно лиза
зрелые женщины порно видео онлайн бесплатно
порно видео большее половые губы
порно онлайн кастинг анал
русское домашнее порно пьяная жена
порно аниме онлайн
порно девушка дрочит
порно сайт онлайн бесплатно
4f00a3c

JerryCap 0 0 0 24.05.2022 19:04:40 #


Өте қызықты

жұмыс істемейді
_________________
Индиан-ау-Квебек казинолық - phonbet android ресми қосымшасы - киберспорт ставкаларлар в кс

FrankSluct 0 0 0 24.05.2022 19:27:29 #


порно изнасилование зрелых женщин

порно видео сестра анал https://pornoeb.vip/ пара и девушка порно

порно жесткие кончи девушек порно видео измена доме

домашнее порно от 1 лица
скачать и смотреть порно без регистрация
порно онлайн бесплатно фильм с русским переводом
порно видео девушки дрочат парням
домашнее порно жены скрытая камера
порно фильмы со зрелыми бесплатно
домашнее порно в одежде
порно подростков онлайн
порно шоу онлайн
порно монашки анал
2c5afc2

DonovanDiasp 0 0 0 25.05.2022 04:43:58 #


порно молодых при муже

порно чат онлайн регистрация Порно сиси писи

русское домашнее порно волосатые порно соло дойки домашнее порно первый раз в попу

порно девушка медсестра
порно волосатые с большой грудью
порно большая жопа жены
порно со зрелыми лесбиянками
скачать бесплатно порно жмж
порно девушки кончают оргазм
смотреть порно нарезки
порно видео большие члены
порно фильмы красивые анал
скk
c5afc29

ScottNot 0 0 0 25.05.2022 23:36:04 #


Жақсылыққа рахмет

Рахмет, +
_________________
галкин, букмекерлік кеңесті қалай жеңуге болады - Онлайн казинолық блэкджек депозитті тіркеу бонусы жоқ 25 доллар - ставкалар ставкаларсынан пайда

FranksSluct 0 0 0 26.05.2022 10:39:52 #


Aloha

Ahoy, matey!


справка 026 у купить в москве справка в сад купить






Где можно купить больничный лист в Москве? Мы предоставляем возможность оформить больничный лист на выгодных для вас условиях. Наш курьер доставит документ в любой административный округ Москвы. Выбирайте удобный район и сообщите адрес при оформлении больничного листа нашему оператору.


Прочее: http://moscow.24med.space/product/spravka-ot-ortodonta/

RonaldoCex 0 0 0 26.05.2022 10:56:05 #


Thank

форма футбольных клубов
https://footballnaya-forma-msk.ru/
https://cse.google.li/url?q=https://footballnaya-forma-msk.ru

MickelonGyday 0 0 0 26.05.2022 12:03:04 #

Сайт принадлежит ООО Группа Ралтэк. 2014 — 2022 гг