0

I'm rather new to OOP and have a question about how to best deal with using one class name, but then adding functionality to that class internally based on a parameter passed.

Basic story is I have an 'item' class which takes 2 parameters $module and $id. Some 'modules' (but not all) will have fancy functionality that the 'item' class won't contain by default. Some modules will also want to overwrite some of the methods in the item class. My goal is to be able to call up a module's extension of the 'item' class from within the __construct of the item class. I want to have the single 'item' class though.

My first thought is to create an instance of the module's item extending class when I construct the item class, then, whenever I call a method in the item class, I'll check to see if I have that module specific instance and call the method on that instance.

For example:

class item {
    // Variable where I'll store the instance of the child class
    var $child;
    // Child class may call up item's constuct, I'll use this to avoid an infinite loop
    var $child_initalized;
    // Child called, guess we have to keep track of this so we don't get stuck in infite loops either.
    var $child_called = 0;
    // Example variables
    var $id, $name;

    function __construct($module,$id) {
        if(!$this->child_initialized) {
            $this->child_initialized = 1; // So we don't do this again and get stuck in an infite loop
            $child_class = __CLASS__.'_'.$module;
            if(class_exists($child_class)) {
                $this->child = new $child_class($module,$id);
                return;
            }
        }

        // do normal class construction stuff here, such as setting $this->id and $this->name
        $this->module = $module;
        $this->id = $id;
    }

    // So we can catch child methods not in parent item class
    function __call($name,$arguments) {
        if($this->child) {
            return $this->child->$name();
        }
    }

    function id() {
        $this->child_called = 0;
        if($this->child) {
            $this->child_called = 1;
            return $this->child->id();
        }
        return $this->id;
    }
}

class item_photos extends item {
    function __construct($module,$id) {
        $this->child_initialized = 1;
        parent::__construct($module,$id);
    }

    function id() {
        return "photo-".$this->id;
    }

    function photo_width() {
        return 250; // Just some sample value
    }
}

$item = new item('photos',123);
print $item->id()."<br />"; // photo-123
print $item->photo_width()."<br />"; // 250

Anyway, that was just a quick example. However, my method seems...well, wrong. Any better way to accomplish this or should I be rethinking how I'm structuring everything? Any advice or ideas you could offer would be great.

/*********** Edit ***********/

