i'm currently constructing some kind of mini-framework for a project, and come up with this solution. I have tried many of them, but this seems to me very convinient (code is shortened for simplicity):
# Basically it's just a Registry pattern
class Repository {
private static $objects = array();
public function loadObject($alias, $object) {
self :: $objects[$alias] = $object;
return true;
}
public function __get($name) {
if ($this->objectExists($name)) {
return self::$objects[$name];
} else {
return false;
}
}
}
class Database extends Repository {
/* database class */
}
class Session extends Repository {
public function some_func($key, $value) {
/* i can access database object using $this in any class that extends Repository */
$this -> database -> exec (/* sql */);
}
}
/* =================== */
# Load core objects
$R = new Repository :: getInstance();
$R -> loadObject ('config', new Config());
$R -> loadObject ('database', new Database());
$R -> loadObject ('session', new Session());
/* =================== */
Can you see any problems or drawbacks with this approach? For me i see maybe i little more memory consumption, because each next class holds more and more objects from Repository.
Before i had a design where each class was independent, but anyway all of them require database, session, config etc, no i had to declare them in any class.
Just wanted to note that i'm planning this design only for core objects, not for specific classes.
"because each next class holds more and more objects from Repository" - I don't exactly understand what you meant by that, I think as the objects are static there's only one copy.
I think you can use a little bit different approach to avoid drawback, by combining singleton pattern.
class Repository
{
private static $instance;
private $objects = array();
private static getInstance()
{
if (!Repository::$instance)
!Repository::$instance = new Repository();
return !Repository::$instance();
}
public static function loadObject($alias, $object)
{
Repository::getInstance()->objects[$alias] = $object;
return true;
}
public static function get($name)
{
$repository = Repository::getInstance();
if (isset($repository->objects[$name]
return $repository->objects[$name];
else
return false;
}
You will then use this in your child classes:
Repository::get('config');
and in bootstrap
Repository::loadObject('config', new Config());
Repository::loadObject('database', new Database());
etc.
Don't extend Repository
:
- A database is not a repository, a repository has a database
- Your database/session/config aren't related and shouldn't be. Liskov substitution principle:
[...] if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).
Edit: trying to answer follow-up questions in this reply.
This technique is called dependency injection. A session example:
class Session {
// notice the clean API since no methods are carried along from a possibly huge base class
public function __construct(ISessionStorage $storage) {
$this->_storage = $storage;
}
public function set($key, $value) {
$this->_storage->set($key, $value);
}
}
interface ISessionStorage {
public function set($key, $value);
}
class DatabaseSessionStorage implements ISessionStorage {
public function __construct(Db $db) {
$this->_db = $db
}
public function set($key, $value) {
$this->_db->query("insert....");
}
}
class CookieSessionStorage implements ISessionStorage {
public function set($key, $value) {
$_SESSION[$key] = $value;
}
}
// example where it's easy to track down which object went where (no strings used to identify objects)
$session = new Session(new DatabaseSessionStorage(new Db()));
$session->set('user', 12512);
// or, if you'd prefer the factory pattern. Note that this would require some modification to Session
$session = Session::factory('database');
$session->set('user', 12512);
Sure you could store connection settings hardcoded in a config-file. This only means the other files need to get hold of that config class without going through their parents. For example:
class Database {
// The same pattern could be used as with the sessions to provide multiple database backends (mysql, mssql etc) through this "public" Database class
public function __construct(Config $config) {
$this->_config = $config;
$this->_connect();
}
private function _connect() {
$this->_config->getDatabaseCredentials();
// do something, for example mysql_connect() and mysql_select_db()
}
}
If you'd prefer to keep config information out of php-files (for easier editing/reading), see the Zend_Config
-classes for examples of accessing different storage devices including the more common ones: ini, php array, xml. (I'm only mentioning Zend_Config since I've used it and am satisfied, parse_ini_file
would do as well.)
A good & hopefully easy read: Fabience Potencier - What is dependency injection?
Edit #2:
Also see the slide: Matthew Weier O'Phinney - Architecting your models
I think it's a bad example. Keeping object such as config, database and session in internal array is unsuitable.
These objects should be explicit (probably static) properties in Your base class and should be accessed by exact name.
Purist probably would say that accessing array this is also slower than directly accessing properties :-))
I would implement these objects as singletons and maybe hide them in something like Application class.
I think overriding __get() is good for objects that have dynamic properties, or when You want to prevent accessing non-existent properties (just throw exception).
This also helps (side effect) against rising notices by PHP (I hate them :-)) when any property is undefined, but notices should be killed explicitly because they are (small but...) just BUGS!
Keep in internal array properties that belong to specific objects (with tens of properties).
I use this solution intensively in classes mapped to db table records.
Regards.