Trying to get a grasp on MVC, I wrote the following:
//Model/Model.php
class Model
{
private $dbh;
public function __construct()
{
$this->dbh = dbConnect();
}
private function dbConnect()
{
//return database connection
}
public function crudMethod()
{
//interact with databse using $this->dbh
}
}
//Controller/Controller.php
class Controller
{
public $modelConnection;
public function __construct()
{
$this->modelConnection = new Model();
}
}
//View/index.php
$obj = new Controller;
$obj->modelConnection->crudMethod();
?>
Looking at my code, I feel like I'm completely missing the point here.
The controller doesn't serve real value, I might as well instantiate the Model class directly.
Should a Model class ever be instantiated? Inside or outside the controller? Or is it bad practice altogether?
How would I improve this structure to support the MVC paradigm?
Your problem is that you are putting information in your layers that should not be there. Your Data Access layer does not need to know HOW it will be connected, they just need to know that there is a properly configured driver to get data from.
In the same way, you are giving your controllers responsibilities they doesn't need: creating models.
Why this is bad?
Imagine that for some reason, you change the dependencies of your model classes. Like if you now have to store data from the model in two different databases at the same time for redundancy.
How would you do this? Change all of your controllers and alter the way you instantiate models inside them?
That's a lot of work! And bug prone.
So, how can I solve this?
I like to do this using Dependency Injection Principle and the Factory Pattern.
Why a factory?
To decouple the creation of my Model from who uses it. My default model factory creates Models that requires only one database connection to work:
namespace MyApplication\Model\Factory;
use MyApplication\Storage\StorageInterface;
interface FactoryInterface {
public function create($modelClassName);
}
class WithStorage implements FactoryInterface {
const MODEL_NAMESPACE = '\\MyApplication\\Model\\';
private $storage;
public function __construct(StorageInterface $storage) {
$this->storage = $storage;
}
public function create($modelClassName) {
$refl = new \ReflectionClass(self::MODEL_NAMESPACE . $modelClassName);
try {
return $refl->newInstance($this->storage);
} catch (\ReflectionException $e) {
throw new RuntimeException("Model {$modelClassName} not found");
}
}
}
Now, for your model classes that requires a storage, you can create a structure like this:
namespace MyApplication\Storage;
interface StorageInterface {
public function insert($container, array $data);
public function update($container, array $data, $condition);
// etc..
}
class PdoStorage implements StorageInterface {
private $dbh;
public function __construct(\PDO $dbh) {
$this->dbh = $dbh;
}
public function insert($container, array $data) {
// impl. omitted
}
public function update($container, array $data, $condition) {
// impl. omitted
}
}
So, if you have the following class:
namespace MyApplication\Model;
use MyApplication\Storage\StorageInterface;
// Entity impl. will be omitted for brevity.
use MyApplication\Entity\EntityInterface;
abstract class AbstractApplicationModel {
private $containerName;
private $storage;
protected function __construct($name, StorageInterface $storage) {
$this->containerName = (string) $name;
$this->storage = $storage;
}
public function save(EntityInterface $entity) {
// impl. omitted
}
public function delete(EntityInterface $entity) {
// impl. omitted
}
}
class UserModel extends AbstractApplicationModel {
public function __construct(StorageInterface $storage) {
parent::__construct('users', $storage);
}
}
With this, we solve our problem with the coupling within the model. Ok.
So, with all this, how can I get a Model component that is ready to store my data from my controller?
Directly:
namespace MyApplication\Controller;
use MyApplication\Model\UserModel;
use MyApplication\Storage\PDOStorage;
use MyApplication\Entity\User;
class UserController {
public function onCreate() {
$model = new UserModel(new Storage(new \PDO(...))); // Here's our problem
$entity = User::createFromArray([
'name' => 'John',
'surname' => 'Doe',
]);
try {
$model->save($entity);
} catch (Exception $e) {
echo 'Oops, something is wrong: ' . $e;
}
}
}
If you have just one model to deal in your whole application, you are ready to go. But you have several of them, then you will have problems.
What if I no longer want to use PDO as my storage driver and work with MySQLi? What if I don't want to use a RDBMS anymore, instead I want to store my data in plain text files?
This way, you'd have to change the implementation of all controllers (which violates the OCP). Lots of repetitive work to do. I hate this!
Oh wait! We have the freaking factory to create models for us! So, let's use it!
namespace MyApplication\Controller;
use MyApplication\Model\Factory\FactoryInterface;
use MyApplication\Entity\User;
abstract class AbstractController {
private $modelFactory;
public function __construct(ModelFactory $factory) {
$this->modelFactory = $factory;
}
public function getModelFactory() {
return $this->modelFactory;
}
}
class UserController {
public function onCreate() {
$model = $this->getModelFactory()->create('UserModel'); // now it's better
$entity = User::createFromArray([
'name' => 'John',
'surname' => 'Doe',
]);
try {
$model->save($entity);
} catch (Exception $e) {
echo 'Oops, something is wrong: ' . $e;
}
}
}
Now, to have all this working together, you have to do some setup on your bootstrap/front controller:
use MyApplication\Storage\PDOStorage;
use MyApplication\Model\Factory\WithStorage as ModelFactory;
$defaultPDODriver = new \PDO(...);
$defaultStorage = new PdoStorage($defaultPDODriver);
$defaultModelFactory = new ModelFactory($defaultStorage);
$controller = new UserController($defaultModelFactory);
Now, what do I do if I want to change my storage engine to plain text files?
$defaultStorage = new PlainFileStorage('/path/to/file'); // just this
Now, what do I do if I want to change my storage engine to one custom implementation that have 2 distinct database to save the same data?
$master = new PdoStorage(new \PDO(...));
$slave = new PdoStorage(new \PDO(.......));
$defaultStorage = new RedundancyStorage($master, $slave);
See? Now the way you store information is irrelevant to your models.
The same way, if you have some crazy business logic that changes the way your models do things based on the setup, you can also change your model factory:
$defaultModelFactory = new SomeCrazyModelFactory(...);
Your controllers are not even aware that your models changed (of course, you'd have to respect the same interface to be allowed to do this interchangeably).
That's a possible way, it's more or less how I do, but there are a few other possibilities.
You are mixing concerns in your interpretation of a Model. In Model-View-Controller, a Model is some type of knowledge, which could be a single class representing a domain concept or a more complex structure e.g. a Page or Document in the context of a CMS.
However in your example, your Model class is really just a Singleton holder for your database connection, that is not its purpose.
A database connection is a service that is provided across the MVC tiers, which should preferably be wired using dependency injection. You should not instantiate services in Controller code.
Whether instantiating Model objects within a controller makes sense depends largely on the context, but there is no rule that forbids it.
Related
Following from my previous post about removing ServiceLocatorAwareInterface's from my zf2 app, i am now faced with a puzzle involving object creation when using data mappers.
The current implementation of my data mapper uses a tablegateway to find specific rows, calls the service manager to obtain a domain object, then populates and returns the full object.
public function findById($userId){
$rowset = $this->gateway->select(array('id' => $userId));
$row = $rowset->current();
if (!$row) {
throw new \DomainException("Could not find user with id of $userId in the database");
}
$user = $this->createUser($row);
return $user;
}
public function createUser($data){
$userModel = $this->getServiceManager()->get('Model\User');
$hydrator = $this->getHydrator();
if($data instanceof \ArrayObject){
$hydrator->hydrate($data->getArrayCopy(), $userModel);
}else{
$hydrator->hydrate($data, $userModel);
}
return $userModel;
}
The model needs to be called from the service manager because it has other dependencies, so calling $user = new App\Model\User() from within the mapper is not an option.
However, now i am removing instances of the servicemanager from my code, i am unsure of the best way to get the model into the mapper. The obvious answer is to pass it in the constructor and save the instance as a property of the mapper:
public function __construct(TableGateway $gateway, \App\Model\User $userModel){
$this->_gateway = $gateway;
$this->_userModel= $userModel;
}
public function createUser($data){
$userModel = $this->_userModel;
//....snip....
}
This works to a degree, but then multiple calls to createUser (such as when finding all users, for instance) over writes each instance with the last objects data (as to be expected, but not what i want)
So i need a "new" object returned each time i call createUser, but the dependency being passed into the constructor. With the model passed into the constructor I can clone the object eg.
public function createUser($data){
$userModel = clone $this->_userModel
//....snip....
}
...but something about it doesn't seem right, code smell?
You are right, it doesn't smell good.
Designing an ORM isn't easy. There is and probably always will be discussion about the way an ORM should be designed. Now, when I'm trying to understand your design I noticed you are pointing out that your models contain the data but also have "other" dependencies. This is wrong, the models containing your data should work without any layer in your application.
Entities should work without the ORM
In my opinion you should separate your business logic (dependencies) from your data. This will have many advantages:
More expressive
Easier to test
Less coupling
More flexible
Easier to refactor
For more information about how to design your ORM layer I highly recommend browsing through these slides.
DataMaper
Lets make the UserMapper responsible for separating the in-memory objects (containing only data) from the database.
class UserMapper
{
protected $gateway;
protected $hydrator;
public function __construct(TableGateway $gateway, HydratorInterface $hydrator)
{
$this->gateway = $gateway;
$this->hydrator = $hydrator;
}
public function findOneById($id)
{
$rowset = $this->_gateway->select(array('id' => $id));
$row = $rowset->current();
if(!$row) {
throw new \DomainException("Could not find user with id of $id in the database.");
}
$user = new User;
$this->hydrator->hydrate($row, $user);
return $user;
}
public function findManyBy(array $criteria)
{
// $criteria would be array('colum_name' => 'value')
}
public function save(User $user)
{
$data = $this->hydrator->extract($user);
// ... and save it using the $gateway.
}
}
For more information about the responsibility of data mappers check out Martin Fowler's definition.
Buniness Logic
It's recommended not to place any model related business logic directly into the Controller. Therefor lets just create a simple UserService which will handle validation. If your fond of form objects you could also use Zend\Form\Form in this process.
class UserService
{
protected $inputFilter;
protected $hydrator;
public function __construct(InputFilter $inputFilter, HydratorInterface $hydrator)
{
$this->inputFilter = $inputFilter;
$this->hydrator = $hydrator;
}
protected function validate(array $data)
{
// Use the input filter to validate the data;
}
public function createUser(array $data)
{
$validData = $this->validate($data);
$user = new User;
$this->hydrator->hydrate($validData, $user);
return $user;
}
}
Data Object
Now lets make the objects containing the data Plain Old PHP Objects, not bound by any restriction. This means they are not coupled with any logic and we could use them anywhere. For instance if we decide to replace our ORM with another like Doctrine.
class User
{
protected $name;
public function setName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
}
More information about the concept of Plain Old PHP Objects can be found on Wikipedia's explanation of POJO.
I'm moving onto teaching myself OOP in PHP.
I'm creating a couple of little web apps and have followed a lot of tutorials that either create the database (using PDO) via a Singleton, or via passing the global around. I've read that these are pretty much the same thing and are both to be avoided like the plague.
So I've watched the Google Tech Talks on clean code, and read almost every SO article on dependency injection and the like. I have a couple of questions.
The clean code videos suggest you shouldn't do 'work' in your constructors. Is this 'work' in reference to business logic. Ie. If my class's job is to create another object, is that an OK kind of 'work'?
For example, in trying to conform to single repsonibility classes I created three.
Class DB - which actually connects to the database.
Class DBFactory - which creates the DB object which connects to the database.
Class DBInstance - which returns a single instance of the DBFactory created PDO object.
Please note that I'm trying to create a single instance, without creating a Singleton pattern.
So I try and pass my dependencies for each class up the chain. I find myself in a position where I have to create all of the objects (from DB down) so I can inject the dependencies. For some reason I thought it would work the other way, I'd create the first object, which would create the second for me etc. I'm clearly missing something?
Hopefully this helps others as well - there seems to be a myriad of questions relating to this stuff and databases but very little good examples.
(I should mention this does work, I do get a list of hotel names out of the database!)
TestCode.php
include './classes/DB.php';
include './classes/DBFactory.php';
include './classes/DBInstance.php';
include './classes/Location.php';
$db = new DB;
$dbfactory = new DBFactory($db);
$dbinstance = new DBInstance($dbfactory);
$dbh = $dbinstance->getDbInstance();
//Example business logic
$location_names = Location::getLocationNames($dbh);
print_r($location_names);
Class DB.php:
class DB {
private $_dbhost = 'myhost';
private $_dbname = 'myname';
private $_dbuser = 'myuser';
private $_dbpass = 'mypass';
private $_error;
public function connect() {
try {
return new PDO("mysql:host=$this->_dbhost;dbname=$this->_dbname",
$this->_dbuser, $this->_dbpass);
}
catch (PDOException $e) {
$this->_error = 'Error! ' . $e->getMessage() . '<br />';
die();
}
}
public function getError() {
if (isset($this->_error)) {
return $this->_error;
}
}
}
Class DBFactory.php
class DBFactory {
private $_dbh;
public function __construct(DB $db) {
$this->_dbh = $db;
}
public function Create() {
return $this->_dbh->Connect();
}
}
Class DBInstance.php
class DBInstance {
private static $_dbinstance;
public function __construct(DBFactory $dbfactory) {
if (!isset(self::$_dbinstance)) {
self::$_dbinstance = $dbfactory->Create();
}
}
public function getDbInstance() {
return self::$_dbinstance;
}
}
Your code seems to do what you want it to.. but maybe we can use less object instantiation using inheritance and maybe we can avoid static properties in instanciated classes.
Also in regard to using a pattern of dependency injection that is able to handle multiple connections, but support using a single instance of it. exemple first, classes after
$params = array
('host'=>'localhost',
'db'=>'ice',
'user'=>'kopitar',
'pass'=>'topnet',
'charset'=>'utf8'); // passing the charset explicitely is great
$handle = new handle($params);
$db = $handle->getInstance();
we can either pass the $db to our functions
$location_names = Location::getLocationNames($db);
or the whole $handle. as long as $handle is not reconstructed, it will always return the same database connection.
$location_names = Location::getLocationNames($handle);
if I want to reconstruct I need the whole $handle
$handle->__construct(/* params but with another database infos */);
$db2 = $handle->getInstance();
As for the classes, I think we want the params to arrive from the instanciated class, so we can change them later.
class db {
function __construct($params) {
foreach ($params as $param => $value) {
$this->{$param} = $value; // assigns the connections infos
}
}
protected function connect() {
$dsn = 'mysql:host='.$this->host.';dbname='.$this->db.';charset='.$this->charset;
return new PDO($dsn,$this->user,$this->pass);
}
}
the factory creates a connection from params and passes it to something else, good factory
class factory extends db {
protected function create() {
return $this->connect();
}
}
now we want to have our object to keep it's connection as long as we do not rebuild it. so we give it to instance
class instance extends factory {
function instantiate() {
$this->instance = $this->create();
}
}
and last but not least, our handle which returns the instance. it could be in instance class.....................
but I feel like having four and find no real reason not to.
class handle extends instance {
function __construct($params) {
db::__construct($params);
$this->instantiate(); // when we construct a handle, we assign an instance to the instance property
}
function getInstance() {
return $this->instance;
}
}
KISS
Don't make things more complex than they are, of course this is just my opinion, but as I see it you are building a complex solution for a problem that someone else says might exist is some cases.
Php is not multi threaded so there goes one of the biggest arguments overboard. (in very rare-occasions it might be)
I'm using singletons for my database connections for about 15 years now and never ever had a problem with them, I do play around with different connections having one singleton handle several connection instances, but whatever... it works great and everyone that looks at the code.. understands it directly.
I'm not using globals because they can be overwritten and are kind of hard to predict (when it holds the correct object, and when/why they don't)
Use OOP to make your code cleaner, easier to work with and more flexible.
Don't use it to fix problems that aren't there and make your code more complex because others tell you to.
An very simple example of a db-connection singleton class handling several different connections.
class singleton{
private static $_instances=array();
public static function getInstance($connectionName){
if(!isset(self::$_instance[$connectionName]){
self::$_instance[$connectionName]=self::_getConnection($connectionName);
}
return self::$_instance[$connectionName];
}
}
just my 2 cents
Why do you have a factory if you have a singleton? This is needless.
This is a never-ending debate, but I'm advocate of do not use singletons for database connections.
As far as in most applications, you have only one data channel, you can consider your database connection unique, but this might not be always true.
In deed, the effort made to create a singleton database connection is even bigger than just create a regular one.
Also, your class DB is not configurable, therefore, you need to change it when your connection parameters change. And I think DB is a very bad name for this.
I'd rather call this Storage and do something like:
inteface Storage {
public function insert($container, array $data);
public function update($container, array $data, $where);
public function delete($container, $where);
public function getAll($container);
public function getOne($identifier);
}
final class PdoStorage implements Storage {
private $dbh;
private $dsn;
private $user;
private $pswd;
public function __construct($dsn, $user, $pswd) {
$this->dsn = $dsn;
$this->user = $user;
$this->pswd = $pswd;
}
// Lazy Initialization
private function connect() {
if ($this->dbh === null)
$this->dbh = new PDO($this->dsn, $this->user, $this->pswd);
}
public function insert($container, array $data) {
$this->connect();
// ... omitted for brevity
}
}
Now, when you need a database storage, you do:
$someObj = new SomeClass(new PdoStorage(...));
Now you might be wondering if you will need to create an PdoStorage for each single object that depends on it.
The answer is: no!
Now you can use a factory to simplify your life.
class SomeFactory {
private $defaultStorage;
public function __construct(Storage $storage) {
$this->defaultStorage = $storage;
}
public function create($type) {
// Somehow fetches the correct class to instantiate and put it into $class variable , for example... and then
return new $class($this->defaultStorage); // Or you'd better do this with reflection
}
}
$factory = new SomeFactory(new PdoStorage(...));
$factory->create('SomeClass');
This way, you can have just one database connector or more if you need.
I'm just getting to grips with OOP, so sorry if this question seems a little all over the place, its how my head is feeling right now.
I have looked at constructors in the PHP docs but it does not seem to cover dependency injection.
I have a class called DatabaseLayer, this class simply creates a connection to my database
//php class for connecting to database
/**
* Class DatabaseLayer - connects to database via PDO
*/
class DatabaseLayer
{
public $dbh; // handle of the db connexion
/**
* #param $config Config- configuration class
*/
private function __construct(Config $config)
{
$dsn = $config->read('db.dsn');
$user = $config->read('db.user');
$password = $config->read('db.password');
$this->dbh = new \PDO($dsn, $user, $password);
}
public function getConnection()
{
if ($this->dbh)
{
return $this->dbh;
}
return false;
}
}
Q1: i have private function __construct(Config $config)
I'm not sure i fully understand the reason of having __construct(Config $config) instead of just using __construct($config)
Does (Config $config) automatically create $config as a new Config instance?
or do i have to do the following:
$config = new Config();
$dbLayer = new DatabaseLayer($config);
I want to extend the DatabaseLayer class and include methods for interacting with the database relating to my GameUser which is another class i have created, when i extend the DatabaseLayer class i need to inject the GameUser class
I know my new class DatabaseLayerUser inherits the methods and properties from the DatabaseLayer class.
Q2: as the new DatabaseLayerUser class is based on the 'DatabaseLayer' class it needs to have the Config class, because i used __construct(Config $config) does this get it automatically?
or do i have to pass both the Config and GameUser to DatabaseLayerUser
class DatabaseLayerUser EXTENDS DatabaseLayer
{
private $config; /** #var Config */
private $user; /** #var GameUser */
/**
* #param Config $config
* #param GameUser $user
*/
private function __construct(Config $config, GameUser $user){
$this->config = $config;
$this->user = $user;
}
/**
* profileExists - Checks to see if a user profile exists
* internal #var $PDOQuery
* #var $PDOStatement PDOStatement
* #throws Exception - details of PDOException
* #return bool
*/
private function profileExists()
{
try{
$PDOQuery = ('SELECT count(1) FROM userprofile_upl WHERE uid_upl = :uid');
$PDOStatement = $this->dbh->prepare($PDOQuery);
$PDOStatement->bindParam(':uid', $this->_userData->uid);
$PDOStatement->execute();
return $PDOStatement->fetchColumn() >0;
}catch(PDOException $e){
throw new Exception('Failed to check if profile exists for '.$this->_userData->uid, 0, $e);
}
}
}`
Q3: i saw there is a parent::__construct();
does this mean i should use:
private function __construct(Config $config, GameUser $user){
parent::__construct($config);
$this->user = $user;
}
I think you're really mixing up means and purposes here. The goal is never to use dependency injection per se, but to solve programming problems that you encounter with it. So let's first try and correct the dependencies between the classes and their responsibilities a little.
In my opinion, it is best to start this excercise from the application domain, start small, and then refactor if you want to introduce abstraction. At least when learning or as a thought experiment. When more experienced and when implementing the real thing, it is probably smarter to look a couple of steps ahead.
So for now I'll simply assume you're making an online game, with different users that are represented by GameUser objects, and no further than that.
The sole responsibility of the GameUser class should be to represent the domain data and logic, i.e.: it contains a couple of properties (let's say username and score) and methods (let's say incrementScore) that have to do with the application domain itself (game users). It should not be responsible for practical implementation details, most notably: how it gets persisted (written to file/db/whatever). That's why a DatabaseLayerUser that is responsible for storing itself is probably a bad idea. So let's start instead with a nice and clean GameUser domain class and use encapsulation (private properties to prevent tampering from the outside):
class GameUser {
private $_username;
private $_score;
public function __construct($username, $score) {
$this->_username = $username;
$this->_score = $score;
}
public function getUsername() { return $this->_username; }
public function getScore() { return $this->_score; }
public function incrementScore() {
$this->_score++;
return $this->_score;
}
}
We can now create a new GameUser ($user = new GameUser('Dizzy', 100);), but we cannot persist it. So we need some kind of repository where we can store these users and fetch them again later. Let's call it just that: the GameUserRepository. This is a service class. Later when there is more than one type of domain object and repository, we can create a DatabaseLayer class that will group these and/or act as a facade, but we start small now and will refactor later.
Once again, the responsibility of the GameUserRepository class is to allow us to fetch and store GameUser objects, and to check if a profile for a given username exists. In principle, the repository could store the GameUser objects in a file or somewhere else, but for now, we're making making the choice here to persist them in a SQL database (start small, refactor later, you get it.)
The responsibility of the GameUserRepository is not management of the database connection. It will however need a database object so it can pass the SQL queries to it. So it delegates the responsibility of setting up the connection and actually executing the SQL queries it will create.
Delegation rings a bell. Dependency injection comes into play here: we will inject a PDO database object (service). We'll inject it in the constructor (i.e. constructor DI as opposed to setter DI). It is then the responsibility of the caller to figure out how to create the PDO service, our repository really couldn't care less.
class GameUserRepository {
private $_db;
public function __construct(PDO $db) {
$this->_db = $db;
}
public function profileExists($username) {
try {
$PDOQuery = ('SELECT count(1) FROM userprofile_upl WHERE uid_upl = :uid');
$PDOStatement = $this->dbh->prepare($PDOQuery);
$PDOStatement->bindParam(':uid', $username);
$PDOStatement->execute();
return $PDOStatement->fetchColumn() >0;
} catch(PDOException $e) {
throw new Exception('Failed to check if profile exists for '. $username, 0, $e);
}
}
public function fetchGameUser($username) { ... }
public function storeGameUser(GameUser $user) { ... }
}
To answer Q1 & Q2 in one go: function __construct(PDO $db) simply expresses a type constraint: PHP will check that the $db parameter value is a PDO object. If you're trying to run $r = new GameUserRepository("not a PDO object");, it will throw an error. The PDO type constraint has nothing to do with dependency injection.
I think you're confusing this with the functionality of DI frameworks that actually inspect the constructor signature at run-time (using reflection), see that a PDO-type argument is needed, and then indeed create such an object automagically and pass it to the constructor when creating the repository. E.g. the Symfony2 DI bundle can do this, but it has nothing to do with PHP itself.
Now we can run code like this:
$pdo = new PDO($connectionString, $user, $password);
$repository = new GameUserRepository($pdo);
$user = $repository->fetchGameUser('Dizzy');
But this begs the question: what is the best way to create all these objects (services), and where do we keep them? Surely not by just putting the above code somewhere and using global variables. These are two clear responsibilities that need to be located somewhere, so the answer is that we create two new classes: the GameContainer class and the GameFactory class to create this container.
The responsibility of the GameContainer class is to centralize the GameUserRepository service with other services that we'll create in the future. The responsibility of the GameFactory class is to set up the GameContainer object. We'll also create a GameConfig class to configure our GameFactory:
class GameContainer {
private $_gur;
public function __construct(GameUserRepository $gur) { $this->_gur = $gur; }
public function getUserRepository() { return $this->_gur; }
}
class GameConfig { ... }
class GameFactory {
private $_config;
public function __construct(GameConfig $cfg) {
$this->_config = $cfg;
}
public function buildGameContainer() {
$cfg = $this->_config;
$pdo = new PDO($cfg->read('db.dsn'), $cfg->read('db.user'), $cfg->read('db.pw'));
$repository = new GameUserRepository($pdo);
return new GameContainer($repository);
}
}
We can now imagine a game.php application with basically the following code:
$factory = new GameFactory(new GameConfig(__DIR__ . '/config.php'));
$game = $factory->buildGameContainer();
One crucial thing is still missing however: the use of interfaces. What if we want to write a GameUserRepository that uses an external web service to store and fetch GameUser objects? What if we want to provide a MockGameUserRepository with fixtures to facilitate testing? We can't: our GameContainer constructor explicitly demands a GameUserRepository object as we have implemented it using the PDO service.
So, now is the time to refactor and extract an interface from our GameUserRepository. All consumers of the current GameUserRepository will now have to use the IGameUserRepository interface instead. This is possible thanks to dependency injection: references to GameUserRepository are all just used as type constraints that we can replace by the interface. It would not be possible if we hadn't delegated the task of creating these services to the GameFactory (which will now have the responsibility to determine an implementation for each service interface.)
We now get something like this:
interface IGameUserRepository {
public function profileExists($username);
public function fetchGameUser($username);
public function storeGameUser(GameUser $user);
}
class GameContainer {
private $_gur;
// Container only references the interface:
public function __construct( IGameUserRepository $gur ) { $this->_gur = $gur; }
public function getUserRepository() { return $this->_gur; }
}
class PdoGameUserRepository implements IGameUserRepository {
private $_db;
public function __construct(PDO $db) {
$this->_db = $db;
}
public function profileExists($username) {...}
public function fetchGameUser($username) { ... }
public function storeGameUser(GameUser $user) { ... }
}
class MockGameUserRepository implements IGameUserRepository {
public function profileExists($username) {
return $username == 'Dizzy';
}
public function fetchGameUser($username) {
if ($this->profileExists($username)) {
return new GameUser('Dizzy', 10);
} else {
throw new Exception("User $username does not exist.");
}
}
public function storeGameUser(GameUser $user) { ... }
}
class GameFactory {
public function buildGameContainer(GameConfig $cfg) {
$pdo = new PDO($cfg->read('db.dsn'), $cfg->read('db.user'), $cfg->read('db.pw'));
// Factory determines which implementation to use:
$repository = new PdoGameUserRepository($pdo);
return new GameContainer($repository);
}
}
So this really puts all pieces together. We can now write a TestGameFactory injecting the MockGameUserRepository, or, better still, extend GameConfig with a "env.test" boolean and have our existing GameFactory class decide whether to construct a PdoGameUserRepository or MockGameUserRepository based on that.
The connection with DI practices should now be clear as well. The GameContainer, of course, is your DI container. The GameFactory, is the DI container factory. It is these two that are implemented with all bells and whistles by DI frameworks such as the Symfony2 DI bundle.
You can indeed imagine extending the factory and its config up to the point where all services are completely defined in an XML file, including their implementation class names:
<container env="production">
<service name="IGameUserRepository" implementation="PdoGameUserRepository">
<connectionString>...</connectionString>
<username>...</username>
<password>...</password>
</service>
</container>
<container env="test">
<service name="IGameUserRepository" implementation="MockGameUserRepository"/>
</container>
You can also imagine generalizing the GameContainer so that services are fetched like $container->getService('GameUserRepository').
As for passing constructor arguments to parent classes in PHP (which has little to do with DI, except that it'll be needed sooner or later if you use constructor injection), you can do this as you suggest yourself:
class A {
private $_someService;
public function __construct(SomeService $service) { $this->_someService = $service; }
}
class B extends class A {
private $_someOtherService;
public function __construct(SomeService $service, SomeOtherService $otherService) {
parent::__construct($service);
$this->_someOtherService = $otherService;
}
}
$b = new B(new SomeService(), new SomeOtherService());
But you'll have to stay away from the private constructors. They reek of singletons.
I'm wondering if anyone could give me a suggestion for to best handle this situation:
I have several systems from which to pull data to display on a single PHP-driven website. The type of information will be the same across systems (contacts, addresses, etc) but the way I pull data (MS-SQL, XML, REST) will not.
I want to create a class, or set of classes, for each of the connection types and use simple methods such as getContact(), getAddress(), etc. I am wondering how best to structure this.
The most obvious way that comes to mind means creating classes for each connection type, like:
class.sys_mysql.php. class.sys_xml.php, etc
But then won't I be duplicating the methods in each class? Maybe that's OK, but I'm curious if there's a better way, as far as future maintenance goes.
Maybe I should simply isolate the queries/data extraction methods, into separate class files? Classes within classes? Extended classes? I'm less familiar with these.
Any advice would be greatly appreciated.
DC
--------- more info ----------
Hi all. I really appreciate all the great advice. Not to belabor this thread but I'm still a bit confused on how I should break things down. I will try and be a bit more specific:
Basically, I have 3 (more in the future) offices, from which one PHP website pulls information. Each office uses a different CRM, and a different system for interfacing with that CRM. One uses MSSQL, another XML requests, etc.
Each office wants to display information similarly on the website, but there are minor differences. There may be more differences in the future. However, there are by far more similarities, and so I want to capitalize on higher level functions like getContacts($id) which are shared between them.
I am trying to write these classes so I can:
1) use higher level methods to pull data easily
2) account for different ways of pulling data (xml,sql,etc)
3) account for differences between how data is displayed on the website (office 1, office 2, office 3)
4) manage the connection credentials for each office and allow for expandability_
5) I should also mention that I will be creating separate classes for reporting, sending out automated e-mails, calculating finances...separate modules that will need to use existing classes to pull data.
I realize that some of the examples here see to cover 1 and 2, but I am confused as to how to get 3, 4 and 5 working with 1 and 2.
I really appreciate the help.
DC
This is what Interfaces are for.
You define the methods required to interact with the data in an Interface, and then you create classes that implement that Interface
If some of the systems have similar access models (i.e. perhaps two different DB Servers, but both are accessed using PDO) you could abstract it further and put the "low level" functionality into service-specific classes (which implement an Interface) and then a higher-level class which defines the actual methods you use.
Another option is that you could put the "common" methods (those that are identical or can be made idetntical with service-type checks) into a base class, which all others extend.
Example for option one:
interface DataModel {
public function findContacts($search);
public function getContact($id);
public function findAddresses($search);
public function getAddress($id);
}
class XMLDataModel implements DataModel {
public function findContacts($search) {
...
}
public function getContact($id) {
...
}
public function findAddresses($search) {
...
}
public function getAddress($id) {
...
}
}
class RESTDataModel implements DataModel {
public function findContacts($search) {
...
}
public function getContact($id) {
...
}
public function findAddresses($search) {
...
}
public function getAddress($id) {
...
}
}
As you can see, you simply define an Interface, which specifies which methods a class must implement.
If you had two very similar classes, perhaps one for MySQL and one for PostreSQL, and you can't/don't want to combine them into a single PDO class, you could do the following:
class PDODataModel implements DataModel {
private $model;
public function __construct ($serverType) {
if ($serverType === 'mysql') {
$this->model = new MySQLPDODataModel();
}
elseif ($serverType === 'postgresql') {
$this->model = new PostgresQLPDODataModel();
}
}
public function findContacts($search) {
// common logic about $search, perhaps checking it's a valid search?
$result = $this->model->searchForContacts($search);
// more common logic, maybe higher level filtering..
return $result;
}
public function getContact($id) {
...
}
public function findAddresses($search) {
...
}
public function getAddress($id) {
...
}
}
interface PDODataModelDriver {
public function searchForContacts($search);
}
class MySQLPDODataModel extends PDODataModel implements PDODataModelDriver {
public function searchForContacts($search) {
// MySQL-specific query to search for contacts
}
}
class PostgresSQLPDODataModel extends PDODataModel implements PDODataModelDriver {
public function searchForContacts($search) {
// PostgreSQL-specific query to search for contacts
}
}
The other option I mentioned was to work in the opposite direction:
abstract class PDODataModel implements DataModel {
protected $pdo;
protected $dsn;
public function __construct () {
$this->pdo = new PDO($this->dsn);
}
public function findContacts($search) {
// common logic about $search, perhaps checking it's a valid search?
$result = $this->searchForContacts($search);
// more common logic, maybe higher level filtering..
return $result;
}
public function getContact($id) {
...
}
public function findAddresses($search) {
...
}
public function getAddress($id) {
...
}
}
class MySQLPDODataModel extends PDODataModel {
protected $dsn = 'mysql:dbname=testdb;host=127.0.0.1';
protected function searchForContacts($search) {
// MySQL-specific query to search for contacts
}
}
class PostgresSQLPDODataModel extends PDODataModel {
protected $dsn = 'pgsql:host=localhost;port=5432;dbname=testdb';
protected function searchForContacts($search) {
// PostgreSQL-specific query to search for contacts
}
}
This is a classical example of a strategy design patter. Your first mind was absolutely fine, but if you're repeating yourself in each class you should consider creation of a abstract class that will handle the common code.
So it could look like this:
$myService = new MyService(new XMLReader('/path/to/file'));
echo $myService->getContanct('abc')->getName();
And skeleton of your classes:
class MyService {
private $reader;
public function __construct(ReaderInterface $reader) {
$this->reader = $reader;
}
// ...
public function getContacnt($id) {
$contact = $this->reader->getContact($id);
// do some extra stuff here
return $contact;
}
}
interface ReaderInterface {
public function getContanct($id);
public function getAddress($id);
}
abstract class AbstractReader implements ReaderInterface {
protected $loaded = false;
protected $data = array();
abstract protected function load();
public function getContanct($id) {
if ($this->loaded == false) {
$this->load();
$this->loaded = true;
}
return $this->data['contact'][$id];
}
}
class XMLReader extends AbstractReader {
public function __construct($filepath) {
...
}
protected function load() {
...
foreach (...) {
$this->data[...] = ...;
}
}
}
class MSSQLReader extends AbstractReader {
public function __construct(PDO $dbh) {
...
}
protected function load() {
...
while ($row = $stmt->fetchRow()) {
$this->data[...] = ...;
}
}
}
EDIT (2011-03-07) - According to your comment.
PHP supports variable variables (new $type()) but never use this! It's a horrible, and if overused make code really crappy.
This is a yet another example of a "classical issue". Use a factory pattern (depending on the complexion of the creation you might want to use more abstract variety of this pattern - abstract factory
When you need to dynamically determine class name (eg. from variable) use reflection API to instate an object.
You should create an object-storage mapping layer for each data source, which instantiates the objects into storage agnostic model objects. See http://martinfowler.com/eaaCatalog/dataMapper.html
If you have control over the structure of your data formats, I suggest you serialize your data in a consistent way (especially in XML) and provide drivers for each data format.
For instance, every driver will have 'findAll', 'getOne', 'count', etc. methods. The driver can be given a model to populate with the retrieved data.
abstract class DataDriver {
function __construct($model) {}
abstract public function findAll();
abstract public function getOne();
abstract public function count();
// ...
}
class XMLDriver extends DataDriver {
// implements all the methods
}
class SQLDriver extends DataDriver {
// implements all the methods
}
class Contact {
public var $firstName;
public var $lastName;
function getFullName() {
return trim($this->firstName . ' ' . $this->lastName);
}
}
$accessor = new SQLDriver('Contact');
$contacts = $accessor->findAll();
If your data will be serialized in an uncontrolled manner, the approach you suggest is the best. Just make sure to separate your models (e.g. Address book, Contact) from the method of retrieval (eg. get_address_book_xml, get_address_book_sql, etc.)
Of course there are many ways of separating your models from your data-mapping driver. The importance is you find the solution that works best for you given that you're using such different formats.
I'm working on creating a domain layer in Zend Framework that is separate from the data access layer. The Data Access Layer is composed to two main objects, a Table Data Gateway and a Row Data Gateway. As per Bill Karwin's reply to this earlier question I now have the following code for my domain Person object:
class Model_Row_Person
{
protected $_gateway;
public function __construct(Zend_Db_Table_Row $gateway)
{
$this->_gateway = $gateway;
}
public function login($userName, $password)
{
}
public function setPassword($password)
{
}
}
However, this only works with an individual row. I also need to create a domain object that can represent the entire table and (presumably) can be used to iterate through all of the Person's in the table and return the appropriate type of person (admin, buyer, etc) object for use. Basically, I envision something like the following:
class Model_Table_Person implements SeekableIterator, Countable, ArrayAccess
{
protected $_gateway;
public function __construct(Model_DbTable_Person $gateway)
{
$this->_gateway = $gateway;
}
public function current()
{
$current = $this->_gateway->fetchRow($this->_pointer);
return $this->_getUser($current);
}
private function _getUser(Zend_Db_Table_Row $current)
{
switch($current->userType)
{
case 'admin':
return new Model_Row_Administrator($current);
break;
case 'associate':
return new Model_Row_Associate($current);
break;
}
}
}
Is this is good/bad way to handle this particular problem? What improvements or adjustments should I make to the overall design?
Thanks in advance for your comments and criticisms.
I had in mind that you would use the Domain Model class to completely hide the fact that you're using a database table for persistence. So passing a Table object or a Row object should be completely under the covers:
<?php
require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();
$db = Zend_Db::factory('mysqli', array('dbname'=>'test',
'username'=>'root', 'password'=>'xxxx'));
Zend_Db_Table_Abstract::setDefaultAdapter($db);
class Table_Person extends Zend_Db_Table_Abstract
{
protected $_name = 'person';
}
class Model_Person
{
/** #var Zend_Db_Table */
protected static $table = null;
/** #var Zend_Db_Table_Row */
protected $person;
public static function init() {
if (self::$table == null) {
self::$table = new Table_Person();
}
}
protected static function factory(Zend_Db_Table_Row $personRow) {
$personClass = 'Model_Person_' . ucfirst($personRow->person_type);
return new $personClass($personRow);
}
public static function get($id) {
self::init();
$personRow = self::$table->find($id)->current();
return self::factory($personRow);
}
public static function getCollection() {
self::init();
$personRowset = self::$table->fetchAll();
$personArray = array();
foreach ($personRowset as $person) {
$personArray[] = self::factory($person);
}
return $personArray;
}
// protected constructor can only be called from this class, e.g. factory()
protected function __construct(Zend_Db_Table_Row $personRow) {
$this->person = $personRow;
}
public function login($password) {
if ($this->person->password_hash ==
hash('sha256', $this->person->password_salt . $password)) {
return true;
} else {
return false;
}
}
public function setPassword($newPassword) {
$this->person->password_hash = hash('sha256',
$this->person->password_salt . $newPassword);
$this->person->save();
}
}
class Model_Person_Admin extends Model_Person { }
class Model_Person_Associate extends Model_Person { }
$person = Model_Person::get(1);
print "Got object of type ".get_class($person)."\n";
$person->setPassword('potrzebie');
$people = Model_Person::getCollection();
print "Got ".count($people)." people objects:\n";
foreach ($people as $i => $person) {
print "\t$i: ".get_class($person)."\n";
}
"I thought static methods were bad
which is why I was trying to create
the table level methods as instance
methods."
I don't buy into any blanket statement that static is always bad, or singletons are always bad, or goto is always bad, or what have you. People who make such unequivocal statements are looking to oversimplify the issues. Use the language tools appropriately and they'll be good to you.
That said, there's often a tradeoff when you choose one language construct, it makes it easier to do some things while it's harder to do other things. People often point to static making it difficult to write unit test code, and also PHP has some annoying deficiencies related to static and subclassing. But there are also advantages, as we see in this code. You have to judge for yourself whether the advantages outweigh the disadvantages, on a case by case basis.
"Would Zend Framework support a Finder
class?"
I don't think that's necessary.
"Is there a particular reason that you
renamed the find method to be get in
the model class?"
I named the method get() just to be distinct from find(). The "getter" paradigm is associated with OO interfaces, while "finders" are traditionally associated with database stuff. We're trying to design the Domain Model to pretend there's no database involved.
"And would you use continue to use the
same logic to implement specific getBy
and getCollectionBy methods?"
I'd resist creating a generic getBy() method, because it's tempting to make it accept a generic SQL expression, and then pass it on to the data access objects verbatim. This couples the usage of our Domain Model to the underlying database representation.