Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
163 views
in Technique[技术] by (71.8m points)

php - Design Patterns: How to create database object/connection only when needed?

I've a simple application, say it has some classes and an "extra" one that handles database requests. Currently i'm creating the database object everytime the app is used, but in some cases there's no need for a database connection. I'm doing it like this (PHP btw):

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

But sometimes the $foo object does not need db access, as only methods without database actions are called. So my question is: What's the professional way to handle situations like this / how to create the db connection/object only when needed ?

My goal is to avoid unnecessary database connections.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.

New Answer

After using pimple for a while, I learned much about how it works, and how it's not actually that amazing after all. It's still pretty cool, but the reason it's only 80 lines of code is because it basically allows the creation of an array of closures. Pimple is used a lot as a service locator (because it's so limited in what it can actually do), and this is an "anti-pattern".

Firstly, what is a service locator?

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.

I was creating pimple in the bootstrap, defining dependencies, and then passing this container to each and every single class I instantiated.

Why is a service locator bad?

What's the problem with this you say? The main problem is that this approach hides dependencies from the class. So if a developer is coming to update this class and they haven't seen it before, they're going to see a container object containing an unknown amount of objects. Also, testing this class is going to be a bit of a nightmare.

Why did I do this originally? Because I thought that after the controller is where you start doing your dependency injection. This is wrong. You start it straight away at the controller level.

If this is how things work in my application:

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

...then the dependency injection container should start working right away at the first controller level.

So really, if I were to still use pimple, I would be defining what controllers are going to be created, and what they need. So you would inject the view and anything from the model layer into the controller so it can use it. This is Inversion Of Control and makes testing much easier. From the Aurn wiki, (which I'll talk about soon):

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

Enter Auryn

On that note, I'd like to introduce you to something brilliant called Auryn, written by Rdlowrey that I was introduced to over the weekend.

Auryn 'auto-wires' class dependencies based on the class constructor signature. What this means that, for each class requested, Auryn finds it, figures out what it needs in the constructor, creates what it needs first and then creates an instance of the class you asked for originally. Here's how it works:

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

...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 AurynProvider(new AurynReflectionPool);
    
  • 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('LibraryDatabaseDatabaseInterface', 'LibraryDatabaseMySQL');
    

If I wanted to change to MongoDB and I'd written a class for it, I'd simple change LibraryDatabaseMySQL to LibraryDatabaseMongoDB.

  • 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(ModelUserModel $userModel)
    {
        $this->userModel = $userModel;
    }
}

class UserModel
{
    protected $db;

    public function __construct(LibraryDatabaseInterface $db)
    {
        $this->db = $db;
    }
}

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

  • Create the LibraryDatabaseInterface, 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.

In essence, you still pass your database object to the classes that require it, using the methods I have described previously, but to decide when to perform the connection on a method-by-method basis, you just run the connect method in the required one. No you don't need a singleton. You just tell it when to connect when you want it to, and it doesn't when you don't tell it to connect.


Old Answer

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.

The Problem

You want to create some objects, but only certain ones need access to the database. What you're currently doing is creating the database object on each request, which is totally unnecessary, and also totally common before using things like DiC containers.

Two Example Objects

Here's an example of two objects that you may want to create. One needs database access, another doesn't need database access.

/**
 * @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() { }
}

So, what's the best way to create these objects and handle their relevant dependencies, and also pass in a database object only to the relevant class? Well, lucky for us, these two work together in harmony when using a Dependen


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...