Just to clarify, my goal really is to be able to call one class ('item' in my example) and, based on one or more of the params passed to it, to be able to dynamically load more functionality based on those params (in my example, it's $module). But that extra functionality won't always exist (so I can't really always call new item_photos instead of new item()).

Here's an updated example. Calling $item = new item('videos',456) at the end of the code is an example of how some module's won't have extra functionality.

class item {
    // Variable where I'll store the instance of the child class
    public $child;
    // Child class may call up item's constuct, I'll use this to avoid an infinite loop. Also to avoid when calling child class in methods.
    public $child_initalized;
    // Example variables
    public $module, $id, $name;

    function __construct($module,$id) {
        if(!$this->child_initialized) {
            $child_class = __CLASS__.'_'.$module;
            if(class_exists($child_class)) {
                $this->child = new $child_class($module,$id);
                return;
            }
        }

        // do normal class construction stuff here, such as setting $this->id and $this->name
        $this->module = $module;
        $this->id = $id;
    }

    // So we can catch child methods not in parent item class
    function __call($name,$arguments) {
        if($this->child) {
            return $this->child->$name();
        }
    }

    function id() {
        $this->child_initalized = 0;
        if($this->child) {
            $this->child_initalized = 1;
            return $this->child->id();
        }
        return $this->id;
    }
}

class item_photos extends item {
    function __construct($module,$id) {
        $this->child_initialized = 1;
        parent::__construct($module,$id);
    }

    function id() {
        return "photo-".$this->id;
    }

    function photo_width() {
        return 250; // Just some sample value
    }
}

$item = new item('photos',123);
print "photo id: ".$item->id()."<br />"; // photo-123
print "photo size: ".$item->photo_width()."<br />"; // 250

$item = new item('videos',456);
print "video id: ".$item->id()."<br />"; // 456
print "video size: ".$item->photo_width()."<br />"; // nothing (that method doesn't exist here)

/********** Edit 2 **********/

Still struggling a bit. I looked into abstract factory's and they don't seem like they'll work. If I understand them correctly, I'd have to declare all methods in the abstract class which is tricky as the modules would be extending the item class so I wouldn't know all the methods. Plus, it seems I'd have to call 'item_photos' or 'item_videos' instead of just 'item' which is what I'm trying to avoid. Not to mention in my example item_videos doesn't even exist.

In view of my goal of calling one class then, inside that classes __construct, determining if there is any extra functionality that needs to be included based on the $module, I've come up with this.

Basically what was the 'item' class gets moved to a new 'item_core'. The old item class now just determines which class to use, either the module's item class if it exists (ex: item_photos) or the core item class (item_core). After I initialize one of those classes I store the instance and use that whenever I call a method (via __call, let me know why it's bad to use __call).

class item {
    // Instance of the core class or module class
    private $instance;

    function __construct($module,$id) {
        $class = __CLASS__;
        $class_core = $class.'_core';
        $class_module = $class.'_'.$module;

        // Module class
        if(class_exists($class_module)) $this->instance = new $class_module($module,$id);

        // Core class
        else $this->instance = new $class_core($module,$id);
    }
    // Catch all calls, redirect accordingly
    function __call($name,$arguments) {
        if($this->instance) {
            if(method_exists($this->instance,$name)) return $this->instance->$name();
        }
    }
}
class item_core {
    public $module, $id;

    function __construct($module,$id) {
        $this->module = $module;
        $this->id = $id;
    }

    function id() {
        return $this->id;
    }
}

class item_photos extends item_core {
    function __construct($module,$id) {
        parent::__construct($module,$id);
    }

    function id() {
        return "photo-".$this->id;
    }

    function photo_width() {
        return 250; // Just some sample value
    }
}

$item = new item('photos',123);
print "photo id: ".$item->id()."<br />"; // photo-123
print "photo size: ".$item->photo_width()."<br />"; // 250

$item = new item('videos',456);
print "video id: ".$item->id()."<br />"; // 456
print "video size: ".$item->photo_width()."<br />"; // nothing (that method doesn't exist in the videos module)

Anyone see any problems with this or anyway to improve it? It still seems a bit off to me.

/**** Edit 3 *******/

Alright, seems to me the thing I want to do is be able to return a different instance than I called up. So, call new item($module,$id);, but if $module == photos (in my example) I'd want to return an instance of new item_photos($module,$id);. So far I've come up with 2 possible solutions.

First is what I outlined in Edit 2 (see above).

Second option would be to simply call a function (probably 'item') and return the correct object instance there (since functions can return any object instance you want while the class constructor only returns an instance of its own class). So (basic example):

function item($module,$id) {
    $class_module = 'item_'.$module;
    // Module class
    if(class_exists($class_module)) return new $class_module($module,$id);
    // Core class
    else return new item($module,$id);
}
class item{..}
class item_photos{..}

$item = item('photos',123);
print $item->id(); // photo-123

Not sure I like mixing function/classes, but it does the trick pretty simply.

Thoughts?

Charlie
  • 323
  • 2
  • 12

3 Answers3

2

Instead of creating the child class internally, you should create it externally and inject it into the item class (dependency injection is something you may want to look up). You may want to do that with a factory in your controller. The use of __call is also a bit shady and breaks the interface. I'm not sure why you'd need a level of abstraction around item_photos .. you could just engage that object itself.

Explosion Pills
  • 188,624
  • 52
  • 326
  • 405
  • Thanks, I'll take a look at dependency injection. It'd probably be better to simply call new item_photos, but not all modules are going to have classes that extend 'item'. Also, several other programmers are going to be working on this and, to be frank, I'm not sure they'd understand they'd have to use item() sometimes (if there's not extending class for that module) and use item_photos (or whatever module it is) for others. – Charlie May 02 '12 at 00:14
2

It looks like what you're trying to implement can be achieved by the factory design pattern. In a nutshell, the pattern is good when you want to pass in a bunch of configuration options and get something back that represents the object you want to work with. Rather than trying to alter your Item class from the inside, why don't you create an ItemFactory class that returns a new object. For example, you ask the factory for a photo or video and it returns a Photo class or a Video class (which extend from a generic Item class) that has only the things it needs and nothing extra.

