_____________________________________________________________________________________________
IS THIS A GOOD APPROACH FOR DEALING WITH MANY OBJECTS IN LARGE PHP PROJECTS?
_____________________________________________________________________________________________
Updated: 11th October 2001
Notes:
Class: A class is an abstract description of something (e.g. A computer in general)
Object: An object is the actual instance of the class e.g. (THAT computer overthere, none other than that one).
Having read a great deal about classes and objects on the web and various PHP books, it seems to me that object oriented programming is the way to go. It seems like an excellent way to efficiently structure your code. Each object method is responsible for accepting input and sending an output. You can change things inside your methods, and as long as the output is acceptable, then you don’t need to worry about knock on effects elsewhere in the system.
However… the reality in PHP presents me with some problems. It seems that there are plenty of tutorials on the web for explaining how to design a single class and instantiate single objects, but inter-object communication is far more difficult and discussion avoided!
Ideally you want to be able to instanstiate your objects once, open your database connections once, etc.
Take the following scenario:
The mysql_class is a database abstraction layer that handles all mysql connections and queries and reports any errors. But what happens if you have another class:PHP Code:$code_base = "somewhere/in/the/system/";
include_once ($code_base . "classes/mysql_class.inc");
$mysql = new mysql_class();
The update statistics class is there to monitor client activity, report how many requests he/she are making, where they are making them, length of time it is taking to complete. But this class also needs access to the mysql database.PHP Code:$code_base = "somewhere/in/the/system/";
include_once ($code_base . "classes/update_statistics_class.inc");
$statistics = new update_statistics_class();
What do we do?
There seem to be a few approaches to this issue:
1. Instantiate a new object inside another object
The new statistics class has something like:
So a new mysql object is instantiated within the new object. But this means creating a complete new mysql object, with new open/close connections. So this consumes more memory along with the additional overhead of making the database connection. What happens when we instantiate another object e.g. test_class, and it too requires access to the database. That means another mysql object, more memory, more database overhead.PHP Code:class statistics_class {
function statistics_class() {
$this->mysql = new mysql_class();
}
}
Not happy with this.
2. Pass the global mysql object to the new object using an alias:
When the update_statistics class is instantiated we use:
Then this global mysql object is available from within the new object (passed as a reference/alias):PHP Code:$statistics = new update_statistics_class($mysql);
On first glance, this seems like a really good idea. After all, each object has a pointer to the global $mysql object. This means that we open just one mysql connection per script and each class has time-share on the one object. We don’t need to worry about closing the connection as PHP automatically does this for you when the script ends and we never have to worry about two objects using the mysql connection simultaneously.PHP Code:class statistics_class {
function statistics_class(&$mysql) {
$this->mysql = $mysql;
}
}
So we save on having to create a copy of the mysql object and therefore memory. And we also save on database overhead as we are only opening one connection to the mysql database.
But the downside is that when you have many objects (and you could have as many as 15 as I do), then this can also become a kludge. What happens if you end up with:
Then from inside the object you need to instantiate a new object, which also has to have access to the mysql object.PHP Code:$statistics = new update_statistics_class($mysql, $object1, $object2, $object3 ……);
It’s just messy.PHP Code:class statistics_class {
function some_method($mysql) {
// Invoke new object that is only required from within statistics
// and doesn’t need to be invoked globally.
$some_other_class = new some_other_class($mysql);
}
}
3. Create local aliases to global objects
The last approach is one I am currently using. It still uses the idea of passing global objects around but it’s a cleaner method. You don’t pass the mysql as an alias, but allow a small script inside the class constructor to create local references to your global variables/arrays/objects.
So:
where the create_references.inc file is:PHP Code:class statistics_class {
function statistics_class() {
// Replace with contents of the create_references file. This will make
// local aliases of all global variables including objects such as mysql
// so we don't have to keep creating new instances.
require("classes/create_references.inc");
}
}
Since all our variables/objects are in lower case, we only extract the lower case variables from globals, and we then create a local pointer to each global variable/array/object using the line:PHP Code:<?php
reset($GLOBALS);
while (list($key, $val) = each ($GLOBALS)) {
if (ereg("[a-z0-9]+", $key)) {
print "$key => $val\n<br>\n";
// Make a reference/alias from a local pointer
// to the global variable/array/object. Changing either one will
// affect the value.
$this->{$key} = &$GLOBALS[$key];
}
}
?>
Using this approach, all objects that are now instantiated will have access to all other objects which are already instantiated.PHP Code:$this->{$key} = &$GLOBALS[$key];
e.g. we end up with:
$this->mysql = &$GLOBALS[$mysql];
inside each and every object.
If you were to list all the lower case aliases defined in the current object you might see:
See how all these objects are now available as $this->error->some_method or $this->user->some_variable.Code:argv => Array argc => 1 code_base => /somewhere/in/the/system/ working_directory => members years_active => 1998 - 2001 debug_code => 1 debug => Object user => Object error => Object mysql => Object test => Object
Notice that we also have a user object. Instead of crowding out the global name space with variables that might conflict with each other, we have moved all the user variables into the user object. If for example, we want the name of the remote_user as previously defined by $GLOBALS[remote_user], we simply have to access $this->user->remote_user or $this->user->keyword for a keyword that has been selected in a form.
IT’S ALL VERY CONVENIENT BUT
It’s all very convenient but I get this nagging feeling (doesn’t this always happen!) Shouldn’t all objects be entities unto themselves? What happens if you call a method in the test object e.g. test_method:
One method is now accessing the properties/methods of four other objects (a user object, a mysql object, a statistics object and a basket object). But it could just as easily be 15 objects!PHP Code:class test_class {
function test_class() {
// Replace with contents of the create_references file. This will make
// local aliases of all global variables including objects such as mysql
// so we don't have to keep creating new instances.
require("classes/create_references.inc");
}
function test_method() {
// Requires a user variable
$remote_user = $this->user->remote_user;
// Requires a connection to the mysql method
$this->mysql->query(“some query that uses $remote_user”);
or alternatively:
$this->mysql->query(“select * from some table where user = {$this->user->remote_user}”);
Then update the statistics:
$this->statistics->update_statistics();
Maybe update your basket too:
$this->basket->add_item($item_name, $quantity);
}
}
Or…. should we be using large associative arrays for user information. E.g. we may have form elements that look like:
When posted, we now have the $select associative array inside PHP which consists of two keys (nos_responses, and search_type) and their values. They can be accessed as $select[nos_responses] or $select[search_type] from within PHP. The above example is known as an HTML array (and yes it does work).Code:<select name="select[nos_responses]"> <option value="100" selected>100</option> <option value="500">500</option> </select> <input type="submit" name="Submit" value="Proceed >>"> <input type="hidden" name="select[search_type]" value="Comprehensive">
Then we can just pass this whole associative array to each class by reference:
The above example emphasises the difference between the two approaches outlined above. First we have a local reference to the global mysql object as described previously. The difference is that the ‘select’ associative array is now passed by reference. We are no longer using a user object.PHP Code:$test = $new test_class($select);
class test_class {
function test_class(&$select) {
// Replace with contents of the create_references file. This will make
// local aliases of all global variables including objects such as mysql
// so we don't have to keep creating new instances.
require("classes/create_references.inc");
// Make the pointer available to all other methods in this object
$this->select = $select;
}
function test_method() {
--- Now instead of: ---
$this->mysql->query(“select * from some table where user = {$this->user->remote_user}”);
--- We use: ---
$this->mysql->query(“select * from some table where user = {$this->select[remote_user]}”);
}
}
THE BIG QUESTIONS
What is the best way to approach inter-object-dependency?
If we globalise all the objects and provide local pointers, are we setting ourselves up for problems in the future?
Should we be passing variables/arrays/objects by reference to each new instantiated object?
Are there better ways to think about inter-object communication in PHP?
Hopefully we can stimulate some interesting debate on this topic which hasn't been covered anywhere else to my knowledge.
Mike Mindel
Technical Director
Wordtracker
(www.wordtracker.com)





B as it happens).

(also thanks for the book reference)


Bookmarks