设计模式:如何创建数据库对象仅在需要时/连接?(Design Patterns: How to cr

2019-09-02 11:05发布

我有一个简单的应用程序,比如它有一些类和一个“额外的”一个处理数据库的请求。 我目前正在创建数据库对象每次使用应用程序,但在某些情况下,没有必要对一个数据库连接。 我正在做这样的(PHP的BTW):

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

但有时$foo对象不需要数据库访问,因为只有不包含数据库的操作方法被调用。 所以我的问题是:/如何创建数据库连接/只在需要时反对什么是处理这种情况的专业呢?

我的目标是,以避免不必要的数据库连接。

Answer 1:

注意:虽然直接回答问题OPS,是当你需要它 ,单说这是没有帮助的注入为 “我的时候只能创建/需要的时候,而不是在每次请求连接到数据库”。 我解释这里你如何真正去了解的正确,因为是不是真的有很多有用的信息赫然出现在非特定的框架方面在这方面提供帮助。


更新:在“老”这个问题的答案,可以看到下面。 这鼓励了服务定位器模式,这是非常有争议的,很多的“反模式”。 新的答案添加了什么,我已经从研究的经验教训。 请先阅读旧的答案 ,看看如何进展。

新建答案

使用疙瘩一段时间后,我学到了很多关于它是如何工作的,以及它是如何实际上并没有那么惊人毕竟。 它仍然很酷,但它只有80行代码的原因是因为它基本上允许关闭阵列的创建。 疙瘩被使用了很多的服务定位器(因为它是在什么它实际上可以做这样的限制),这是一种“反模式”。

首先,什么是服务定位器?

服务定位器模式是在软件开发用于封装涉及具有较强的抽象层获得服务的处理的设计图案。 该模式使用被称为“服务定位器”,其在请求返回需要执行特定任务的信息的中央登记。

我在自举创建疙瘩,定义的依赖关系,然后使这个容器的每一个单一的I类实例化。

为什么是一个服务定位器坏了?

什么是与这个你说的问题? 主要的问题是,这种方法从类隐藏的依赖 。 所以,如果一个开发人员来更新这个类之前,他们还没有看到它,他们会看到包含对象的未知量的容器对象。 此外,测试这个类将是一个有点噩梦。

我为什么做这个原本? 因为我认为, 控制器就是你开始做你的依赖注入。 这是错误的 。 你启动它马上在控制器级别。

如果是这样的事情在我的应用程序是如何工作的:

前端控制器 - > 引导 - > 路由器 - > 控制器/方法 - > 模型[服务|域对象|映射器] - > 控制器 - > 查看 - > 模板

......那么依赖注入容器应立即开始在第一控制器级别的工作。

所以真的,如果我仍然使用疙瘩,我会定义控制器将要创建什么,他们需要什么。 所以,你会从注射模型层的视图和任何到控制器 ,因此可以使用它。 这是反转的控制权 ,并使得测试更加容易。 从Aurn维基,(我会尽快谈):

在现实生活中,你将不会被运送整个五金商店(希望)到施工现场,所以你可以访问你需要的任何部件盖房子。 取而代之的是,工头(__construct())请求,将需要的特定部位(门窗),去了解他们采购。 你的对象以相同的方式应有的功能; 他们应该问只为做好本职工作所需的特定的依赖。 给众议院获得整个五金店充其量是可怜的OOP风格,在最坏的情况可维护性噩梦。 -从Auryn百科

输入Auryn

关于这一点,我想向您介绍一些精彩称为Auryn ,以书面Rdlowrey ,我被介绍给周末。

Auryn“自动线”类依赖性基于类的构造函数签名。 这意味着,对于每个请求的类,Auryn发现它,计算出它的构造函数需要,创建它需要什么,然后再创建你问原来是类的一个实例。 下面是它如何工作的:

该供应商递归实例基于在其构造方法签名中指定的参数类型,提示类依赖性。

...and if you know anything about PHP's reflection, you'll know some people call it 'slow'. So here's what Auryn does about that:

You may have heard that "reflection is slow". Let's clear something up: anything can be "too slow" if you're doing it wrong. Reflection is an order of magnitude faster than disk access and several orders of magnitude faster than retrieving information (for example) from a remote database. Additionally, each reflection offers the opportunity to cache the results if you're worried about speed. Auryn caches any reflections it generates to minimize the potential performance impact.

So now we've skipped the "reflection is slow" argument, here's how I've been using it.

How I use Auryn

  • I make Auryn part of my autoloader. This is so that when a class is asked for, Auryn can go away and read the class and it's dependencies, and it's dependencies' dependencies (etc), and return them all into the class for instantiation. I create the Auyrn object.

    $injector = new \Auryn\Provider(new \Auryn\ReflectionPool);
    
  • I use a Database Interface as a requirement in the constructor of my database class. So I tell Auryn which concrete implementation to use (this is the part you change if you want to instantiate a different type of database, at a single point in your code, and it'll all still work).

    $injector->alias('Library\Database\DatabaseInterface', 'Library\Database\MySQL');
    

If I wanted to change to MongoDB and I'd written a class for it, I'd simple change Library\Database\MySQL to Library\Database\MongoDB.

  • Then, I pass the $injector into my router, and when creating the controller / method, this is where the dependencies are automatically resolved.

    public function dispatch($injector)
    {
        // Make sure file / controller exists
        // Make sure method called exists
        // etc...
    
        // Create the controller with it's required dependencies
        $class = $injector->make($controller);
        // Call the method (action) in the controller
        $class->$action();
    }
    

Finally, answer OP's question

Okay, so using this technique, let's say you have the User controller which requires the User Service (let's say UserModel) which requires Database access.

class UserController
{
    protected $userModel;

    public function __construct(Model\UserModel $userModel)
    {
        $this->userModel = $userModel;
    }
}

class UserModel
{
    protected $db;

    public function __construct(Library\DatabaseInterface $db)
    {
        $this->db = $db;
    }
}

If you use the code in the router, Auryn will do the following:

  • Create the Library\DatabaseInterface, using MySQL as the concrete class (alias'd in the boostrap)
  • Create the 'UserModel' with the previously created Database injected into it
  • Create the UserController with the previously created UserModel injected into it

That's the recursion right there, and this is the 'auto-wiring' I was talking about earlier. And this solves OPs problem, because only when the class hierarchy contains the database object as a constructor requirement is the object insantiated, not upon every request.

Also, each class has exactly the requirements they need to function in the constructor, so there are no hidden dependencies like there were with the service locator pattern.

RE: How to make it so that the connect method is called when required. This is really simple.

  1. Make sure that in the constructor of your Database class, you don't instantiate the object, you just pass in it's settings (host, dbname, user, password).
  2. Have a connect method which actually performs the new PDO() object, using the classes' settings.

    class MySQL implements DatabaseInterface
    {
        private $host;
        // ...
    
        public function __construct($host, $db, $user, $pass)
        {
            $this->host = $host;
            // etc
        }
    
        public function connect()
        {
            // Return new PDO object with $this->host, $this->db etc
        }
    }
    
  3. So now, every class you pass the database to will have this object, but will not have the connection yet because connect() hasn't been called.

  4. In the relevant model which has access to the Database class, you call $this->db->connect(); and then continue with what you want to do.

从本质上讲,你还通过你的数据库对象到需要它的类,用我以前描述的方法,而是要决定何时执行的一个方法,通过方法的基础连接,只要运行所需的连接方法一。 不,你并不需要一个单身。 你只要告诉它的时候,当你希望它连接,当你不告诉它连接没有。


老回答

我要解释深入一点关于依赖注入容器,以及它们如何能有助于您的情况。 注:了解“MVC”的原则将帮助显著这里。

问题

你想创建一些对象,但只有特定的一些需要访问数据库。 你当前做的是建立在每个请求 ,这是完全不必要的,并且也使用像DIC容器东西之前完全常见的数据库对象。

两个实施例的对象

下面是您可能要创建两个对象的示例。 人们需要数据库访问,另一种不需要数据库访问。

/**
 * @note: This class requires database access
 */
class User
{
    private $database;

    // Note you require the *interface* here, so that the database type
    // can be switched in the container and this will still work :)
    public function __construct(DatabaseInterface $database)
    {
        $this->database = $database;
    }
}

/**
 * @note This class doesn't require database access
 */
class Logger
{
    // It doesn't matter what this one does, it just doesn't need DB access
    public function __construct() { }
}

那么,什么是创建这些对象和处理其相关的依赖,同时也传递一个数据库对象只向该班上最好的方法是什么? 那么,我们是幸运的,这两个一起工作和谐使用依赖注入容器时。

输入疙瘩

疙瘩是一个非常酷依赖注入容器(由Symfony2的框架的制造商),利用PHP 5.3 +的封闭件 。

那疙瘩做它的方式是真的很酷 - 你要的对象不是实例化,直到你直接问吧。 所以,你可以设置新对象的负荷,但直到你问他们,不创建他们!

这里是一个非常简单的疙瘩例如,你在你的自举创造:

// Create the container
$container = new Pimple();

// Create the database - note this isn't *actually* created until you call for it
$container['datastore'] = function() {
    return new Database('host','db','user','pass');
};

然后,你把你的用户对象和你的记录器对象在这里。

// Create user object with database requirement
// See how we're passing on the container, so we can use $container['datastore']?
$container['User'] = function($container) {
    return new User($container['datastore']);
};

// And your logger that doesn't need anything
$container['Logger'] = function() {
    return new Logger();
};

真棒! 所以..实际上,我怎么使用$容器对象?

好问题! 所以,你已经创建了$container对象在你的引导 ,并成立了对象及其所需的相关性 。 在您的路由机制,您通过容器到控制器。

注:例如最起码的准则

router->route('controller', 'method', $container);

在您的控制器,您可以访问$container传入的参数,当你问它从用户对象,你得到一个新的用户对象(工厂式),与数据库对象已经注入!

class HomeController extends Controller
{
    /**
     * I'm guessing 'index' is your default action called
     *
     * @route /home/index
     * @note  Dependant on .htaccess / routing mechanism
     */
    public function index($container)
    {
        // So, I want a new User object with database access
        $user = $container['User'];

       // Say whaaat?! That's it? .. Yep. That's it.
    }
}

什么你已经解决

所以,你现在已经是一石多杀鸟(而不是两个)。

  • 没有任何更多- 对每个请求创建一个数据库对象 ! 当你问它,因为的关闭疙瘩用途的唯一创建
  • 从控制器中删除“新”的关键字 -是的,这是正确的。 你交给这个责任交给容器。

注:我继续发言之前,我想指出一点两颗子弹如何显著的。 如果没有这个容器,假设您在整个应用程序创建的50个用户对象。 后来有一天,你要添加新的参数。 OMG -你现在需要通过你的整个应用程序,这个参数添加到每个new User() 然而,随着DIC -如果你正在使用$container['user']无处不在,你只需要添加第三参数去容器一次 ,仅此而已。 是的,这完全是真棒。

  • 转出数据库的能力 -你听见我说,这整个的一点是,如果你想从MySQL改为PostgreSQL的-你改变的代码放到你的货柜返回一个新的不同类型的数据库你编码,并作为只要所有返回同一类东西,就是它! 换出具体的实施方案 ,每个人都始终竖琴关于的能力

最重要的部分

这是使用容器的一种方式,它只是一个开始。 有许多方法可以使这更好的 - 例如,而不是物品转交容器的每一个方法,你可以使用反射/某种映射来决定需要什么样的容器的部分。 自动化这个,你是金色的。

我希望你发现这个有用。 我已经在这里做了它的方式已至少削减显著大量的开发时间,对我来说,这是引导好开心!



Answer 2:

这大约是我使用。

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方法结合的提防。



Answer 3:

这里有一个简单的方法的例子:

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()) ;
  }
}