Here is an article that explains the pattern in more detail with PHP examples: http://sourcemaking.com/design_patterns/abstract_factory/php/2

Andrew
  • 227,796
  • 193
  • 515
  • 708
  • Went with a sort of factory design. It appears in a factory design I'd have to create the factory instance, then call a method on the factory to return an instance of the correct item instance (item or item_photos, etc.) which is an extra step I was hoping to avoid. See my 'Edit 2' and 'Edit 3' for 2 different versions I came up which avoid 2 calls, but aren't the most glamorous. I'm going to test the speed of each and pick which ever is faster. Thanks Andrew (and all) for your help. – Charlie May 03 '12 at 21:12
  • Glad I could help. =] You might be interested in using a static method for returning the instance. Something like `ItemFactory::getInstance('photo')` which means you won't need an instance of `ItemFactory`. But it's easy to start overusing static methods, so use them wisely. =] – Andrew May 03 '12 at 23:18
1

I think you got it all wrong.

Let's say you want to implement a class A. And a class B which shares some common attributes with A but with MORE functionality. And then a class C, and so on...

Here's how I'd do it :

  • Figure out which are the minimum common attributes of your classes
  • Implement them as - let's say - a BasicItem class. (you may not even use this class per se, but just use it for your... "derivative" classes)
  • Base all your other A,B, whatever classes on top of that (using inheritance).
Bart
  • 19,692
  • 7
  • 68
  • 77
Dr.Kameleon
  • 22,532
  • 20
  • 115
  • 223
  • I agree with this answer, you should use inheritance on top of a 'mother' class which contains all the common methods. – Ozzy May 02 '12 at 00:36
  • The issue with inheritance (if I'm understanding that 'inheritance' means using 'extend') is that some module's won't have extra functionality so there will be no class that extends 'item' (see the call to $item = new item('videos',456); I added at the end of the code). Of course, I could require that any module I or another programmer makes needs to have a class that extends item, even if if does nothing, but that seems a bit odd. – Charlie May 02 '12 at 00:46
  • @Charlie If you need to create an **instance** of a **class** with NO extra functionality other than what's included in your "mother" (a.k.a. `BasicItem`) class, why not use your "mother" class itself? *(I honestly think you're confused as far as classes and inheritance goes; usually, when you go as far as trying to implement something weirdly complex that seems almost impossible, it's simply because your take is wrong)*. – Dr.Kameleon May 02 '12 at 00:50
  • Thanks for the comment Dr.Kameleon and I may take may be wrong. However, the problem isn't that I need NO extra functionality. It's that sometimes I need no extra functionality and sometimes I do, depending on the params that get passed to the class (the $module param to be specific). My goal is to be able to use one class and to be able to add functionality to that class IF extra functionality is available (which is determined by the $module param). – Charlie May 02 '12 at 16:30
  • @Charlie Why use 1 class then (and modify with the `$module` param) and not use 2, instead? (Does that sound too nonsensical?) – Dr.Kameleon May 02 '12 at 21:15
  • So one class for regular items ('item') and one for the photos class ('item_photos') or any other class with special functionality (ex: 'item_articles')? Or is there some method you can think of to use a 2nd class within the 1st (see my latest example for an example of how I'm doing this right now)?If it's the former, I'm really trying to avoid using 2 class names. I know I can do that, but I'm trying to avoid it. – Charlie May 02 '12 at 22:47
  • *(I know this discussion has almost turned philosophical, but I can't help it...)* `I'm trying to avoid it`. **Why**? – Dr.Kameleon May 02 '12 at 22:57
  • Well, my explanation is mostly case specific. At any rate... This is all for a CMS which contains modules. Each module has items. A module can be configured many ways, but the goal is to access & process all items in any module through a basic collection of core code. Undoubtedly I'll need to extend that core functionality for certain modules, but many programmers will be working on this and I want to make it as simple for them to both access & manipulate data. I don't want to have to explain that to get an item from module X you call item_x, to get an item from module Y you call item, etc. – Charlie May 02 '12 at 23:28