Intereting Posts
Безопасный хэш и соль для паролей PHP странная вещь, ответ ajax включает пробелы Как правильно создать функцию для форматирования на PHP в расширении Yii2 Highcharts Как безопасно записывать данные JSON в файл с помощью PHP Как избежать добавления одного и того же шаблона навигатора на каждую страницу .html Определите часовой пояс из широты / долготы без использования веб-сервисов, таких как Geonames.org Как использовать foreach с PHP и XML (simplexml) Запуск нескольких запросов в MySQL без использования подзапроса CakePHP: Как получить данные из двух таблиц с помощью внутреннего соединения? PHP – удалить запятую из последнего цикла PHP Сортировка многомерного массива по количеству элементов Laravel 5 Сессия, работающая в Postman, не работает, если вызвана из браузера Доступ к классам IPB на внешнем сервере с основного сайта CakePHP и пространства имен? Угловая перенастройка PHP не работает

Альтернатива Singleton для PHP PDO

Это мой класс, который я использую для подключения к моей MySQL данных MySQL . Как вы можете видеть, я использую Singleton Pattern но почти каждый пост говорит, что это очень плохой шаблон. Каков наилучший подход для создания класса подключения к базе данных? Есть ли лучший образец?

 class DB extends PDO { function __construct() { try { parent::__construct('mysql:host=' . 'localhost' . ';dbname=' . 'kida', 'root', 'root', array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'"); parent::setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch(PDOException $e) { echo $e->getMessage(); } } public static function get_instance() { static $instance = false; if(!$instance) $instance = new self; return $instance; //returns pdo object. } } 

Использование singleton-pattern (или antipattern) считается плохой практикой, потому что он очень сильно тестирует ваш код, а определения очень сложны до тех пор, пока в какой-то момент проект становится трудно управлять. У вас может быть только один фиксированный экземпляр вашего объекта на php-процесс. При написании автоматических модульных тестов для вашего кода вы должны иметь возможность заменить объект, который должен тестировать код, с помощью тестового двойника, который ведет себя по назначению. Когда код, который вы хотите протестировать, использует одноэлементный, тогда вы не можете заменить его тестовым двойным.

Лучший способ (по моему знанию) организовать взаимодействие между объектами (например, ваш объект Database-Object и другие объекты с использованием базы данных) – это обратить вспять направление зависимостей. Это означает, что ваш код не запрашивает объект, который ему нужен, из внешнего источника (в большинстве случаев глобальный, такой как статический метод get_instance из вашего кода), но вместо этого получает свой объект-атрибут (тот, который ему нужен), обслуживаемый извне прежде чем он понадобится. Обычно вы должны использовать Depency-Injection Manager / Container, как этот, из проекта symfony для создания ваших объектов.

Объекты, которые используют объект базы данных, получат его при вводе в конструкцию. Его можно вводить либо методом сеттера, либо конструктором. В большинстве случаев (не все) лучше вводить параметр (ваш db-объект) в конструкторе, потому что таким образом объект, который использует db-объект, никогда не будет находиться в недопустимом состоянии.

Пример:

 interface DatabaseInterface { function query($statement, array $parameters = array()); } interface UserLoaderInterface { public function loadUser($userId); } class DB extends PDO implements DatabaseInterface { function __construct( $dsn = 'mysql:host=localhost;dbname=kida', $username = 'root', $password = 'root', ) { try { parent::__construct($dsn, $username, $password, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'"); parent::setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch(PDOException $e) { echo $e->getMessage(); } } function query($statement, array $parameters = array()) { # ... } } class SomeFileBasedDB implements DatabaseInterface { function __construct($filepath) { # ... } function query($statement, array $parameters = array()) { # ... } } class UserLoader implements UserLoaderInterface { protected $db; public function __construct(DatabaseInterface $db) { $this->db = $db; } public function loadUser($userId) { $row = $this->db->query("SELECT name, email FROM users WHERE id=?", [$userId]); $user = new User(); $user->setName($row[0]); $user->setEmail($row[1]); return $user; } } # the following would be replaced by whatever DI software you use, # but a simple array can show the concept. # load this from a config file $parameters = array(); $parameters['dsn'] = "mysql:host=my_db_server.com;dbname=kida_production"; $parameters['db_user'] = "mydbuser"; $parameters['db_pass'] = "mydbpassword"; $parameters['file_db_path'] = "/some/path/to/file.db"; # this will be set up in a seperate file to define how the objects are composed # (in symfony, these are called 'services' and this would be defined in a 'services.xml' file) $container = array(); $container['db'] = new DB($parameters['dsn'], $parameters['db_user'], $parameters['db_pass']); $container['fileDb'] = new SomeFileBasedDB($parameters['file_db_path']); # the same class (UserLoader) can now load it's users from different sources without having to know about it. $container['userLoader'] = new UserLoader($container['db']); # or: $container['userLoader'] = new UserLoader($container['fileDb']); # you can easily change the behaviour of your objects by wrapping them into proxy objects. # (In symfony this is called 'decorator-pattern') $container['userLoader'] = new SomeUserLoaderProxy($container['userLoader'], $container['db']); # here you can choose which user-loader is used by the user-controller $container['userController'] = new UserController($container['fileUserLoader'], $container['viewRenderer']); 

Обратите внимание, что разные классы не знают друг о друге. Между ними нет прямых отношений. Это делается, если не требуется фактический класс в конструкторе, но вместо этого требуется интерфейс, который предоставляет необходимые ему методы.

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

PS: Пожалуйста, извините мои постоянные ссылки на проект symfony, это то, к чему я больше всего привык. Другие проекты, такие как Drupal, Propel или Zend, вероятно, также имеют такие концепции.