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


i've simple application, has classes , "extra" 1 handles database requests. i'm creating database object everytime app used, in cases there's no need database connection. i'm doing (php btw):

$db = new database();     $foo = new foo($db); // passing db 

but $foo object not need db access, methods without database actions called. question is: what's professional way handle situations / how create db connection/object when needed ?

my goal avoid unnecessary database connections.

note: although direct answer ops question, "when can create / connect database when required , not on every request" inject when need it, saying not helpful. i'm explaining here how go correctly, there isn't lot of useful information out there in non-specific-framework context in regard.


updated: 'old' answer question can see below. encouraged service locator pattern controversial , many 'anti-pattern'. new answer added i've learned researching. please read old answer first see how progressed.

new answer

after using pimple while, learned how works, , how it's not actually amazing after all. it's still pretty cool, reason it's 80 lines of code because allows creation of array of closures. pimple used lot service locator (because it's limited in can do), , "anti-pattern".

firstly, service locator?

the service locator pattern design pattern used in software development encapsulate processes involved in obtaining service strong abstraction layer. pattern uses central registry known "service locator" on request returns information necessary perform task.

i creating pimple in bootstrap, defining dependencies, , passing container each , every single class instantiated.

why service locator bad?

what's problem say? main problem approach hides dependencies class. if developer coming update class , haven't seen before, they're going see container object containing unknown amount of objects. also, testing class going bit of nightmare.

why did originally? because thought after controller start doing dependency injection. wrong. start straight away @ controller level.

if how things work in application:

front controller --> bootstrap --> router --> controller/method --> model [services|domain objects|mappers] --> controller --> view --> template

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

so really, if still use pimple, defining controllers going created, , need. inject view , model layer controller can use it. inversion of control , makes testing easier. aurn wiki, (which i'll talk soon):

in real life wouldn't build house transporting entire hardware store (hopefully) construction site can access parts need. instead, foreman (__construct()) asks specific parts needed (door , window) , goes procuring them. objects should function in same way; should ask specific dependencies required jobs. giving house access entire hardware store @ best poor oop style , @ worst maintainability nightmare. - auryn wiki

enter auryn

on note, i'd introduce brilliant called auryn, written rdlowrey introduced on weekend.

auryn 'auto-wires' class dependencies based on class constructor signature. means that, each class requested, auryn finds it, figures out needs in constructor, creates needs first , creates instance of class asked originally. here's how works:

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

...and if know php's reflection, you'll know people call 'slow'. here's auryn that:

you may have heard "reflection slow". let's clear up: can "too slow" if you're doing wrong. reflection order of magnitude faster disk access , several orders of magnitude faster retrieving information (for example) remote database. additionally, each reflection offers opportunity cache results if you're worried speed. auryn caches reflections generates minimize potential performance impact.

so we've skipped "reflection slow" argument, here's how i've been using it.

how use auryn

  • i make auryn part of autoloader. when class asked for, auryn can go away , read class , it's dependencies, , it's dependencies' dependencies (etc), , return them class instantiation. create auyrn object.

    $injector = new \auryn\provider(new \auryn\reflectionpool); 
  • i use database interface requirement in constructor of database class. tell auryn concrete implementation use (this part change if want instantiate different type of database, @ single point in code, , it'll still work).

    $injector->alias('library\database\databaseinterface', 'library\database\mysql'); 

if wanted change mongodb , i'd written class it, i'd simple change library\database\mysql library\database\mongodb.

  • then, pass $injector router, , when creating controller / method, this dependencies automatically resolved.

    public function dispatch($injector) {     // make sure file / controller exists     // make sure method called exists     // etc...      // create controller it's required dependencies     $class = $injector->make($controller);     // call method (action) in controller     $class->$action(); } 

finally, answer op's question

okay, using technique, let's have user controller requires user service (let's usermodel) requires database access.

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;     } } 

