Using classes within other classes

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
VirtuosiMedia
Forum Contributor
Posts: 133
Joined: Thu Jun 12, 2008 6:16 pm

Using classes within other classes

Post by VirtuosiMedia »

Say you have a high level class that requires several low level classes, for example, a form class requiring validation, filter, and an xhtml creation classes. How should the low level classes be instantiated and used?

I can think of a few different ways, but I'm not sure which is the best practice.

1) An instance of the lower level class could be passed in as a parameter. However, to me it seems like you may be needlessly cluttering up your API, plus your methods won't save much time in the end.

2) You could instantiate each low level class in the constructor of the high level class and assign them to class members. However, if not all methods require all of the low level classes, you may be instantiating class instances unnecessarily.

3) Each method could instantiate the classes it needs. However, you may be creating multiple instances of the same object for no reason.

So far, I think I've used a combination of all three methods, but mainly two and three. Still, I'd love to know if there is another option or some sort of rule of thumb to apply in this case. I've been kind of applying it instinctively for when the situation seem right, but I'd like a little more to go on than that. How do you solve this?
User avatar
Eran
DevNet Master
Posts: 3549
Joined: Fri Jan 18, 2008 12:36 am
Location: Israel, ME

Re: Using classes within other classes

Post by Eran »

I use mainly a combination of 2 and 3 and sometimes 1 depending on the requirements. Instances can be passed in the constructor but are optional, and methods requiring instances of other classes lazy load them - meaning they are instanced just before used and saved for later.
For example:

Code: Select all

class Compositor {
    protected $_dbAdapter;
    
    public function __construct($dbAdapter = null) {
        if($dbAdapter instanceof DbAdapter) {
            $this -> _dbAdapter = $dbAdapter;
        }
    }
    
    protected function _connect() {
        if(! $this -> _dbAdapter instanceof DbAdapter) {
            $this -> _dbAdapter = new DbAdapter();
        }
        return $this -> _dbAdapter;
    }
    
    public function useAdapter() {
        $adapter = $this -> _connect();
        //Use adapter;
    }
}
The adapter can be passed in the constructor if it's needed to preconfigure it. Otherwise it will be created just before it's used and cache for the lifetime of the compositor object.
User avatar
allspiritseve
DevNet Resident
Posts: 1174
Joined: Thu Mar 06, 2008 8:23 am
Location: Ann Arbor, MI (USA)

Re: Using classes within other classes

Post by allspiritseve »

VirtuosiMedia wrote:So far, I think I've used a combination of all three methods, but mainly two and three. Still, I'd love to know if there is another option or some sort of rule of thumb to apply in this case. I've been kind of applying it instinctively for when the situation seem right, but I'd like a little more to go on than that. How do you solve this?
Or you could use a registry:

Code: Select all

class MyClass   {
 
function __construct ($registry)    {
    $this->registry = $registry;
}
 
function method()   {
    $stmt = $this->registry->getDb()->prepare ('...');
    // etc...
}
 
}
I don't know the exact pattern, but what I've been doing is having a getter for each shared class I need that handles instantiation and keeps an internal reference to the object so future getDb() requests will always return the same connection. I could be butchering the pattern, but its worked for me.
User avatar
Eran
DevNet Master
Posts: 3549
Joined: Fri Jan 18, 2008 12:36 am
Location: Israel, ME

Re: Using classes within other classes

Post by Eran »

The registry is the OO equivalent of globals. Unless you have a good reason, I'd suggest against it. It's not the end of the world to instance two objects of the same class - it's much worse if you're using the same object in two unrelated contexts where its state can change and create unforeseen problems.
User avatar
allspiritseve
DevNet Resident
Posts: 1174
Joined: Thu Mar 06, 2008 8:23 am
Location: Ann Arbor, MI (USA)

Re: Using classes within other classes

Post by allspiritseve »

pytrin wrote:The registry is the OO equivalent of globals. Unless you have a good reason, I'd suggest against it. It's not the end of the world to instance two objects of the same class - it's much worse if you're using the same object in two unrelated contexts where its state can change and create unforeseen problems.
Not if used correctly. If its instantiated (instead of being static) then it's a scoped global, which isn't as bad. Also, you don't want to create a new database connection every time you make a query-- that would be wasteful. Objects like that, that are pretty much stateless, are better candidates to be shared. Sure, you can pass the connection through the constructor, but often you end up passing instances through the constructor just so you can pass them on to other objects that need them. If you're doing that, or passing more than 3 or so shared instances around, a registry is a good solution. Not a perfect one, since it can lead to the same problems as globals, but an acceptable one (again, if used correctly).
User avatar
Eran
DevNet Master
Posts: 3549
Joined: Fri Jan 18, 2008 12:36 am
Location: Israel, ME

Re: Using classes within other classes

Post by Eran »

