Php 设计模式:如何仅在需要时创建数据库对象/连接?
我有一个简单的应用程序,比如说它有一些类和一个处理数据库请求的“额外”类。目前,每次使用应用程序时,我都会创建数据库对象,但在某些情况下,不需要数据库连接。我是这样做的(PHP btw): 但是有时Php 设计模式:如何仅在需要时创建数据库对象/连接?,php,oop,design-patterns,database-connection,Php,Oop,Design Patterns,Database Connection,我有一个简单的应用程序,比如说它有一些类和一个处理数据库请求的“额外”类。目前,每次使用应用程序时,我都会创建数据库对象,但在某些情况下,不需要数据库连接。我是这样做的(PHP btw): 但是有时$foo对象不需要db访问,因为只调用没有数据库操作的方法。所以我的问题是:处理这种情况的专业方法是什么/如何仅在需要时创建db连接/对象 我的目标是避免不必要的数据库连接。下面是一个简单方法的示例: class Database { public $connection = null ;
$foo
对象不需要db访问,因为只调用没有数据库操作的方法。所以我的问题是:处理这种情况的专业方法是什么/如何仅在需要时创建db连接/对象
我的目标是避免不必要的数据库连接。下面是一个简单方法的示例:
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()) ;
}
}
您可以使用单例模式来实现这一点,并在每次需要数据库时请求一个数据库对象。这导致了类似这样的结果
$db = DB::instance();
class DB {
//...
private static $instance;
public static function instance() {
if (self::$instance == null) {
self::$instance = new self();
}
}
//...
}
其中DB::instance声明如下
$db = DB::instance();
class DB {
//...
private static $instance;
public static function instance() {
if (self::$instance == null) {
self::$instance = new self();
}
}
//...
}
考虑使用依赖项注入容器,类似的东西将是一个很好的开始。使用依赖项注入容器,您可以“教”容器如何在应用程序中创建对象,直到您请求它们,它们才被实例化。使用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();
这大概就是我使用的
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方法中的后期静态绑定
注意:虽然ops问题的直接答案是“我什么时候才能在需要时创建/连接数据库,而不是在每次请求时创建/连接数据库”是在需要时注入数据库,只是简单地说这没有帮助。我在这里解释您实际上是如何正确地进行这项工作的,因为在非特定的框架上下文中确实没有很多有用的信息在这方面提供帮助
更新:此问题的“旧”答案见下文。这鼓励了服务定位器模式,这是非常有争议的,对许多人来说是一种“反模式”。新的答案加上我从研究中学到的东西。请先阅读旧答案,看看进展如何
新答案
在使用粉刺一段时间后,我了解了它的工作原理,以及它到底是如何不那么神奇。它仍然很酷,但它只有80行代码的原因是它基本上允许创建闭包数组。Pimple经常被用作服务定位器(因为它的实际功能非常有限),这是一种“反模式”
首先,什么是服务定位器?
服务定位器模式是软件开发中使用的一种设计模式,用于封装获取具有强抽象层的服务所涉及的过程。此模式使用一个称为“服务定位器”的中央注册表,该注册表根据请求返回执行特定任务所需的信息
我在引导中创建了pimple,定义了依赖项,然后将这个容器传递给我实例化的每个类
为什么服务定位器不好?
你说这有什么问题?主要问题是,这种方法对类隐藏依赖关系。因此,如果开发人员来更新这个类,而他们以前没有看到过它,那么他们将看到一个容器对象,其中包含未知数量的对象。此外,测试这个类将是一场噩梦
我当初为什么这么做因为我认为在控制器之后就是开始进行依赖注入的地方。这是错误的。您可以直接在控制器级别启动它
如果这是我的应用程序中的工作方式:
前端控制器-->引导-->路由器控制器/方法模型[服务|域对象|映射器]控制器视图模板
…然后依赖项注入容器应立即在第一个控制器级别开始工作
所以说真的,如果我仍然使用pimple,我将定义要创建什么控制器,以及它们需要什么。因此,您将将视图和模型层中的任何内容注入控制器,以便它可以使用它。这非常简单,并且使测试更加容易。来自Aurn wiki(我将很快讨论):
在现实生活中,你不会通过把整个五金店(希望是)运到建筑工地来建造房子,这样你就可以获得你需要的任何部件。相反,工头(_construct())要求提供所需的特定零件(门窗)并着手采购。你的目标应该以同样的方式运作;他们应该只询问完成工作所需的特定依赖项。让用户访问整个硬件商店充其量是糟糕的OOP风格,最糟糕的是维护性噩梦
进入奥林
在这一点上,我想向你们介绍一个很棒的东西,叫做,作者是我在周末被介绍给他的
Auryn基于类构造函数签名“自动连接”类依赖项。这意味着,对于请求的每个类,Auryn都会找到它,在构造函数中找出它需要什么,首先创建它需要什么,然后创建您最初请求的类的实例。下面是它的工作原理:
提供程序基于参数递归实例化类依赖项
$injector->alias('Library\Database\DatabaseInterface', 'Library\Database\MySQL');
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();
}
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;
}
}
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
}
}
/**
* @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() { }
}
// 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();
};
router->route('controller', 'method', $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.
}
}
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;
}
}
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;
}
}
}
<?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';
}
?>
<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);
}
}
?>