Динамический 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. Пользуйтесь!

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

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

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

Комментарии


7626$ for 8 minutes Binary options trading strategy

Williamsub 0 0 0 16.06.2022 07:05:16 #


Jako urzędowo podpatrywać slajdy w Internecie?

JeraldFibia 0 0 0 07.07.2022 02:15:49 #


VODA

Samantawar 0 0 0 05.07.2022 17:09:54 #


مرحبًا ، لدينا بروفة لنلعبها

RaymondKaf 0 0 0 04.07.2022 10:01:26 #


Annunci Iol

SylviaTer 0 0 0 02.07.2022 06:29:52 #


مرحبًا ، لدينا بروفة لنلعبها

RaymondKaf 0 0 0 01.07.2022 20:34:06 #


Annunci Iol

SylviaTer 0 0 0 29.06.2022 07:23:54 #


HEllo

LolaRend 0 0 0 27.06.2022 00:54:42 #


Hello

Vincifaw 0 0 0 24.06.2022 20:48:55 #


VODA

Samantarxv 0 0 0 20.06.2022 04:38:25 #


Где посмотреть фильмы?

Freddyrom 0 0 0 14.07.2022 10:14:54 #


Где посмотреть фильмы?

Freddyrom 0 0 0 14.07.2022 18:01:40 #


Где посмотреть фильмы?

Freddyrom 0 0 0 15.07.2022 14:30:02 #


Gdy oficjalnie patrzyć slajdy w Necie?

Jamesvak 0 0 0 15.07.2022 14:37:56 #


Как покупать игры для XBOX в РФ в 2022 года?

SammieGaife 0 0 0 22.07.2022 15:37:05 #


silhouettes of trees on hill at orange sunset with great mountains on background

Edwardgat 0 0 0 25.07.2022 00:40:56 #


On the positive.

MichaelLor 0 0 0 26.07.2022 01:54:47 #


용인출장마사지의 효과를 입증한 실험는 많다.

BYEugeneEvidarids 0 0 0 27.07.2022 06:51:29 #


74 челябинск

Arnolddig 0 0 0 27.07.2022 18:04:45 #


the Man Adam Kokesh The Alyona Show Alyona Minkovski The Big Picture Thom Hartmann from RT America The News with Ed Schultz Ed Schultz Redacted Tonight Lee Camp from RT America How to Watch the News with Slavoj ЕЅiЕѕek Larry King Now Larry King. 25654ac

HerbertlOolf 0 0 0 03.08.2022 16:12:03 #


neverming

HindiVideoSiz 0 0 0 04.08.2022 04:10:28 #


How to make Russians aware that the war is bad. APPEAL to Users

XMC.PL-Master 0 0 0 08.08.2022 10:21:46 #


Jako legalnie widzieć filmy w Necie?

FrankieFap 0 0 0 10.08.2022 17:34:54 #


neverming

XNXXSiz 0 0 0 12.08.2022 18:31:22 #


Gdy formalnie poznawać celuloidy w Necie?

FrankieFap 0 0 0 17.08.2022 16:59:33 #


Спасибо за сотрудничество!

ShannonLok 0 0 0 18.08.2022 07:33:40 #


Самое лучшее средство от всех болезней?!

TorgSpirtol 0 0 0 18.08.2022 12:06:30 #


Новые игры 2022 года на ПК

VictorWox 0 0 0 19.08.2022 00:03:59 #


neverming

JITTSiz 0 0 0 21.08.2022 18:18:39 #


What's your side to CBD?

JesseJew 0 0 0 22.08.2022 14:20:04 #


игровой клуб вулкан азартные игры на

Clezimago 0 0 0 24.08.2022 04:01:52 #


free slots online

LeighaMut 0 0 0 24.08.2022 19:47:29 #


How to save tiktok video without watermark?

RaymondKab 0 0 0 25.08.2022 22:21:16 #


Earn traffic for your site

WilliamBulse 0 0 0 28.08.2022 00:33:38 #


Спасибо за сотрудничество!

ShannonLok 0 0 0 28.08.2022 07:49:52 #


Самое лучшее средство от всех болезней?!

TorgSpirtol 0 0 0 28.08.2022 23:43:52 #


4g прокси

WayZicky 0 0 0 29.08.2022 19:38:21 #


Масштабирование в Автокад

JaHep 0 0 0 29.08.2022 20:08:39 #


Как восстановить поврежденный файл Photoshop?

Allendor 0 0 0 31.08.2022 15:04:57 #


Online File Repair

JamesRounc 0 0 0 02.09.2022 14:03:34 #


Top 10 Online Slots

NathanRoaro 0 0 0 02.09.2022 16:12:44 #


hello, i just registered

XmasterAbila 0 0 0 05.09.2022 12:59:32 #


hello, i just registered

XmasterAbila 0 0 0 07.09.2022 10:48:21 #


hello, i just registered

XmasterAbila 0 0 0 08.09.2022 16:19:44 #


hello, i just registered

XmasterAbila 0 0 0 10.09.2022 16:17:52 #


онлайн бесплатно порнуха русский перевод

Kennethfer 0 0 0 14.09.2022 18:21:54 #


Hello, i just registered

IndianVok 0 0 0 15.09.2022 00:45:51 #


How to Beat the Pros promptly With Quick Andsimple Tips

JaKmesclava 0 0 0 19.09.2022 05:11:46 #


"How to Play Online Poker for Beginners: The Most Effective Tips & Tricks"

JaKmahesclava 0 0 0 21.09.2022 03:32:50 #


How to Play Casino Poker-- A Beginner's Guide

JaKmehesclava 0 0 0 22.09.2022 01:41:44 #


301 Moved Permanently

huayzaabet 0 0 0 26.09.2022 19:35:14 #


Test, just a test

charleshicktw 0 0 0 29.09.2022 01:07:43 #


TLaucher Minecraft [NEW]

NiceMods 0 0 0 26.09.2022 20:03:49 #


DataFast Proxies | Highest Quality IPv6 Proxy!

datafastproxiespx01 0 0 0 30.09.2022 09:53:40 #




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