Sli*_*liq 19 php oop design-patterns database-connection
我有一个简单的应用程序,说它有一些类和一个处理数据库请求的"额外".目前我每次使用应用程序时都会创建数据库对象,但在某些情况下,不需要数据库连接.我这样做(PHP顺便说一句):
$db = new Database();
$foo = new Foo($db); // passing the db
Run Code Online (Sandbox Code Playgroud)
但有时该$foo对象不需要db访问,因为只调用没有数据库操作的方法.所以我的问题是:处理这种情况的专业方法是什么/如何仅在需要时创建数据库连接/对象?
我的目标是避免不必要的数据库连接.
Jim*_*mbo 44
注意:尽管ops 的直接答案是问题,"我什么时候才能在需要时创建/连接到数据库,而不是在每次请求时" 在需要时注入它,只是说这没有帮助.我在这里解释你是如何正确地解决这个问题的,因为在非特定框架环境中确实没有很多有用的信息来帮助这方面.
更新:此问题的"旧"答案如下所示.这鼓励了服务定位器模式,这是非常有争议的,并且很多都是"反模式".新的答案补充了我从研究中学到的东西.请先阅读旧答案,看看这是如何进展的.
使用疙瘩一段时间后,我学到了很多关于它是如何工作的,以及它如何并不是真正是惊人的毕竟.它仍然很酷,但它只有80行代码的原因是因为它基本上允许创建一个闭包数组.疙瘩被广泛用作服务定位器(因为它在实际上可以做到如此有限),这是一种"反模式".
服务定位器模式是软件开发中使用的设计模式,用于封装获得具有强抽象层的服务所涉及的过程.此模式使用称为"服务定位器"的中央注册表,根据请求返回执行特定任务所需的信息.
我在引导程序中创建了疙瘩,定义了依赖项,然后将此容器传递给我实例化的每个单独的类.
你说这有什么问题?主要问题是这种方法隐藏了类的依赖关系.因此,如果开发人员要更新此类并且之前没有看过它们,他们将会看到包含未知数量对象的容器对象.此外,测试这个课程将是一个噩梦.
我为什么最初这样做?因为我认为在控制器开始执行依赖注入之后.这是错的.你可以直接在控制器级别启动它.
如果这是我的应用程序中的工作方式:
前端控制器 - > Bootstrap - > 路由器 - > 控制器/方法 - > 模型[服务|域对象|映射器] - > 控制器 - > 视图 - > 模板
...然后依赖注入容器应该立即开始在第一个控制器级别工作.
所以,如果我仍然使用疙瘩,我将定义将要创建哪些控制器,以及它们需要什么.因此,您可以将视图和任何内容从模型层注入控制器,以便它可以使用它.这是控制反转,使测试更容易.来自Aurn wiki,(我将很快谈到):
在现实生活中,您不会通过将整个五金店(希望)运送到施工现场来建造房屋,以便您可以访问所需的任何部件.取而代之的是,工头(__construct())请求,将需要的特定部位(门窗),去了解他们采购.你的对象应该以相同的方式运作; 他们应该只询问完成工作所需的具体依赖性.让房子进入整个五金店是最糟糕的OOP风格,最糟糕的是可维护性的噩梦. - 来自Auryn Wiki
关于这一点,我想向您介绍一些精彩称为Auryn,以书面Rdlowrey,我被介绍给周末.
Auryn'自动连接'类依赖于基于类构造函数签名.这意味着,对于每个请求的类,Auryn发现它,计算出它的构造函数需要,创建它需要什么,然后再创建你问原来是类的一个实例.以下是它的工作原理:
Provider基于其构造方法签名中指定的参数type-hints递归地实例化类依赖项.
...如果你对PHP的反思有所了解,你会发现有些人称之为'慢'.所以这就是Auryn对此的看法:
你可能听说过"反思很慢".让我们清楚一点:如果你做错了,任何东西都可能"太慢".反射比磁盘访问快一个数量级,比从远程数据库检索信息(例如)快几个数量级.此外,如果您担心速度,每次反射都可以缓存结果.Auryn缓存它产生的任何反射,以最大限度地减少潜在的性能影响.
所以现在我们已经跳过了"反思很慢"的论点,这就是我一直在使用它的方式.
我让Auryn成为我自动加载器的一部分.这样,当一个类被要求时,Auryn可以离开并读取类和它的依赖项,它是依赖项的依赖项(等),并将它们全部返回到类中进行实例化.我创建了Auyrn对象.
$injector = new \Auryn\Provider(new \Auryn\ReflectionPool);
Run Code Online (Sandbox Code Playgroud)我在数据库类的构造函数中使用数据库接口作为要求.所以我告诉Auryn要使用哪个具体实现(如果要在代码中的单个点实例化不同类型的数据库,这是您更改的部分,并且它仍然可以工作).
$injector->alias('Library\Database\DatabaseInterface', 'Library\Database\MySQL');
Run Code Online (Sandbox Code Playgroud)如果我想改成MongoDB并且我为它编写了一个类,我只需要Library\Database\MySQL改为Library\Database\MongoDB.
然后,我将其传递$injector到我的路由器,并在创建控制器/方法时,这是自动解决依赖关系的地方.
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();
}
Run Code Online (Sandbox Code Playgroud)好了,所以使用这种技术,让我们说你有用户控制器,它要求用户服务(让我们说的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;
}
}
Run Code Online (Sandbox Code Playgroud)
如果您使用路由器中的代码,Auryn将执行以下操作:
这就是那里的递归,这就是我之前谈到的'自动布线'.这解决了有机磷农药的问题,因为只有当类层次结构包含数据库对象的构造函数的要求是insantiated对象,不是在每次请求.
此外,每个类都具有在构造函数中运行所需的完全要求,因此没有像服务定位器模式那样的隐藏依赖项.
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
}
}
Run Code Online (Sandbox Code Playgroud)所以现在,您传递数据库的每个类都将具有此对象,但由于尚未调用connect(),因此将不具有连接.
$this->db->connect();然后继续执行您想要执行的操作.本质上,您仍然使用我之前描述的方法将数据库对象传递给需要它的类,但是为了决定何时在逐个方法的基础上执行连接,您只需在所需的方法中运行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() { }
}
Run Code Online (Sandbox Code Playgroud)
那么,创建这些对象并处理相关依赖项的最佳方法是什么,并且只将数据库对象传递给相关的类?好吧,幸运的是,这两个在使用依赖注入容器时协调一致.
Pimple是一个非常酷的依赖注入容器(由Symfony2框架的制造商提供),它使用PHP 5.3 +的闭包.
痘痘做的方式真的很酷 - 你想要的对象直到你直接要求它才会被实例化.因此,您可以设置一个新对象的负载,但在您要求它们之前,它们不会被创建!
这是一个非常简单的疙瘩示例,您可以在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');
};
Run Code Online (Sandbox Code Playgroud)
然后,在此处添加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();
};
Run Code Online (Sandbox Code Playgroud)
好问题!因此,您已经在引导程序中创建了$container对象,并设置了对象及其所需的依赖项.在路由机制中,将容器传递给控制器.
注意:示例基本代码
router->route('controller', 'method', $container);
Run Code Online (Sandbox Code Playgroud)
在您的控制器,您访问$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.
}
}
Run Code Online (Sandbox Code Playgroud)
所以,你现在已经用一块石头杀死了多只鸟(不仅仅是两只).
注意:在继续之前,我想指出第二点是多么重要.如果没有这个容器,假设您在整个应用程序中创建了50个用户对象.然后有一天,您想要添加一个新参数.OMG - 您现在需要浏览整个应用程序并将此参数添加到每个应用程序中new User().然而,使用DiC - 如果你在$container['user']任何地方使用,你只需将第三个参数添加到容器一次,就是这样.是的,这完全是真棒.
这是使用容器的一种方式,它只是一个开始.有许多方法可以使这更好的-例如,而不是物品转交容器的每一个方法,你可以使用反射/某种映射来决定需要什么样的容器的部分.自动化,你是金色的.
我希望你发现这很有用.我在这里完成它的方式至少为我减少了大量的开发时间,启动它很有趣!