Injecting an object into an object

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Injecting an object into an object

Post by s.dot »

Across this application I've been building, I need my database wrapper object in a lot of the classes. What's the best way to do this?

I have a couple in mind.

Code: Select all

$obj = new obj(db::getInstance());

//class
class obj
{
     private $_obj;
     function __construct($ojb=false)
    {
        if ($obj) { $this->_obj = $obj; }
     }
}
Which then leaves me the question of how do I access it throughout the object..

Code: Select all

$this->_db->query();
or

Code: Select all

function db()
{
    return $this->_db;
}

//........
$this->db()->query();
Does it matter? Is there a different way I should be doing it?
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
fredrik
Forum Newbie
Posts: 13
Joined: Sun Nov 04, 2007 4:41 am

Post by fredrik »

The easiest way is without a doubt to just pass the database object to every object that needs it, it's not very elegant - but it works. Assuming PHP5:

Code: Select all

<?php
class MyClass {
    protected $db;
    
    function __construct(Database $db = null) {
        $this->db = $db;
    }

    function doSomething() {
        $this->db->query('something');
    }
}
There are other ways to go about this, rated in terms of how advanced they are to implement:
  • Registry
  • Service Locator
  • Dependency Injection / Injector
User avatar
Mordred
DevNet Resident
Posts: 1579
Joined: Sun Sep 03, 2006 5:19 am
Location: Sofia, Bulgaria

Post by Mordred »

viewtopic.php?t=76799&start=15#432212

Global functions!

With a pinch of OOP salt though:

CMysqlDatabase wrapper class.
CPostgreDatabase wrapper class.
(etc)

Note that in PHP you don't actually need to have an abstract parent IDatabase interface, but if you feel uncomfortable without one, just write one.

Based on project configuration, a global variable $g_pDatabase is instantiated of the relevant DB driver class.

A set of global functions use the $g_pDatabase object. I.e.:
function DBQuery($sQuery) {
global $g_pDatabase;
$g_pDatabase->Query($sQuery);
//actually in my implementation I do some other nice stuff as caching the last query result and so on.
}

When using, you just call DBQuery().
No need to Database::getInstance()->Query(), or Registry::get('db')->Query(), or $this->_db->Query(), etc.

On the rare occasion you need a second db connection, then instantiate another object and put it in a member, or a registry or whatever. I find it silly to do so if I would only have one.
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post by John Cartwright »

Ugh, globals..
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

Pass via constructor or Registry. IMO, where a database connection is needed it usually indicates the need for a Model class which encapsulates a model of the data being manipulated/read/written. For example, have a generic set of classes which handle database reading/writing, but on top of those generate some classes with a pretty simple interface.

Best sources to look for are pattern descriptions of Data Mapper, Row Date and Table Data Gateway, Data Objects (I have a tutorial here on the forum for that one which I intentionally simplified to serve as an introduction), ORM.

In my source code I have a Model class (API changes a lot between apps and packages) like:

Code: Select all

$user = new User;
$user->name = 'Pádraic';
$user->save();

Code: Select all

$user = new User;
$user->findFirst(array('name'=>'Pádraic'));
echo $user->name; // Pádraic
Overall point is that User encapsulates the database connection (say PDO) which is sourced from a Registry. Because my application code is unaware of the connection object, and only knows about User and it's simple interface, I don't need to pass the Database object everywhere. In fact, I could replace PDO with MDB2 or ADOdb and it would never know the difference. The second benefit is that by using Model classes, I now have a small collection of objects where any database changes are isolated - I could edit User to use an ADOdb object, or MDB2 object, pretty easily. Even better, User could extend a common "Model" class where the database stuff is handled in one single manageable class.

Worth noting it's considered good practice to make use of Model style solutions - they keep the application code independent of any database access solution, can carry the weight of business logic ($user->findActiveUsers()), and reduce the amount of data manipulation, sorting and filtering application code tends to collect. It can be inefficient since simple solutions assume one object per database row which has led to ever more complex solutions from Data Mappers (which are not bad) to full-blown ORMs like Propel. Sometimes accessing SQL directly in some scenarios is far more efficient, esp. if it improves performance substantially.

I'd say do some reading, see what others say here, and maybe experiement with a few possible Model solutions to see how they fit.
User avatar
Kieran Huggins
DevNet Master
Posts: 3635
Joined: Wed Dec 06, 2006 4:14 pm
Location: Toronto, Canada
Contact:

Post by Kieran Huggins »

Pádraic++

Persistent connections & data models make Kieran happy. If you chained that sucka I'd start hyperventilating!
fredrik
Forum Newbie
Posts: 13
Joined: Sun Nov 04, 2007 4:41 am

Post by fredrik »

Pádraic: While I do agree that going the "Model"-way with Domain Objects and Domain Mappers are a sweet sweet path, it can also require some serious programmatic gymnastics to pull of in a smooth way, especially when we're talking about domain object consisting of more then 1-2 tables and using several foreign keys. I recently started using Propel (and later phpDoctrine which I also dropped for the same reasons) for one of my hobby projects, but had to drop it because it just isn't viable when I had to many tables representing one domain object. Performance was seriously suffering (I just have something against pulling 10 fields from 3 different tables to build one object when I just need to use 1-2 of them each) and the code was beginning to look really iffy at places. All of the PHP O/R-M solutions are also seriously lacking when it comes to doing really advanced queries, which - for me - makes them close to useless for anything then your average blog-type application.

