Click here to go back to the previous section: Delegation
Please note that this documentation relates to Vanilla 1
The ObjectFactory is used throughout 99% of Vanilla to instantiate classes. In this document we will discuss how the ObjectFactory is currently used in Vanilla, and how extension authors can take advantage of it. Please be aware that the ObjectFactory is meant to be used as a last-ditch effort only. You *should* be able to achieve just about anything in Vanilla by making your own controls and/or using delegation.
The ObjectFactory is used to instantiate classes by a label associated with their name. Using the ObjectFactory I have set up a system of references to class names. Based on the reference contained within the factory, it will instantiate a different object. Let’s begin by examining how the ObjectFactory instantiates objects.
Typically an object will be instantiated in PHP like this:
$MyObject = new MyClass();
With the ObjectFactory, classes are instantiated with one of the following two methods:
// Method 1 $MyObject = $Context->ObjectFactory->NewObject($Context, "MyClass"); // Method 2 $MyObject = $Context->ObjectFactory->NewContextObject($Context, "MyClass");
To understand what the difference is between these two methods, let’s take a look at them:
function NewContextObject(&$Context, $ClassLabel, $Param1 = "", ... $Param10 = "") { return $this->CreateObject($Context, $ClassLabel, 1, $Param1, ... $Param10); } function NewObject(&$Context, $ClassLabel, $Param1 = "", ... $Param10 = "") { return $this->CreateObject($Context, $ClassLabel, 0, $Param1, ... $Param10); }
Note: For the purpose of this documentation, I have not added $Param2 through to $Param9 in the examples above or below.
Both of these methods call the CreateObject method:
function CreateObject(&$Context, $ClassLabel, $IsContextObject, $Param1 = '', $Param2 = '', $Param3 = '', $Param4 = '', $Param5 = '', $Param6 = '', $Param7 = '', $Param8 = '', $Param9 = '', $Param10 = '') { if (!array_key_exists($ClassLabel, $this->ClassIndex)) { // If the class has not yet been defined, assume the class label is the class name $ClassName = $ClassLabel; } else { $ClassName = $this->ClassIndex[$ClassLabel]; } if (!class_exists($ClassName)) { $PrefixArray = $Context->Configuration['LIBRARY_NAMESPACE_ARRAY']; $PrefixArrayCount = count($PrefixArray); $i = 0; for ($i = 0; $i < $PrefixArrayCount; $i++) { $File = $Context->Configuration['LIBRARY_PATH'].$PrefixArray[$i].'/'.$PrefixArray[$i].'.Class.'.$ClassName.'.php'; if (file_exists($File)) { include($File); break; } } // If it failed to find the class, throw a fatal error if (!class_exists($ClassName)) $Context->ErrorManager->AddError($Context, 'ObjectFactory', 'NewObject', 'The "'.$ClassName.'" class referenced by "'.$ClassLabel.'" does not appear to exist.'); } if ($IsContextObject) { return new $ClassName($Context, $Param1, $Param2, $Param3, $Param4, $Param5, $Param6, $Param7, $Param8, $Param9, $Param10); } else { return new $ClassName($Param1, $Param2, $Param3, $Param4, $Param5, $Param6, $Param7, $Param8, $Param9, $Param10); } }
One thing to note here is that the ObjectFactory first attempts to see if the requested class name has already been defined. If it hasn’t been defined, it begins searching the library for the file. This was done so that not all libraries needed to be included on every page load. Instead the object factory will include the libraries as they are instantiated within the page.
More relevant to the discussion at hand: as you can see by the last six lines of the method, calling the NewContextObject method will create a new instance of your class and pass the context object into your new class’ constructor by reference. Calling the NewObject method will create a new instance of your class and will NOT pass the context into your class’ constructor.
The result of these two methods is that any class instantiated with the NewContextObject method will have access to everything that the Context object has to offer, like: session data, the database connection, warning and error managers, etc.
The ParamN variables are optional variables passed into your class’ constructor. This allows you to optionally pass up to 10 different variables into your constructor for whatever purpose you may require.
Please keep in mind that if you are using the NewContextObject method of instantiating a class, your class’ constructor MUST contain the Context object as your first parameter and it MUST be passed in by reference. For example:
class MyNeatClass { var $Context; // Class constructor function MyNeatClass (&$Context) { $this->Context = &$Context; } function SomeOriginalMethod() { // Do something original } }
The ObjectFactory class also contains an associative array called ClassIndex that stores class label & class name pairs. As you saw with the construction methods above, if the ObjectFactory is told to instantiate a class for which the label is not defined, it will assume that the label is the same as the class name. This is done to save code and time. The end result is that you can create a class called MyNeatClass and not set a reference in the ObjectFactory, and then when you instantiate MyNeatClass with one of the construction methods, the ObjectFactory will essentially perform a straight up class instantiation like PHP does with MyNeatClass as the class name.
But now what if another programmer came along and wanted to extend your MyNeatClass and add a new method to it? Here’s how he could do it:
class AnotherClass extends MyNeatClass { function SomeOriginalMethod() { // Now it does something different } }
Now he wants to make sure that his new class is used throughout the entire application, so he needs to change the ObjectFactory’s reference to MyNeatClass. He can do this through the ObjectFactory’s SetReference method:
$Context->ObjectFactory->SetReference("MyNeatClass", "AnotherClass");
So, from this point forward, any time the ObjectFactory tries to instantiate a new object based on the MyNeatClass reference, it will actually instantiate AnotherClass instead.
The biggest shortcoming of the ObjectFactory is that once a new reference has been set, it may break an extension if the reference is changed again.
It is also worthy of note that the ObjectFactory is instantiated in the constructor for the Context object. As a result of this, neither the context class, nor any of the classes instantiated in it’s constructor can be overridden by the ObjectFactory.
The ObjectFactory is an extremely powerful tool in the extension writer’s arsenal, and allows just about anything to be done with Vanilla. But it should be documented that a class has been overridden so that conflicting extensions can be quickly identified should problems arise. Furthermore, it is important to realize that there should almost never be a need to override a class in this way but instead you should accomplish these tasks through the use of delegation or creating custom classes.
If you’ve just finished reading the entire section on Developing New Extensions and you still have questions, you should join the Lussumo Community Forum: we’re here to help!