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?
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