您好, 欢迎来到 !    登录 | 注册 | | 设为首页 | 收藏本站

设计模式:仅在需要时如何创建数据库对象/连接?

设计模式:仅在需要时如何创建数据库对象/连接?

尽管可以 直接回答 ops问题:“何时只能在需要时创建/连接到数据库,而不是在每个请求时都可以连接到数据库”,但这是在需要时注入的 ,只是说这 帮助。我在这里解释的是您实际上是如何正确处理的,因为在非特定框架的上下文中确实没有很多有用的信息可以帮助您解决此问题。

以下是该问题的“旧”答案。这鼓励了非常有争议的服务定位器模式,并且对许多“反模式”而言。新的答案加上了我从研究中学到的知识。请先阅读旧答案 ,看看进展如何。

使用疙瘩一段时间后,我学到了很多关于它是如何工作的,以及它如何并不是 真正 是惊人的毕竟。它仍然很酷,但是之所以只有80行代码,是因为它基本上允许创建闭包数组。Pimple经常用作服务定位器(因为它实际上只能做些限制),这是一种“反模式”。

服务定位器模式是一种设计模式,用于软件开发,以封装包含具有强大抽象层的服务的过程。此模式使用一个称为“服务定位器”的中央注册表,该注册表根据请求返回执行特定任务所需的信息。

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

你说这是什么问题?主要问题是这种方法在类中 项。因此,如果开发人员即将更新此类,而他们之前从未见过,那么他们将看到一个包含 的容器 。此外,测试此类课程将是一场噩梦。

为什么我本来要这样做? 是您开始进行依赖项注入的地方。这是 。您可以在控制器级别立即启动它。

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

-> -> -> -> -> -> ->

…然后,依赖项注入容器应立即在第一个 开始工作。

所以说真的,如果我仍然使用pimple,我将定义要创建的控制器以及它们需要的控制器。因此,您可以 以便可以使用它。这是控制反转,使测试更加容易。从Aurn Wiki,(我将在稍后讨论):

在现实生活中,您不会通过将整个五金店(希望地)运送到施工现场来盖房子,以便您可以访问所需的任何零件。取而代之的是,领班(__construct())询问需要的特定部件(门和窗户),然后进行采购。您的对象应该以相同的方式起作用。他们应该只要求执行工作所需的特定依赖项。让众议院有权进入整个五金店,这最好是糟糕的OOP风格,最糟糕的是可维护性的噩梦。-来自Auryn Wiki

关于这一点,我想向您介绍由Rdlowrey撰写的一个很棒的东西,叫做Auryn,我是在周末被介绍给我的。

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

Provider根据构造函数方法签名中指定的参数类型提示,递归实例化类依赖关系。

…如果您对PHP的反射有所了解,就会知道有人称它为“慢速”。因此,这是Auryn所做的:

您可能听说过“反射很慢”。让我们澄清一下:如果做错了,任何事情都可能“太慢”。反射比磁盘访问快一个数量级,比从远程数据库检索信息快几个数量级。此外,如果您担心速度,则每个反射都会提供缓存结果的机会。Auryn会缓存其生成的所有反射,以最大程度地降低潜在的性能影响。

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

我使Auryn成为自动装带器的一部分。这样,当请求一个类时,Auryn可以离开并读取该类及其依赖项以及依赖项的依赖项(等),然后将它们全部返回给该类以进行实例化。我创建Auyrn对象。

$injector = new \Auryn\Provider(new \Auryn\ReflectionPool);

我将数据库 接口 用作数据库类的构造函数中的要求。因此,我告诉Auryn使用哪个具体的实现(如果要在代码中的一点上实例化不同类型的数据库,这是您要更改的部分,并且所有这些仍然可以使用)。

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

如果我想更改为MongoDB并为此编写了一个类,则可以简单地更改Library\Database\MysqLLibrary\Database\MongoDB

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

}

好的,所以使用这种技术,假设您有一个需要用户服务的User控制器(比如说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将执行以下操作:

那就是递归,这就是我之前所说的“自动装配”。这解决了OP的问题,因为 ,该对象才会被实例化, 被实例化。

同样,每个类都具有在构造函数中正常运行所需的要求,因此 像服务定位器模式那样存在 。

RE:如何制作它,以便在需要时调用connect方法。这真的很简单。

具有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();并继续执行您想做的事情。

从本质上讲,你还通过你的数据库对象到需要它的类,用我以前描述的方法,但以决定何时执行上的连接 方法,通过方法的基础 ,只要运行所需的连接方法一。不,您不需要单身人士。您只要告诉它何时连接就可以,而当您不告诉它连接时就不告诉。

我将更深入地解释有关依赖注入容器的知识,以及它们如何可以帮助您解决问题。注意:理解“ 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() { }
}

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

Pimple是一个非常酷的依赖项注入容器(由Symfony2框架的开发者使用),它使用PHP5.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');
};

然后,在此处添加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对象 并设置了对象 及其所需的依赖关系 。在路由机制中,您将容器传递给控制器??。

注意:基本代码示例

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

在您的控制器中,您可以访问$container传入的参数,然后从中请求用户对象时,您将获得一个新的User对象(工厂风格),并且已经注入了数据库对象!

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']各处使用,只需将第三个参数添加到容器 一次即可 。是的,那太棒了。

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

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

其他 2022/1/1 18:16:36 有736人围观

撰写回答


你尚未登录,登录后可以

和开发者交流问题的细节

关注并接收问题和回答的更新提醒

参与内容的编辑和改进,让解决方法与时俱进

请先登录

推荐问题


联系我
置顶