Шаблоны проектирования. Как создать объект базы данных / соединение только при необходимости?

У меня есть простое приложение, скажем, что у него есть несколько классов и «дополнительный», который обрабатывает запросы к базе данных. В настоящее время я создаю объект базы данных каждый раз, когда приложение используется, но в некоторых случаях нет необходимости в подключении к базе данных. Я делаю это так (PHP btw):

$db = new Database(); $foo = new Foo($db); // passing the db 

Но иногда объекту $foo не нужен доступ к db, так как вызываются только методы без действий базы данных. Поэтому мой вопрос: какой профессиональный способ справиться с такими ситуациями / как создать соединение / объект db только тогда, когда это необходимо?

Моя цель – избежать ненужных подключений к базе данных.

Это примерно то, что я использую.

 class Database { protected static $connection; // this could be public if you wanted to be able to get at the core database // set the class variable if it hasn't been done and return it protected function getConnection(){ if (!isset(self::$connection)){ self::$connection = new mysqli($args); } return self::$connection; } // proxy property get to contained object public function __get($property){ return $this->getConnection()->__get($property); } // proxy property set to contained object public function __set($property, $value){ $this->getConnection()->__set($property, $value); } // proxy method calls to the contained object public function __call($method, $args){ return call_user_func_array(array($this->getConnection(), $method), $args); } // proxy static method calls to the contained object public function __callStatic($method, $args){ $connClass = get_class($this->getConnection()); return call_user_func_array(array($connClass, $method), $args); } } 

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

Вот пример простого подхода:

 class Database { public $connection = null ; public function __construct($autosetup = false){ if ($autosetup){ $this->setConnection() ; } } public function getProducts(){//Move it to another class if you wish $this->query($sql_to_get_products); } public function query($sql) { if (!$connection || !$connection->ping()){ $this->setupConnection() ; } return $this->connection->query($sql); } public function setConnection(){ $this->connection = new MySQLi($a, $b, $c, $d) ; } public function connectionAvailable(){ return ($connection && $connection->ping()) ; } } 

Посмотрите на использование контейнера для инъекций зависимостей, что-то вроде Pimple было бы хорошим местом для начала. С контейнером для инъекций зависимостей вы научитесь контейнеру создавать объекты в своем приложении, они не создаются, пока вы не попросите их. С помощью Pimple вы можете настроить ресурс, который будет использоваться совместно, чтобы он когда-либо создавался только один раз во время запроса независимо от того, как часто вы запрашиваете у него контейнер.

Вы можете настроить классы для принятия контейнера в своем конструкторе или использовать метод setter для ввода в ваш класс.

Упрощенный пример может выглядеть так:

 <?php // somewhere in your application bootstrap $container = new Pimple(); $container['db'] = $container->share( function ($c) { return new Database(); } ); // somewhere else in your application $foo = new Foo($container); // somewhere in the Foo class definition $bar = $this->container['db']->getBars(); 

Надеюсь, поможет.

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

Другой аспект – более важный: не кладите код, который делает какую-либо тяжелую работу в ваших конструкторах. В случае объекта базы данных это означает: Не подключайтесь к базе данных внутри конструктора.

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

Создание объекта в PHP разумно быстро. Код класса обычно доступен в кеше кода операции, поэтому он вызывает только вызов автозагрузчика и затем выделяет некоторые байты в памяти для свойств объектов. После этого будет запускаться конструктор. Если единственное, что он делает, это копирование параметров конструктора в локальные переменные свойств, это даже оптимизировано PHP с помощью ссылок «копирование на запись». Таким образом, нет никакой реальной выгоды, если этот объект не будет создан в первую очередь, если вы не сможете его избежать. Если вы можете: еще лучше.

Я родом из мира Java. Java резидентна в памяти по сравнению с запросами HTML без учета состояния. PHP нет. Это совсем другая история – и что мне нравится в PHP.

Я просто использую: $ conn = @pg_connect (DBConnection);

DBConnection – это определение, содержащее информацию о хосте и т. д. @ гарантирует, что текущее соединение используется или создается новый. Как я могу сделать это легче?

Данные о том, как подключиться к базе данных, стабильны. Само соединение может быть воссоздано во время запроса. Зачем мне лучше программировать людей PHP и воссоздавать @? Они сделали это для сообщества PHP, давайте использовать его.

Кстати, никогда не ставьте тяжелые объекты в конструктор и никогда не позволяйте конструктору выполнять какую-то тяжелую работу и не допускайте, чтобы при построении объекта можно было исключить исключение. У вас может быть незавершенный объект, находящийся в вашей памяти. Предпочтительным является метод init. Я согласен с Энрике Барселуш.

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

 class Database { private $arguments = array(); private $link = null; public function __construct() { $this->arguments = func_get_args(); } public function __call( $method, $arguments ) { return call_user_func_array( array( $this->link(), $method ), $arguments ); } public function __get( $property ) { return $this->link()->$property; } public function __set( $property, $value ){ $this->link()->$property = $value; } private function connect() { $this->link = call_user_func_array( 'mysqli_connect', $this->arguments ); } private function link() { if ( $this->link === null ) $this->connect(); return $this->link; } } 

Другой способ добиться такого же поведения – использовать методы mysqli_init () и mysqli_real_connect (), конструктор инициализирует объект с помощью mysqli_init (), и когда вам нужно реальное соединение, используется метод mysqli_real_connect ().

 class Database { private $arguments = array(); public function __construct() { $this->arguments = array_merge( array( 'link' => mysqli_init() ), func_get_args() ); } public function __call( $method, $arguments ) { return call_user_func_array( array( $this->link(), $method ), $arguments ); } public function __get( $property ) { return $this->link()->$property; } public function __set( $property, $value ) { $this->link()->$property = $value; } private function connect() { call_user_func_array( 'mysqli_real_connect', $this->arguments ); } private function link() { if ( !@$this->arguments['link']->thread_id ) $this->connect(); return $this->arguments['link']; } } 

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

 interface IDatabase { function connect(); } class Database implements IDatabase { private $db_type; private $db_host; private $db_name; private $db_user; private $db_pass; private $connection = null; public function __construct($db_type, $db_host, $db_name, $db_user, $db_pass) { $this->db_type = $db_type; $this->db_host = $db_host; $this->db_name = $db_name; $this->db_user = $db_user; $this->db_pass = $db_pass; } public function connect() { if ($this->connection === null) { try { $this->connection = new PDO($this->db_type.':host='.$this->db_host.';dbname='.$this->db_name, $this->db_user, $this->db_pass); $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); return $this->connection; } catch (PDOException $e) { return $e; } } else { return $this->connection; } } } 

Как насчет этого? В connect () проверьте, установлено ли соединение, если да, верните его, если нет, создайте его и верните. Это не даст вам открыть много связей. Скажем, в действии вашего контроллера вы хотите вызвать два метода UserRepository (которые зависят от базы данных), getUsers () и getBlockedUsers (), если вы вызовете эти методы, connect () будет вызываться в каждом из них, с этой проверкой он вернет уже существующий экземпляр.

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

 $db = DB::instance(); 

где DB :: instance объявлен как-то вроде этого

 class DB { //... private static $instance; public static function instance() { if (self::$instance == null) { self::$instance = new self(); } } //... } 
  <?php mysql_select_db('foo',mysql_connect('localhost','root',''))or die(mysql_error()); session_start(); function antiinjection($data) { $filter_sql = stripcslashes(strip_tags(htmlspecialchars($data,ENT_QUOTES))); return $filter_sql; } $username = antiinjection($_POST['username']); $password = antiinjection($_POST['password']); /* student */ $query = "SELECT * FROM student WHERE username='$username' AND password='$password'"; $result = mysql_query($query)or die(mysql_error()); $row = mysql_fetch_array($result); $num_row = mysql_num_rows($result); /* teacher */ $query_teacher = mysql_query("SELECT * FROM teacher WHERE username='$username' AND password='$password'")or die(mysql_error()); $num_row_teacher = mysql_num_rows($query_teacher); $row_teahcer = mysql_fetch_array($query_teacher); if( $num_row > 0 ) { $_SESSION['id']=$row['student_id']; echo 'true_student'; }else if ($num_row_teacher > 0){ $_SESSION['id']=$row_teahcer['teacher_id']; echo 'true'; }else{ echo 'false'; } ?> 

и в php-файле вставьте javascript

  <script> jQuery(document).ready(function(){ jQuery("#login_form1").submit(function(e){ e.preventDefault(); var formData = jQuery(this).serialize(); $.ajax({ type: "POST", url: "login.php", data: formData, success: function(html){ if(html=='true') { window.location = 'folder_a/index.php'; }else if (html == 'true_student'){ window.location = 'folder_b/index.php'; }else { { header: 'Login Failed' }; } } }); return false; }); }); </script> 

другое соединение

  <?php class DbConnector { var $theQuery; var $link; function DbConnector(){ // Get the main settings from the array we just loaded $host = 'localhost'; $db = 'db_lms1'; $user = 'root'; $pass = ''; // Connect to the database $this->link = mysql_connect($host, $user, $pass); mysql_select_db($db); register_shutdown_function(array(&$this, 'close')); } //*** Function: query, Purpose: Execute a database query *** function query($query) { $this->theQuery = $query; return mysql_query($query, $this->link); } //*** Function: fetchArray, Purpose: Get array of query results *** function fetchArray($result) { return mysql_fetch_array($result); } //*** Function: close, Purpose: Close the connection *** function close() { mysql_close($this->link); } } ?>