gpt4 book ai didi

php - 设计模式: How to create database object/connection only when needed?

转载 作者:行者123 更新时间:2023-12-02 14:02:40 25 4
gpt4 key购买 nike

我有一个简单的应用程序,说它有一些类和一个“额外的”类来处理数据库请求。当前,每次使用该应用程序时,我都会创建数据库对象,但是在某些情况下,不需要数据库连接。我正在这样做(PHP btw):

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

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

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

最佳答案

Note: Although the direct answer to ops question, "when can I only create / connect to the database when required and not on every request" is inject it when you need it, simply saying that is not helpful. I'm explaining here how you actually go about that correctly, as there really isn't a lot of useful information out there in a non-specific-framework context to help in this regard.


Updated: The 'old' answer to this question can be see below. This encouraged the service locator pattern which is very controversial and to many an 'anti-pattern'. New answer added with what I've learned from researching. Please read the old answer first to see how this progressed.



新答案

在使用粉刺一段时间后,我了解了很多有关粉刺的工作原理,以及它实际上并不那么令人惊讶的知识。它仍然很酷,但是之所以只有80行代码,是因为它基本上允许创建闭包数组。 Pimple经常用作服务定位器(因为它实际上只能做些限制),这是一种“反模式”。

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

The service locator pattern is a design pattern used in software development to encapsulate the processes involved in obtaining a service with a strong abstraction layer. This pattern uses a central registry known as the "service locator" which on request returns the information necessary to perform a certain task.



我在 bootstrap 中创建pimple,定义依赖项,然后将此容器传递给实例化的每个类。

为什么服务定位器不好?

您说的这是什么问题?主要问题是这种方法 对类隐藏了依赖项。因此,如果开发人员即将更新此类,而他们之前从未见过,那么他们将看到一个包含 未知对象的容器对象。此外,测试此类类(class)将是一场噩梦。

为什么我本来要这样做? 因为我认为在 Controller 之后是您开始进行依赖项注入(inject)的位置。这是 错误的。您可以在 Controller 级别立即启动它。

如果这是我的应用程序中的工作方式:

Front Controller --> Bootstrap --> Router --> Controller/Method --> Model [Services|Domain Objects|Mappers] --> Controller --> View --> Template



...然后,依赖项注入(inject)容器应立即在第一个 Controller 级别上开始工作。

所以说真的,如果我仍然使用pimple,我将定义要创建的 Controller 以及它们需要的 Controller 。因此,您可以 将 View 和模型层中的任何内容注入(inject)到 Controller 中,以便可以使用它。这是 Inversion Of Control,使测试更加容易。从Aurn Wiki,(我将在稍后讨论):

In real life you wouldn't build a house by transporting the entire hardware store (hopefully) to the construction site so you can access any parts you need. Instead, the foreman (__construct()) asks for the specific parts that will be needed (Door and Window) and goes about procuring them. Your objects should function in the same way; they should ask only for the specific dependencies required to do their jobs. Giving the House access to the entire hardware store is at best poor OOP style and at worst a maintainability nightmare. - From the Auryn Wiki



输入Auryn

关于这一点,我想向您介绍一个叫做 Auryn的精彩作品,它是由 Rdlowrey在周末介绍给我的。

Auryn基于类构造函数签名的“ Autowiring ”类依赖关系。这意味着,对于每个请求的类,Auryn都会找到它,找出构造函数中需要的内容,先创建所需的内容,然后再创建最初要求的类的实例。运作方式如下:

The Provider recursively instantiates class dependencies based on the parameter type-hints specified in their constructor method signatures.



...如果您对 PHP's reflection有所了解,就会知道有人将其称为“慢速”。因此,这是Auryn为此做的:

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.



因此,现在我们跳过了“反射慢”的论点,这就是我一直在使用它的方式。