Answer 4:

考虑使用依赖注入容器,像暗疮将是很好的开端。 有了依赖注入容器,你“教”的容器如何在应用程序中创建的对象,他们没有实例化,直到你问他们。 有了疙瘩,您可以配置为共享资源,这样它的请求时永远只能实例化一次,无论你如何经常问的容器吧。

你可以设置你的类接受其构造方法的容器或使用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();

希望能帮助到你。



Answer 5:

你得到了一些伟大的答案已经,其中大部分集中在注入依赖(这是一件好事),只有按需创建对象的方面。

另一个方面是比较重要的:不要把代码做任何繁重的工作到您的构造函数。 在数据库对象的情况下,这意味着:不要连接到构造数据库里面。

这是为什么更重要? 因为由于使用对象还没有被创建是,如果使用对象被创建始终没有真正的优化,但并不总是运行查询不创建数据库对象。

PHP创建一个对象是合理的快。 类代码通常是在操作码缓存中可用,所以它只能触发到磁带自动加载一个呼叫,然后在内存中对象的属性分配一些字节。 构造函数后会运行。 如果它的唯一的事情就是复制构造函数的参数,以本地属性变量,这甚至与PHP“写入时复制”引用了优化。 所以如果此对象没有得到摆在首位创建的,如果你不能避免它没有真正的好处。 如果可以的话:得更好。



