I'm writing my own OOP framework, partially as a learning exercise, but knowing this codebase isn't going to disappear - only evolve as I learn. Below is some psuedocode of what my setup looks like. In this setup, how would I share DB connections? Do a ctrl+f for "connection" to find the places in the code I'm specifically unsure of how to structure.
A routing file would look like this:
switch($urlParameters['action']){
case 'lading':
//etc etc
break;
case 'userCP':
//etc etc
break;
case 'dashboard':
default:
$page = new dashboard();
$page->route($urlParameters);
break;
}
A page would look like
<?php
abstract class page{ // Page is a mixture of controller and view. Most view logic is frontend, so I do all my controller-view logic here
public var $models;
public var $session;
public var $user;
function __construct(){
$this->models = new models();
$this->session = $this->models->session(new MongoID($_COOKIE['session'])); // Plus some code to prevent session hijacking
$this->user = $this->models->users($this->session->userID);
}
function outputMongoData(){} // Wrapper function used by pages to output Mongo datatypes as regular json (Dates to RFC, IDs to string, rounding floats, etc)
function template($file, $data){} // This uses output buffering and extract() to use php its self as the templating language.
}
class dashboard extends page{
public var $dashboardView;
function __construct(){
parent::__construct();
$this->dashboardView = $this->models->dashboardView($this->user->dashboardViewID);
}
function route($urlParams){
$this->session->updatePageViewCountOrSomething();
echo $this->template('dashboard.php', [
'importantData' => $this->dashboardView->someTypeOfDataFromDashboardView,
'viewCount' => $this->session->pageViewCount
]);
}
}
And the models look like
<?php
class models{
private var $cache = [
'users' => [],
'dashboardViews' => [],
'sessions' => [],
];
// Cache to prevent collisions since we're doing save() vs individual updates, plus saves overhead of querying multiple times (potentially hundreds of times for certain models)
function __construct(){
// Potential connection sharing stuff here
}
private function modelFactory($collection, $modelName, $identifier){
$stringID = (string) $identifier;
if(!isset($this->cache[$collection][$stringID])){
$this->cache[$collection][$stringID] = new $modelName($identifier);
}
return $this->cache[$collection][$stringID];
}
public function session(MongoID $sessionID){
return $this->modelFactory('sessions', 'session', $sessionID);
}
public function user(MongoID $userID){
return $this->modelFactory('users', 'user', $userID);
}
public function dashboardView(MongoID $dashboardViewID){
return $this->modelFactory('dashboardViews', 'dashboardView', $dashboardViewID);
}
}
abstract class model{
public var $db;
public var $collection;
private var $data = [];
function __construct($collectionName){
$this->db = // ??? Not sure how to connect so that I don't have a new connection for every single model..
$this->collection = $this->db->{$collectionName};
}
function __destruct(){
$this->collection->save($this->data);
}
function __get($fieldName){
return $this->data[$fieldName]; // if isset, etc etc
}
}
class session extends model{
// Explanation of schema here
function __construct(MongoID $sessionID){
parent::__construct('sessions');
$this->data = $this->collection->findOne(['_id' => $sessionID]); // or if not found, create..
}
function updatePageViewCountOrSomething(){
$this->data['pageViewCount'] += 1;
$this->data['orSomething'] = ['something' => 'or another'];
}
}
class dashboardView extends model{
// Explanation of schema here
function __construct(MongoID $dashboardViewID){
parent::__construct('dashboardViews');
$this->data = $this->collection->findOne(['_id' => $sessionID]); // or if not found, create..
}
function addColumns(){}
function reorderColumns($newOrder){}
}
From your example, it looks like the models class is functioning as a factory for your model instances. In that case, you could use it as the connection container and then pass the appropriate MongoCollection instance to each model class instead of the collection name.
I'll also note that in the PHP driver, there is not really a concern with constructing multiple MongoClient objects. The driver uses persistent connections internally, so constructing five MongoClient instances all with the same host/port/user/password combination will still only create a single network socket behind the scenes. You can test this out for yourself using the example in this slide and the one following.
Some general concerns with your model implementation:
$data is private to the base model class, but you access it in the child classes. It should probably be protected.
Waiting until __destruct() to save an object back to the database seems like a bad idea, as it could happen at the end of the script or sometime earlier if the object is garbage-collected. Having a predictable point in the application to write data seems preferable.
Instead of using this model for persisting session data, you can read up on the SessionHandlerInterface in PHP 5.4+. That allows you to implement a custom storage backend (MongoDB in this case) to be used by the standard PHP session API (e.g. $SESSION).
A great starting point before creating a framework (even as a thought exercise), would be to read up on design patterns. Anthony Ferrara's blog is a great resource and starting point for the subject. I would also suggest learning about dependency injection and service containers, as that's very relevant to your original question about sharing a common resource (e.g. database connection). Fabien Potencier did a great blog series on the subject. The article is a few years old and references the Symfony 1.x implementation (Symfony2 is the current model he uses), but the early posts will likely serve as a useful introduction.
Related
In an application I'm building there's a CLI entry point class:
class CLIEntryPoint {
protected $factory;
public function __construct(ApplicationObjectFactoryInterface $factory) {
$this->factory = $factory;
}
public function run(...$args) {
$choice = $args[1];
$appObject = $this->factory->makeApplicationObject($choice);
$appObject->doApplicationRelatedStuff();
}
}
This entry point is created using Dependency Injection in my "front controller" script and it receives an ApplicationObjectFactoryInterface implementation (actually the current implementation of ApplicationObjectFactoryInterface is injected by the DI container, which in turn reads it from its configuration file, but that's not the point).
The current implementation of ApplicationObjectFactoryInterface also uses DI and depends on other factories which help it building the resulting application object:
class CurrentImplementationOfApplicationObjectFactory implements ApplicationObjectFactoryInterface {
protected $someComponentFactory;
protected $anotherComponentFactory;
public function __construct(SomeComponentFactoryInterface $someComponentFactory, AnotherComponentFactoryInterface $anotherComponentFactory) {
$this->someComponentFactory = $someComponentFactory;
$this->anotherComponentFactory = $anotherComponentFactory;
}
/**
* Interface's method
*
* #return ApplicationObjectInterface
*/
public function makeApplicationObject($choice) {
$component = $this->someComponentFactory->makeSomeComponent();
$anotherComponent = $this->anotherComponent->makeAnotherComponent();
switch ($choice) {
case 1:
return new CurrentImplementationOfApplicationObject1($component, $anotherComponent);
case 2:
return new CurrentImplementationOfApplicationObject2($component, $anotherComponent);
default:
return new DefaultImplementationOfApplicationObject($component, $anotherComponent);
}
}
}
Here CurrentImplementationOfApplicationObject1, CurrentImplementationOfApplicationObject2 and DefaultImplementationOfApplicationObject all implement the ApplicationObjectInterface interface and therefore they all have the doApplicationRelatedStuff method.
I would like to know whether it's good practice or not to write code like I did and if not how can I improve it.
Basically here I am creating a component which depends on other components in order to function properly using a factory which in turn needs inner factories to build the component which implements the ApplicationObjectInterface interface.
Is it considered good practice?
Thanks for the attention, as always!
EDIT: I looked at the article of Steven and tried to refactor CLIEntryPoint. The only problem now seems to be how to pass the $choice parameter to the factory which now is inside of the proxy when the run() method is called. Is this code structure better than the one I posted above? Of course, SomeComponentFactoryInterface and AnotherComponentFactoryInterface should follow the same behaviour (the factory that uses them should not use them directly, but through two proxies which implement, in order, SomeComponentInterface and AnotherComponentInterface). I hope I get it right, anyway, here is the code:
class CLIEntryPoint {
protected $applicationObject;
public function __construct(ApplicationObjectInterface $applicationObject) {
$this->applicationObject = $applicationObject;
}
public function run(...$args) {
$choice = $args[1]; // How do I deal with different choices when I am using a Proxy? I should have different application objects depending on input.
$this->applicationObject->doApplicationRelatedStuff();
}
}
interface ApplicationObjectInterface {
public function doApplicationRelatedStuff();
}
class ApplicationObjectProxy implements ApplicationObjectInterface {
protected $applicationObjectFactory;
protected $applicationObjectImplementation = NULL;
public function __construct(ApplicationObjectFactoryInterface $factory) {
$this->applicationObjectFactory = $factory;
}
public function __call($method, $args) {
// Calling interface's
$implementation = $this->getImplementation();
$methodOfInterfaceToCall = preg_replace('/Proxy$/', '', $method);
return $implementation->{$methodOfInterfaceToCall}(...$args);
}
/**
* Laxy loading method.
*/
protected function getImplementation() {
if (is_null($this->applicationObjectImplementation)) {
$this->applicationObjectImplementation = $this->applicationObjectFactory->makeApplicationObject(); // Choice should go here somehow...
}
return $this->applicationObjectImplementation;
}
public function doApplicationRelatedStuff() {
// This will call the PHP's magic `__call` method, which in turn will forward the call to the application object's
// implementation returned by the factory.
return $this->doApplicationRelatedStuffProxy();
}
}
Actually yes, this is a pattern called the Abstract Factory Pattern. So an example that I used to present it in front of my class during my undergrad:
So if you are building a video game first person shooter, you might want to create three concrete factories like:
FlyingMonsterFactory
SwimmingMonsterFactory
WalkingMonsterFactory.
All these factories would implement an abstract MonsterFactory.
With this, you can have your video game create a level in which you want waves of the same type of monsters, so you can have a randomWaveMonsterGenerator method return a MonsterFactory which might have returned a concrete SwimmingMonsterFactory. So then you will have a wave of SwimmingMonster(s) generated by the SwimmingMonsterFactory.
So answer your description more directly, looking at your code above, you asked the question on choice for Dependency Injection. With Dependency Injection, I believe for this type of pattern, you will have to inject every concrete class before your code even attempts to get the implementation class.
So for example:
Your code above says the run method gives an argument called
choice.
With this choice, you will have to use it as a parameter into a getImplementation method.
All the concrete objects that the getImplementation method that rely upon Dependency
Injection have to be created BEFORE you call the getImplementation method.
But since you don't know which implementation class will be called, I believe you have to inject ALL the implementation classes before hand.
Then you can use the choice variable as a parameter to get the correct implemented factory class.
Hope this helps!
I have read a lot in the past few days about domain objects, data mappers, and a bunch of other stuff I had no idea about.
I have decided to try and implement this in a bit of code I am writing (partly for learning purposes, and partly because I want to create a REALLY simplified framework to build a few projects quickly...with code that I can easily understand and modify).
After reading this and this, I was planning on creating a SINGLE data mapper, with a connection to the DB inside of it, and then use a factory to pass the data mapper into every domain object (well, the ones that would need it). I include some sample code below
class data_mapper {
private $dbh;
function __construct()
{
$this->dbh = new PDO(DB_STRING, DB_USER, DB_PASS);
}
public function createUser($data) ...
public function updateUser($user_id, $data) ...
public function createCategory($data) ...
}
class user {
private $data_mapper;
public $user_id;
public $data;
function __construct($dm)
{
$this->data_mapper = $dm;
}
function someFunction() {
/* some code */
$this->data_mapper->updateUser($user_id, $data);
/* some more code */
}
}
class factory {
private $data_mapper = null;
function __construct($dm)
{
$this->data_mapper = $dm;
}
public function create($name)
{
return new $name($this->data_mapper);
}
}
/* USAGE */
$dm = new data_mapper();
$factory = new factory($dm);
$user = $factory->create('user');
I am left with two questions:
A lot of recent examples I've looked at create a different data_mapper for each model. Should I be doing this? And if I do, wouldn't that make the factory much more complex (i.e. I would need to create single PDO object and pass that into each data mapper, and then pass the right data mapper into each model)?
If my above code exposes some flaw in the understanding of models, data mappers or anything else, please enlighten me (not really a question, i know)...
As far as I can tell, "data mapper" pattern implemented in modern frameworks in the form of prototype Model class, from which all application models are inherited.
In this prototype model you can implement CRUD methods and thus your models will possess it.
Speaking of passing pdo around, local scholars will tell you that you should pass PDO object as constructor parameter. But if you'll take a look at any modern framework - they are using some sort of singleton that contains a PDO instance
So, you want a REALLY simplified PHP framework. Data mappers sound like over-engineering.
Over the years i made a few KISS frameworks in PHP, this is what i did:
Use templates (aka view) such as Smarty. Great for outsourcing your webdesign.
Make a folder named pages (aka controller). Pages are called by index.php only.
Make a folder named models. Only models talk with your DB.
Make a index.php (aka router). Has a ?page=dog parameter.
Strict MCV (aka MVC) terminology is not the holy grail, the above is a nice implementation for a simple website/app/CMS.
The parts
/pages/page_dog.inc.php
A page loads the model(s) he needs, manipulates and shows it:
<?php if(!defined('YOURFRAMEWORK')){die('External access denied');}
// Page init
require './models/model_dog.inc.php';
$id = $_GET['id']; // todo fix injection attacks
$ModelDog = new ModelDog($DB);
// Page main
$ModelDog->Load($id);
echo $ModelDog->item['breed'];
For listings (a page where user selected the $id) you may not want seperate models representing each result. Make a lister class instead, much like the model but returning multiple items in one array. Its tempting to DRY and make the ListerDog class use the ModelDog but there is no readability gain just performance pain.
/index.php (aka router) calls a page (via require_once()) after auth and init ($DB):
<?php
define('YOURFRAMEWORK', 1); // disable "External access denied" error.
require_once('config.inc.php'); // todo have this hold the $config[] array.
$DB = #new mysqli( // or your derative, so you can log each query() call.
$config['db']['host'],
$config['db']['user'],
$config['db']['pasw'],
$config['db']['database']
);
if ($DB->connect_error) { die('db error: ' . mysqli_connect_errno()); }
// Load page requested by user. For now, its dog hardcoded.
require_once('./pages/page_dog.inc.php');
$DB->close;
/models/model_dog.inc.php (aka model) talks to the DB for you, processes and sanitizes data. I also use this put form processing functions.
<?php if(!defined('YOURFRAMEWORK')){die('External access denied');}
class ModelDog extends BaseModel {
private $tablename = 'dogs';
/**
* Load last (or specific) item.
* #param integer $id
* #return boolean Returns false when failed.
*/
public function Load($id=null) {
$query = "SELECT * FROM `".$this->tablename."` WHERE `id`='".$this->DB->Sanitize($id)."';";
// TODO .. $this->item =
}
public function ItemDefaults() {
return array(
'id' => 0,
'breed' => 'unknown',
'height' => 0
);
}
// TODO ..
}
/models/basemodel.inc.php extend every model class from something common like:
abstract class BaseModel
{
protected $item = array(); // Here is all the data!
protected $DB = null;
public function __construct($aQDB) {
parent::__construct();
$this->DB = $aDB;
$this->Reset();
}
public function Reset() {
$this->item = ItemDefaults();
}
public function Item() { return $item; }
// As seen in dog
abstract public function Load($id);
abstract public function ItemDefaults();
// But descendants (models) must also implement:
abstract public function Save($id = NULL);
abstract public function Delete($id);
// You may want to add Validate() and other internal things here.
}
All of the above is a bare-minimum version of what i build myself when i need another tiny framework. You gain more in proper subclassing than making more classes doing more things. A website is a simple thing in essence, until one overcomplicates it..
The gist / TLDR;
If you really want a REALLY simplified PHP framework, dont read to much. Just write code and you'll discover what it needs to make you work better.
So I have understood how interfaces and abstraction work in PHP, I just don't see the point for example, of having a interface if it just sets a guide and requires implemented objects to have certain methods. Especially since the interface is not even getting instantiated.
This also goes with abstraction, I just can't apply it to my code and see it as such a great thing. When I am trying to create objects on a bigger scale to interact with each other in order to figure out interfaces, each class ends up passing information back and forth, but never is the interface touched.
So what I'm asking is if you guys have any advice or links to outside sources that is good at explaining this kind of thing.
Here's one simple example. Creating interfaces and abstract classes allows you to ensure an object adhears to a common API. See the example below.
interface iCar
{
function drive();
}
abstract class Car implements iCar
{
public $make = 'Generic';
public function drive()
{
printf("I'm driving in my %s%s", $this->make, PHP_EOL);
}
}
class FordTruck extends Car
{
public $make = "Ford";
}
class Porsche extends Car
{
public $make = 'Porsche';
public function drive()
{
printf("I'm speeding around in my %s%s", $this->make, PHP_EOL);
}
}
class Yugo extends Car
{
public $make = 'Yugo';
public function drive()
{
printf("I'm pushing my %s around town%s", $this->make, PHP_EOL);
}
}
function drive(iCar $car)
{
$car->drive();
}
$car1 = new FordTruck;
$car2 = new Porsche;
$car3 = new Yugo;
drive($car1);
drive($car2);
drive($car3);
Even if you don't specify the type of input parameter on the drive() function, you can check if the input is an instanceof an iCar
function drive($car)
{
if ($car instanceof iCar)
$car->drive();
}
Another example would be building a caching interface in your application. You can specify that all cache engines support the same methods for reading/writing/invalidating objects in the cache without knowing (or caring) about the actual implementation of a particular cache engine.
I could give you the simplest as possible example.
Assume you want a feature that allow your site to login with Facebook/Twitter
# here's your interface/abstract class
interface Auth_Adapter {
public function auth();
}
# now your Facebook
class Auth_Adapter_Facebook implements Auth_Adapter {
public function login() {
# include facebook-sdk and auth
}
}
# Twitter
class Auth_Adapter_Twitter implements Auth_Adapter {
public function login() {
# include twitter-oauth and auth
}
}
Imagine when someone try to use Facebook/Twitter thing They can simply call
$adapter = new Auth_Adapter_Facebook;
$adapter->login();
$adapter = new Auth_Adapter_Twitter;
$adapter->login();
As you can see both adapters use the same login interface. What's happen if in the future you have to include 'Pinterest' login? Your code still work as long as you implement the same interface.
EDIT: More explanations
Here's the reason why you have to use interface or abstract
# I use `type-hinting` here. So I can ensure that only object that implements `Auth_Adapter` will allow. Without this implementation someone might pass some other object that doesn't have `login` method in. But in our case we don't have to worry about that.
public function perform_login(Auth_Adapter $adapter) {
$adapter->login();
}
This is my problem, I have a tiny PHP MVC framework i built.
Now when Im in Controller, i should be able to load a Model.
I have Models named like database tables like Users.php, Pages.php etc.
All Controllers extend BaseController, and all Models extend BaseModel, this way I can have some methods available to all Controllers. Like from any Controller I can use loadModel method like this:
$usersModel = $this->loadModel("Users");
Now $usersModel will be object that represents users database table, and from there I should open database connection, and fetch, update, delete stuff from users table.
To get database connection from my Models, baseModel has method loadDatabase() and I use it like this:
$database = $this->loadDatabase();
This would return class that is thin layer around PDO so from there I can use something like this:
$data = $database->fetchAssoc("SELECT * FROM users WHERE name = ?", array("someone"));
This is how I would return some $data from Model to the Controller.
So basicly, Controller can load a model, and then call methods on that model that would return some data or update or delete etc.
Now, Controller can load more then one model. And each model should load database, and this is where it gets complicated. I need only one connection to the database.
This is how loadDatabase method looks:
public function loadDatabase()
{
// Get database configuration
require_once("Application/Config/Database.php");
// Build configuration the way Database Object expects it
$dns = "{$db_config['db_driver']}:host={$db_config['db_host']};dbname={$db_config['db_name']}";
$username = $db_config['db_user'];
$password = $db_config['db_pass'];
$options = $db_config['db_options'];
// Return new Database object
return new \Core\Database\Database($dns, $username, $password, $options);
}
So before I load a Database object, i must load configuration for database connection, as Database objects __constructor expects it. Then I instanciate new database object and return it.
Now Im stuck and I dont know is this the right way to loadDatabase from model?
How should I set this up, how should I load database from inside the model so there is always only one instance of database object. Beacuse if I do something like this from Controller:
$users = $this->loadModel("Users");
$pages = $this->loadModel("Pages");
$users->doSomethingThatNeedsDatabase();
$users->doSomethingThatNeedsDatabase();
$pages->doSomethingThatNeedsDatabase();
I would create 3 database objects :(
So my question is, how should I load Database from inside the Models, and how should that method look in BaseModel?
What if I would like to use 2 databases, I will have some big problems with this setup.
How can I achive this without using singleton pattern?
At the moment, I also have something like this:
public function getDatabase()
{
$reg = \Core\Registry\Registry::getInstance();
$db = $reg->getObject("database");
if(!isset($db))
{
require_once("Application/Config/Database.php");
$dns = "{$db_config['db_driver']}:host={$db_config['db_host']};dbname={$db_config['db_name']}";
$username = $db_config['db_user'];
$password = $db_config['db_pass'];
$options = $db_config['db_options'];
$db = new \Core\Database\Database($dns, $username, $password, $options);
$reg->setObject('database', $db);
}
return $reg->getObject('database');
}
This is Registry pattern, where I could hold shared objects. So when Model asks for DB connection I could check if DB Class is in Registry, and return it, if not I would instaciate and then return... The most confusing thing is that I need to load configuration array...
So what is the best way, to load Database from Models?
Thanks for reading, this was a very long question, but this is bothering me so much, i hope someone could help me with this one... Thanks!
You are going in the wrong way about solving this.
Instead of each time manually making a new "Model" and then configuring it, you should create a structure that does it for you ( extremely simplified version ):
class ModelFactory
{
protected $connection = null;
// --- snip --
public function __construct( $connection )
{
$this->connection = $connection;
}
// --- snip --
public function buildMapper( $name )
{
$instance = new {$name}( $this->connection );
return $instance;
}
// --- snip --
}
This class you would be using in index.php or bootstrap.php , or any other file you use as entry point for your application:
// at the bootstrap level
$modelFactory = new ModelFactory( new PDO(...) );
// i am assuming that you could get $controllerName
// and $action from routing mechanism
$controller = new {$controllerName}( $modelFactory );
$controller->{$action}();
The main problem you have is actually cause by misunderstanding what Model is. In a proper MVC the Model is a layer, and not a specific class. Model layer is composed from multitude of class/instances with two major responsibilities:
domain business logic
data access and storage
The instances in first group are usually called Domain Objects or Business Objects (kinda like situation with geeks and nerds). They deal with validations, computation, different conditions, but have no clue how and where information is stored. It does not change how you make an Invoice , whether data comes from SQL, remote REST API or a screenshot of MS Word document.
Other group consists mostly of Data Mappers. They store and retrieve information from Domain Objects. This is usually where your SQL would be. But mappers do not always map directly to DB schema. In a classic many-to-many structure you might have either 1 or 2 or 3 mappers servicing the storage. Mappers usually one per each Domain Object .. but even that is not mandatory.
In a controller it would all look something like this.
public function actionFooBar()
{
$mapper = $this->modelFactory->buildMapper( 'Book' );
$book = $this->modelFactory->buildObject( 'Book' );
$patron = $this->modelFactory->buildObject( 'Patron' );
$book->setTitle('Neuromancer');
$mapper->fetch( $book );
$patron->setId( $_GET['uid']);
if ( $book->isAvailable() )
{
$book->lendTo( $user );
}
$mapper->store( $book );
}
Maybe this will give you some indications for the direction in which to take it.
Some additional video materials:
Advanced OO Patterns (slides)
Global State and Singletons
Don't Look For Things!
Best way for these models to use dependency injection pattern.
public function loadModel() {
$db = $this->loadDatabase();
$model = new Model();
$model->setDatabase($db);
return $model;
}
where loadDatabase() should once init and after return simple instance of database connection.
Try this:
class Registry
{
/** #return Registry */
public static function getInstance() {}
public function getObject($key) {}
public function setObject($key, $value) {}
/** #return bool */
public function isObjectExists($key) {}
}
class Db
{
const DB_ONE = 'db_one';
const DB_TWO = 'db_two';
protected static $_config = array(
self::DB_ONE => array(),
self::DB_TWO => array(),
);
public static function getConnection($dbName) {}
}
abstract class BaseModel
{
abstract protected function _getDbName();
protected function _getConnection()
{
$dbName = $this->_getDbName();
if (!Registry::getInstance()->isObjectExists($dbName)) {
Registry::getInstance()->setObject($dbName, Db::getConnection($dbName));
}
return Registry::getInstance()->getObject($dbName);
}
}
class UserModel extends BaseModel
{
protected function _getDbName()
{
return Db::DB_ONE;
}
}
class PostModel extends BaseModel
{
protected function _getDbName()
{
return Db::DB_TWO;
}
}
Good luck!
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.
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
"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.
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.