我如何使用Auryn
  • I make Auryn part of my autoloader。这样,当请求一个类时,Auryn可以离开并读取该类及其依赖项,以及依赖项的依赖项(等),然后将它们全部返回到该类中以进行实例化。我创建Auyrn对象。
    $injector = new \Auryn\Provider(new \Auryn\ReflectionPool);
  • 我将数据库接口(interface)用作数据库类的构造函数中的要求。因此,我告诉Auryn使用哪个具体的实现(如果要在代码中的一点上实例化不同类型的数据库,这是您要更改的部分,并且仍然可以使用)。
    $injector->alias('Library\Database\DatabaseInterface', 'Library\Database\MySQL');

  • 如果我想更改为MongoDB并为此编写了一个类,则可以简单地将 Library\Database\MySQL更改为 Library\Database\MongoDB
  • 然后,我将$injector传递到我的路由器中,并在创建 Controller /方法时自动解决依赖关系。
    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();
    }

  • 最后,回答OP的问题

    好的,所以使用这种技术,假设您有一个需要用户服务的User Controller (比如说UserModel),它需要数据库访问权限。
    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;
    }
    }

    如果您在路由器中使用代码,Auryn将执行以下操作:
  • 使用MySQL作为具体类(在boostrap中别名),创建Library\DatabaseInterface
  • 将先前创建的数据库注入(inject)其中,从而创建“UserModel”
  • 使用先前创建的UserModel注入(inject)到其中,创建UserController。

  • 那就是递归,这就是我之前所说的“ Autowiring ”。这解决了OP的问题,因为 仅当类层次结构包含数据库对象作为构造函数要求时,才是实例化的对象, 不是在每次请求时发出的。

    另外,每个类都具有在构造函数中正常运行所需的要求,因此没有 ,没有像服务定位器模式那样的隐含依赖项

    RE:如何制作它,以便在需要时调用connect方法。这真的很简单。
  • 确保在Database类的构造函数中,您无需实例化该对象,而只需传入其设置(主机,dbname,用户,密码)即可。
  • 具有使用类的设置实际执行new PDO()对象的connect方法。
    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
    }
    }
  • 因此,现在,传递给数据库的每个类都将具有该对象,但尚未建立连接,因为尚未调用connect()。
  • 在有权访问Database类的相关模型中,调用$this->db->connect();,然后继续执行您想做的事情。

  • 从本质上讲,您仍然可以使用我之前描述的方法将数据库对象传递给需要它的类,但是要决定何时根据方法进行连接,只需在必需的方法中运行connect方法即可。一。不,您不需要单例。您只是在需要时告诉它何时连接,而在您不告诉它进行连接时则不告诉它。

    旧答案

    I'm going to explain a little more in-depth about Dependency Injection Containers, and how they can may help your situation. Note: Understanding the principles of 'MVC' will help significantly here.



    问题

    您想创建一些对象,但是只有某些对象需要访问数据库。您当前正在做的是在每个请求上创建数据库对象,这完全没有必要,而且在使用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() { }
    }

    那么,创建这些对象并处理它们的相关依赖关系,并且仅将数据库对象传递给相关类的最佳方法是什么?好吧,对我们来说幸运的是,当使用 依赖项注入(inject)容器时,这两者可以和谐地协同工作。

    输入丘疹

    Pimple是一个非常酷的依赖项注入(inject)容器(由Symfony2框架的开发者使用),它使用 PHP 5.3+'s closures

    丘疹的处理方式确实很酷-所需的对象只有在您直接提出要求之前都不会实例化。因此,您可以设置新对象的负载,但是直到您要求它们时,它们才被创建!

    这是您在 boostrap 中创建的一个非常简单的粉刺示例:
    // 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');
    };

    然后,在此处添加User对象和Logger对象。
    // 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对象?

    好问题!因此,您已经在 bootstrap 中创建了 $container对象 并设置了对象及其所需的依赖关系。在路由机制中,您将容器传递给 Controller ​​。

    注意:基本代码示例
    router->route('controller', 'method', $container);

    在您的 Controller 中,您可以访问传入的$container参数,然后当您从中请求用户对象时,您将获得一个新的User对象(工厂风格),并且已经注入(inject)了数据库对象!
    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.
    }
    }

    你解决了什么

    因此,您现在已经用一块石头杀死了多只鸟(而不仅仅是两只)。
  • 在每个请求上创建一个数据库对象-不再了!它仅在您需要时创建,因为Pimple使用
  • 进行了关闭
  • 从 Controller 中删除"new"关键字-是的,没错。您已将此责任移交给了容器。

  • 注意:在继续之前,我想指出第二点的重要性。如果没有此容器,可以说您在整个应用程序中创建了50个用户对象。然后有一天,您想添加一个新参数。 OMG-现在您需要遍历整个应用程序,并将此参数添加到每个new User()中。但是,使用DiC-如果您到处都使用$container['user'],则只需将第三个参数添加到容器中即可,仅此而已。是的,那太棒了。
  • 可以关闭数据库的功能-您听说过,整个要点是,如果您希望从MySQL更改为PostgreSQL-您可以更改容器中的代码以返回已编码的另一种新的数据库类型,只要它们都返回相同的内容,就可以了!交换每个人都经常渴望的具体实现的能力。

  • 重要部分

    这是使用容器的一种方式,这只是一个开始。有很多方法可以使此方法更好-例如,您可以使用反射/某种映射来确定需要容器的哪些部分,而不是将容器移交给每种方法。自动化这个过程,您真是太棒了。

    我希望您觉得这很有用。我在这里完成该操作的方式至少为我节省了可观的开发时间,并且启动非常有趣!

    关于php - 设计模式: How to create database object/connection only when needed?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16472924/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com