Answer 6:

我来自Java世界。 Java是驻留在内存中翻过无国籍HTML请求。 PHP是没有的。 这是一个完全不同的故事 - 我喜欢PHP的东西。

我简单地使用:$康恩= @pg_connect(DBConnection的);

所述DBConnection的是包含有关主机等的信息的定义。该@确保了当前连接使用或新的被创建。 我怎样才能做到这一点更容易?

如何连接到数据库中的数据是稳定的。 连接本身可能在请求过程中被重新创建。 我为什么要编写更好然后PHP的人,并重新创建@? 他们这样做的PHP社区,让我们使用它。

顺便说一句,从来没有把重物在构造函数中,从来没有让构造函数做一些繁重的工作,也没有让它发生异常能建设目标的过程中被抛出。 你可能有一个未完成的对象驻留在你的记忆。 一个初始化方法是优选的。 我同意与恩里克巴塞罗斯。



Answer 7:

这是我使用的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'];
    }
}

我测试的内存消耗这两种方法,得到了相当意外的结果,当连接到数据库并执行查询,第二种方法使用更少的资源。



Answer 8:

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;
        }
    }
}

这个怎么样? 在连接(),检查是否连接已经建立,如果是,返回它,如果没有,创建并返回。 这将阻止您不必打开太多的连接。 比方说,在你的控制器动作,你要调用UserRepository两种方法(即依赖于数据库),getUsers()和getBlockedUsers(),如果你调用这些方法,连接()将在他们中的每一个被调用,与此检查到位它将返回已经存在的实例。



Answer 9:

你可以使用一个单例模式才达到你所需要的数据库的数据库对象这一点,并要求每次。 这导致这样的事情

$db = DB::instance();

其中DB ::实例声明是这样的

class DB {

    //...

    private static $instance;    

    public static function instance() {
        if (self::$instance == null) {
            self::$instance = new self();
        }
    }

    //...

}


Answer 10:

 <?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);

}

  }

  ?>


文章来源: Design Patterns: How to create database object/connection only when needed?