Anyway, back to topic: I would probably use a registry for this, and stop using a singleton (singelton's and abstract+static classes are evil) for the database connection. What happens if you need to connect to two databases at the same time? A static registry is a crude but rather effective solution(Zend uses it in their framework).
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

Use injection over a registry at first...when your application starts to beg for a registry...introduce yet another object, otherwise KISS.
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Post by s.dot »

:(

This topic makes me sad. I thought I had introduced a simple question. But now I have to research registry, data model, ORM, LKJ, JFK, and GWB. :( :P Exaggeration of course, but only a little bit.

I have a long way to go.
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
kyberfabrikken
Forum Commoner
Posts: 84
Joined: Tue Jul 20, 2004 10:27 am

Post by kyberfabrikken »

There's also the option of passing the connection in method calls. This is more fine grained than the other suggestions, but it gives a high degree of decoupling. The problem with passing a connection in the constructor is, that you make the connection a property on the object, which uses it. Properties should belong to the object, otherwise you are really just making the same mistake as global variables, albeit in a much more limited scope.
fredrik
Forum Newbie
Posts: 13
Joined: Sun Nov 04, 2007 4:41 am

Post by fredrik »

scottayy wrote::(

This topic makes me sad. I thought I had introduced a simple question. But now I have to research registry, data model, ORM, LKJ, JFK, and GWB. :( :P Exaggeration of course, but only a little bit.

I have a long way to go.
Honestly, don't feel let down. Imho just go with the simplest solution: Get a registry and pass it to the objects that need access to stuff such as the database connection, etc.

In php5 a _very_ simple registry can be implemented like this:

Code: Select all

<?php
$registry = new ArrayObject; // ArrayObject is a build in object in PHP5+
$registry['db'] = new MyDatabaseClass;


Now you can pass the $registry to all objects that need access to the database class, and you can put other stuff into it that's needed through out the application. So yes, in it's simplest form a registry is just a glorified array. (To those of you that will jump me on this: I'm well aware of all the different aspects of how you can implement a registry, this is just the simplest form which I think suits the original poster)
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

While I do agree that going the "Model"-way with Domain Objects and Domain Mappers are a sweet sweet path, it can also require some serious programmatic gymnastics to pull of in a smooth way, especially when we're talking about domain object consisting of more then 1-2 tables and using several foreign keys.
I feel a lot of gymnastics are unnecessary - don't get me wrong, but a lot of database work can boil down to very simple sets of actions with relatively few complexities. The main problem people have migrating from direct SQL usage to Model-style solutions is the API, the nature of a Model vs Database Row, and how to handle mutliple table dependencies. You can end up with a simple solution with exceptions (where direct SQL is the only feasible approach), a complex solution where simplicity is forced into being an exception, or something in-between. Personally I start with the simplest approach possible (PDO, A lite access layer, and Models which are basically Container objects ripe for additional business logic) and wait for something complex to appear. When it does, I integrate its solution iteratively and press on. Works out for me at least, and being able to spot bottlenecks, slow queries, etc. early as possible lets me avoid most pitfalls. The core system still retains it's original simple feeling - the rest is tacked on carefully on a needs-basis.

Maybe that's why I took one look at Zend_Db and ran for the hills - I never have used it for anything, looked way too complicated for it's own good.
aliasxneo
Forum Contributor
Posts: 136
Joined: Thu Aug 31, 2006 12:01 am

Post by aliasxneo »

I feel kind of stupid after reading all of those posts but nonetheless I'll give my 2 cents :p

In my self-built PHP Framework the way I handle the use of objects across multiple classes is through inheritance. I have one class, called a_main, which handles all of the "tools" of the framework. When an object extends a_main it gets access to every tool in the framework, the database object, system object, router object, etc.

In the constructor of a_main all the classes are setup (db class is connected, smarty templates setup, etc.). Therefor, all I must do to enable my object access to everything is:

Code: Select all

<?php

class myClass extends A_Main
{
    function __construct()
    {
        parent::__construct();

        $test = $this->mysql->query("SELECT * FROM `test`")->fetchRow(); // Now I have access to the MySQL class
    }
}

?>
This enables any object to extend A_Main and not have to worry about how it gets access to all these objects because it's all done in the background.
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

One counter-argument. If everything depends on A_Main - can you reuse them in other applications where A_Main does not exist?

For me, this is the primary argument explaining why encapsulation is a superior method of aggregating objects compared to inheritance.
aliasxneo
Forum Contributor
Posts: 136
Joined: Thu Aug 31, 2006 12:01 am

Post by aliasxneo »

Maugrim_The_Reaper wrote:One counter-argument. If everything depends on A_Main - can you reuse them in other applications where A_Main does not exist?

For me, this is the primary argument explaining why encapsulation is a superior method of aggregating objects compared to inheritance.
True, but to counter that I can re-use them in ANY system which uses my framework. I can literally cut and paste a module from one of my applications into another and have it work flawlessly, and this is exactly what I have done. I have a directory of modules like "members" which contains functionality for implementing a basic member system (authentication, login, etc.).
Post Reply