Also, you don't want to create a new database connection every time you make a query-
That I agree. For this reason I'd set special objects like that as static class members - which are a better form of scoped globals. However, passing a generic registry around is a pattern I especially dislike (much better having something meaningful like Db::connect() which retrieves the open connection always).

However there are certainly some who'll disagree with me - arborint is a big supporter of the registry object.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Using classes within other classes

Post by Christopher »

pytrin wrote:The registry is the OO equivalent of globals.
This is one of the few things on which pytrin and I disagree. Maybe a static Registry is the "OO equivalent of globals." but as you can see from allspiritsteve's example, this:

Code: Select all

class MyClass   {
function __construct ($registry)    {
    $this->registry = $registry;
}
function method()   {
    $stmt = $this->registry->getDb()->prepare ('...');
    // etc...
}
}
If not really that different from this:

Code: Select all

class MyClass   {
function __construct ($db)    {
    $this->db = $db;
}
function method()   {
    $stmt = $this->db->prepare ('...');
    // etc...
}
}
Except that there is an extra layer of indirection -- so an extra contract. And if you are passing several parameters then using a Registry object is equivalent to passing parameters in an assoc array. Passing named parameters, in either way, can be handy to solve certain problems.
(#10850)
User avatar
allspiritseve
DevNet Resident
Posts: 1174
Joined: Thu Mar 06, 2008 8:23 am
Location: Ann Arbor, MI (USA)

Re: Using classes within other classes

Post by allspiritseve »

pytrin wrote:For this reason I'd set special objects like that as static class members - which are a better form of scoped globals. However, passing a generic registry around is a pattern I especially dislike (much better having something meaningful like Db::connect() which retrieves the open connection always).
Static class members are more global than a registry... at least with a registry you can control what objects have access to it. You can also limit what objects from the registry get passed to specific objects, like a gateway or mapper, that only need a db. With static members, everything potentially has access to it.
User avatar
Eran
DevNet Master
Posts: 3549
Joined: Fri Jan 18, 2008 12:36 am
Location: Israel, ME

Re: Using classes within other classes

Post by Eran »

Because of the generic nature of the registry, it's easy to pass it around between multiple contexts - which makes it much harder to make it accountable for any particular objects. If I have a database class that fetches a connection statically, I know that it's responsible for the connection - and it's also a logical scope to put it in. The scope naming is important to me, otherwise I'd use tons of classes named class1, class2 etc with absolutely no semantics. The lack of semantics is what bothers me the most about the registry.

And at that, a registry instance that is not a singleton is worse in my opinion. The whole point of the registry pattern is to make sure you are always accessing the same one. In more complex applications, registry instances could be created several times if not careful (exactly what a singleton prevents). In addition you need to modify interfaces to accommodate the registry - this is quite a chore and makes the code uglier and less easy to follow in my opinion.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Re: Using classes within other classes

Post by Chris Corbyn »

allspiritseve wrote:Static class members are more global than a registry... at least with a registry you can control what objects have access to it. You can also limit what objects from the registry get passed to specific objects, like a gateway or mapper, that only need a db. With static members, everything potentially has access to it.
Before I do this, I'm neutral on this one. I use a registry for large applications and they come in very useful indeed. They are particularly useful in testing (dependency lookup) where you can replace an object in the registry with a mocked one for the sake of testing (take a DB or something that connects to a remote service for example). Likewise, if you can avoid having the dependency on a registry then why not? A database connection for instance need not be kept in the registry.

Your point regarding a static class member being more global because it cannot restrict access isn't true:

Code: Select all

class Db {
  //Only the DB class can make use of this static
  private static $_connection = null; 
}
 
Both approaches have their merits used correctly. I guess it's clearer how to decouple with a registry than it is if you have to pass dependencies around though.
User avatar
allspiritseve
DevNet Resident
Posts: 1174
Joined: Thu Mar 06, 2008 8:23 am
Location: Ann Arbor, MI (USA)

Re: Using classes within other classes

Post by allspiritseve »

Chris Corbyn wrote:Your point regarding a static class member being more global because it cannot restrict access isn't true:
I meant that you can't restrict any given class from calling Db::connection(). With a registry or passing explicitly through the constructor, you control who has access.
pytrin wrote:In addition you need to modify interfaces to accommodate the registry - this is quite a chore and makes the code uglier and less easy to follow in my opinion.
A registry would probably save you from having to edit an interface more often than it will make you modify one... if a class needs another shared variable, instead of having to edit every constructor that needs it, you can pass it through the registry and the interface needn't change.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Using classes within other classes

Post by Christopher »

Well, I see many reasons why we disagree on this. ;)
pytrin wrote:Because of the generic nature of the registry, it's easy to pass it around between multiple contexts - which makes it much harder to make it accountable for any particular objects. If I have a database class that fetches a connection statically, I know that it's responsible for the connection -
I really don't see how a Registry's "generic nature" or the fact that it can be passed around have anything to do with accountablity. There are really not any more things putting that database connection into the Registry than are putting it into the static factory.
pytrin wrote:and it's also a logical scope to put it in. The scope naming is important to me, otherwise I'd use tons of classes named class1, class2 etc with absolutely no semantics. The lack of semantics is what bothers me the most about the registry.
Again, I don't see how the global scope is a better scope to put it in. And I really haven't a clue what "scope naming" is or what would cause you to name classes "class1, class2". I really must be missing something here.
pytrin wrote:And at that, a registry instance that is not a singleton is worse in my opinion. The whole point of the registry pattern is to make sure you are always accessing the same one.
The point of a Registry is to be able to find common objects -- not that there is only one Registry. Say, hypothetically, your application has multiple Registry objects and they all have a handle to your database object. Which ever Registry you asked for the database object -- you would get it. The important thing is that you can get the common objects. And this is all assuming that you would actually create multiple Registries (like the Zend Controllers have ;) )
pytrin wrote:In more complex applications, registry instances could be created several times if not careful (exactly what a singleton prevents).
This assumes that there is an actual real-world problem where you are not "always accessing the same one." I certainly have never see your theoretical problem.
pytrin wrote:In addition you need to modify interfaces to accommodate the registry - this is quite a chore and makes the code uglier and less easy to follow in my opinion.
I disagree it is a chore. And I agree with allspiritsteve that it is less of a chore than editing parameter lists. To me it is much easier to follow because a Registry makes accessing common objects consistent everywhere.
(#10850)
User avatar
Eran
DevNet Master
Posts: 3549
Joined: Fri Jan 18, 2008 12:36 am
Location: Israel, ME

Re: Using classes within other classes

Post by Eran »

Again, I don't see how the global scope is a better scope to put it in.
Following the database connection example, the logical scope is a database class. And as chris pointed out you can easily restrict the static instance to being accessed only from the database class itself, which mean you would have to instance a database class.
And I really haven't a clue what "scope naming" is or what would cause you to name classes "class1, class2". I really must be missing something here.
When I call Db::connect() I've named my scope DB (sort of like with namespaces). $registry is generic, and therefor has no logical context. Unless you implement specific getters for specific objects, you have no control over the objects it contains (unlike the example chris showed with the database class).
This assumes that there is an actual real-world problem where you are not "always accessing the same one." I certainly have never see your theoretical problem.
Try using the latest Joomla library. They tried to build a framework there and used a mishmash of patterns like locator and registry that make it much more difficult to follow logic around in the source code than it should be.

I've said in the beginning this is a personal preference. Everybody should follow the style that appeals most to them.
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: Using classes within other classes

Post by josh »

VirtuosiMedia wrote:Say you have a high level class that requires several low level classes, for example, a form class requiring validation, filter, and an xhtml creation classes. How should the low level classes be instantiated and used?
Dependency injection + composition. You would tell a $door it has a $handle, then you would tell a $wall it has a $door. Walls do not have knowledge of handles, just doors, they are separate objects the are composed into larger objects. Then you would tell a $house it has a $wall. Each variable holds an instance of an object, functions on $house could just delegate to it's wall, without knowing what kind of wall ( straw, brick, etc... ).

It would be $wolf->blow() // easy to understand

not switch ( $wolf->getHouse()->getWalltype->someOtherGarbage( 'mode3' ); // business logic leaking

Then your "container" classes would require their dependencies to be passed to them at construction, or exceptions would be thrown. You cant create a house without a wall object and you cant create a wall object without a door and you cant create a door without a handle. Contrary to popular belief you don't have to use a registry or pass things to every function. Notice I did not pass a $handle to the house directly. Yet given a house I can locate to its handle, or its door.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Using classes within other classes

Post by Christopher »

pytrin wrote:Following the database connection example, the logical scope is a database class. And as chris pointed out you can easily restrict the static instance to being accessed only from the database class itself, which mean you would have to instance a database class.
The logical scope for a database object is a database class?!? I can think of no reason to get an instance statically from the class except for a Singleton. Unless you are saying that $db = DB::getInstance(); is always better than $db = new DB();
pytrin wrote:When I call Db::connect() I've named my scope DB (sort of like with namespaces). $registry is generic, and therefor has no logical context. Unless you implement specific getters for specific objects, you have no control over the objects it contains (unlike the example chris showed with the database class).
I don't see how Db::connect() is any less or more generic than $registry->getDb()->connect(). Not even sure what generic means in a technical sense here, or whether it is good or bad. If it means polymorphic then that is one positive result of using a Registry. It makes objects, not classes, available. Using "Db::connect()" makes mocking difficult and so you need to edit that call everywhere in you app if you change the DB class -- rather than one place.
pytrin wrote:I've said in the beginning this is a personal preference. Everybody should follow the style that appeals most to them.
I think it is pretty clear that we are (still) not anywhere close on this subject! ;)
(#10850)
Post Reply