I'm wanting to write a modular PHP application, so it's easy to download and "install" new features. We'll call the current application "MRF", here's the basic class structure: MRF MRF_SQL extends class SQL This class simply does all of the database stuff for me. It uses PEAR DB and adds just one more layer of simplicity. (Since for web applications I primarily just execute queries and throw the data into arrays anyway) MRF_Smarty extends class Smarty This class just sets the default Smarty variables for the MRF application, so I don't have to do that each time I use it. MRF_auth Just a simple authentication class I threw together that lets me access my organization's authentication protocol So, a sample statement inside the MRF class would look like: PHP: if($this->auth->isLoggedIn) { $data = $this->sql->query("SELECT * FROM table", SQL_ALL, SQL_ASSOC); if(is_array($data)) { $this->tpl->assign("pagedata", $data); $this->tpl->display("datadisplay.tpl"); } } Where $this->auth is the authentication stuff, $this->sql is the database stuff, and $this->tpl is the Smarty templating stuff. So, now if I wanted to write a module that would add a poll to the web application, would I just beef up the MRF parent, add a variable to the MRF parent that would instantiate the new module, or just make an entirely separate POLL class that would have its own instance of the database and templating stuff, so the final layout would be: MRF MRF_SQL MRF_Smarty MRF_Auth MRF_POLL MRF_SQL MRF_Smarty MRF_Auth I'm just trying to figure this out, because the next big project I work on, needs to be able to have the core "engine" with hundreds of optional modules, and I'd prefer to not have to change the core class just to add a module. Any tips?
How about making your "engine" MRF class able to load any other class module in and instantiate it for you? I make a lot of mid-sized PHP based sites at work and we use class modules but don't bother with a kernel ("engine") class generally. I usually find it best to just require_once any modules needed and instantiate them in the site's common include file. For instance, we have a database abstraction class, and can pass an instance of this to a logging class. If the log class doesn't get given a database object to use, it will include the database abstraction module if needed and instantiate it's own. It can get very complex very quickly if you don't plan it all well from the start. I'd recommend having a read of "Professional PHP5". It's a great book that covers this kind of thing in detail and the examples are what I based our module system on.
I'll see if I can get my boss to buy that book. She bought Professional PHP4 and I haven't even opened it yet My question is, how do you get your main class to instantiate it? Don't you have to program for it? MRF looks like this (simplified) PHP: class MRF { private $sql; private $tpl; private $auth; function __construct() { $this->sql = new MRF_SQL(); $this->tpl = new MRF_Smarty(); $this->auth = new MRF_Auth(); } } But, if I had 100 or so modules, then I would have to add a new variable and instantiate it in the main class. I'm more looking for a way to "install" modules by just dropping them in a specific folder. The more I think about this, the more I'm thinking I just need to stop thinking along the lines of having only one object instantiated by the primary script, and just make multiple classes that work well together but aren't neccessarily a part of each other.
I see what you're trying to do but I think you've answered your own question when you said you're thinking about making multiple classes that work well together. Even if purely from a resource usage point of view, having an object of every class instantiated (and even having the classes defined) on every page just for the sake of having them as part of one object is a bad thing. It's also more flexible to be instantiating objects as and when you need them, for instance, what if you had two polls to deal with? Obviously using polls is a bad idea here, but I'm sure you understand what I mean. Don't forget, it's not a bad thing to have a common include that will include all of your basic fundamental classes and create some objects that are used on every page. We have a common include that includes a configuration file (to setup general variables/constants) and a few basic classes. These are things such as the database layer which everything uses for storage (including the sessions), the session object, a GET/POST request handler etc.
Nihilist makes a very good point about instantiation of objects... not least of which is the performance point of view; the more {unused} objects you have the more time it takes to create them, the more memory is used meaning the OS has to swap memory taking CPU time from your app etc. You're point about 'dropping a file into a folder' could only be achieved by writing some meta code which basically had a dictionary of all instantiatable items. I've done something similar but had the ability of using the windows registry. If you wanted to carry this idea through then I would suggest that you use something like the "interface" pattern where all your classes support a virtual interface (a set of predefined functions) which would allow for creation, deletion, population etc. In this way you always know the minimal set of functionality support by any of your classes. A straw-man idea: (A sort of dynamic class factory) 1. Have a directory watcher (lets call it a Sentinel!) that watches for and maintains a list of files in you 'classes directory' 2. When detecting a file it reads it parses out the name of the class and stores it. 3. When ever a class is required, the name of the required class is passed (and say an optional list of construction args) to the sentinel and if it is known, is created on you behlaf and returned back, if not an error can be raised. 4. The sentinel knows that any class it is passed supports an explicit interface of which one (lets call it "Instantiate") is awlays called directly after the constructor. Therefore the Sentinel creates the class then immediately calls "Instanitate" passing in the list of arguments which can be verified by the newly created class itself (oooh! Proper object orientation!) 5. Sentinel passes back new object or error as appropriate. 6. Original call can use, store and manipulate the class as appropiate. Now my only problem with is my lack of PHP knowledge... if you drop a file into a folder, how can you tell PHP (or Apache or whatever) that that file is effectively source code to be used? Can you tell it to dynamically load a file? If PHP is an interpreted language it will have to know about the source file when the interpreter comes to instantiate the file; this is achieved by either an server side include or, explicit file invocation (in ASP) So either the sentinal would have to invoke an function in the new source file by redirection, or (and an off the wall idea) is that the sentinal creates a new file which is the amalgam of all class sources and uses that .... Sorry brain dead now In fact did this ramble even help?
Actually, instead of using a sentinel or anything like that, in PHP I'd just: PHP: function __autoload($class_name) { require_once $class_name . '.class.php'; } Then if I make a class named "POLL" then I just save the file the class is defined in as "POLL.class.php", be sure that the folder with all the classes is in the PHP include path, and off we go! I think that's the way I'll do it in the future.
Nice one-liner there; the point about the sentinal would be that it would invoke that line on determining that a new class file had been dropped into the monitored directory.
OneSeventeen, that's a good way of loading in class modules. Hepath, whilst the "sentinel" idea is nice, it's perhaps not hugely useful in practise. For every class that isn't purely for some sort of backend behind-the-scenes action, you need to know how to use the class anyway. That means you obviously know which class you're using and as such could just include it normally. I do use the sentinel idea for some things. For instance, we have various backends to our logging class. When we instantiate it, we pass it a few variables. One of these is a string of "File", "MySQL" or "PostgreSQL". The class then loads itself up the required storage backend class. The class "loggerBackendMySQL" would be in the file "class.loggerBackendMySQL.php". That way we can add more storage methods quickly and easily if we need, and the main class won't get bogged down with superfluous code. That's another idea I shamelessly stole from the Professional PHP5 book
Thats the way that i do it, i have set up a bunch of resource classes and when i require them i include them im that way. Some that are used all of the time (session handling, database access etc) have a generic script that includes and instanciates all of them, and i just require_once that.