if use code in router, auryn following:

  • create library\databaseinterface, using mysql concrete class (alias'd in boostrap)
  • create 'usermodel' created database injected it
  • create usercontroller created usermodel injected it

that's recursion right there, , 'auto-wiring' talking earlier. , solves ops problem, because only when class hierarchy contains database object constructor requirement object insantiated, not upon every request.

also, each class has requirements need function in constructor, there no hidden dependencies there service locator pattern.

re: how make connect method called when required. simple.

  1. make sure in constructor of database class, don't instantiate object, pass in it's settings (host, dbname, user, password).
  2. have connect method performs new pdo() object, using 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 $this->host, $this->db etc     } } 
  3. so now, every class pass database have object, not have connection yet because connect() hasn't been called.

  4. in relevant model has access database class, call $this->db->connect(); , continue want do.

in essence, still pass database object classes require it, using methods have described previously, decide when perform connection on method-by-method basis, run connect method in required one. no don't need singleton. tell when connect when want to, , doesn't when don't tell connect.


old answer

i'm going explain little more in-depth dependency injection containers, , how can may situation. note: understanding principles of 'mvc' here.

the problem

you want create objects, ones need access database. you're doing creating database object on each request, totally unnecessary, , totally common before using things dic containers.

two example objects

here's example of 2 objects may want create. 1 needs database access, doesn't need database access.

/**  * @note: class requires database access  */ class user {     private $database;      // note require *interface* here, database type     // can switched in container , still work :)     public function __construct(databaseinterface $database)     {         $this->database = $database;     } }  /**  * @note class doesn't require database access  */ class logger {     // doesn't matter 1 does, doesn't need db access     public function __construct() { } } 

so, what's best way create these objects , handle relevant dependencies, , pass in database object relevant class? well, lucky us, these 2 work in harmony when using dependency injection container.

enter pimple

pimple cool dependency injection container (by makers of symfony2 framework) utilises php 5.3+'s closures.

the way pimple cool - object want isn't instantiated until ask directly. can set load of new objects, until ask them, aren't created!

here's simple pimple example, create in boostrap:

// create container $container = new pimple();  // create database - note isn't *actually* created until call $container['datastore'] = function() {     return new database('host','db','user','pass'); }; 

then, add user object , logger object here.

// create user object database requirement // see how we're passing on container, can use $container['datastore']? $container['user'] = function($container) {     return new user($container['datastore']); };  // , logger doesn't need $container['logger'] = function() {     return new logger(); }; 

awesome! so.. how use $container object?

good question! you've created $container object in bootstrap , set objects and required dependencies. in routing mechanism, pass container controller.

note: example rudimentary code

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

in controller, access $container parameter passed in, , when ask user object it, new user object (factory-style), database object injected!

class homecontroller extends controller {     /**      * i'm guessing 'index' default action called      *      * @route /home/index      * @note  dependant on .htaccess / routing mechanism      */     public function index($container)     {         // so, want new user object database access         $user = $container['user'];         // whaaat?! that's it? .. yep. that's it.     } } 

what you've solved

so, you've killed multiple birds (not two) 1 stone.

  • creating db object on each request - not more! it's created when ask because of closures pimple uses
  • removing 'new' keywords controller - yep, that's right. you've handed responsibility on container.

note: before continue, want point out how significant bullet point 2 is. without container, let's created 50 user objects throughout application. 1 day, want add new parameter. omg - need go through whole application , add parameter every new user(). however, dic - if you're using $container['user'] everywhere, add third param container once, , that's it. yes, totally awesome.

  • the ability switch out databases - heard me, whole point of if wanted change mysql postgresql - change code in container return new different type of database you've coded, , long returns same sort of stuff, that's it! ability swap out concrete implementations harps on about.

the important part

this one way of using container, , it's start. there many ways make better - example, instead of handing container on every method, use reflection / sort of mapping decide parts of container required. automate , you're golden.

i hope found useful. way i've done here has @ least cut significant amounts of development time me, , it's fun boot!


Comments

Popular posts from this blog

linux - xterm copying to CLIPBOARD using copy-selection causes automatic updating of CLIPBOARD upon mouse selection -

c++ - qgraphicsview horizontal scrolling always has a vertical delta -