2

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?

thrau
  • 2,915
  • 3
  • 25
  • 32
LifeQuery
  • 3,202
  • 1
  • 26
  • 35
  • `new` is for factories, and in a very limited amount of code. You want to avoid calling `new` in business logic. [this talk takes +/- 40 minutes, but is well worth your time](http://www.youtube.com/watch?v=RlfLCWKxHJ0). – Wrikken Feb 09 '14 at 01:55
  • You should pass inject a factory in your controller. The "how to make it" might be explained a bit in [this post](http://stackoverflow.com/a/11369679/727208). Also "model" is a layer, not a class. More about it: [here](http://stackoverflow.com/a/5864000/727208). – tereško Feb 09 '14 at 08:34
  • @tereško in practice, if I added a factory for instantiating my classes, where would I place this class? it sounds as if it doesn't belong in a model and controller directory – LifeQuery Feb 09 '14 at 13:50
  • @LifeQuery factory itself wouldn't really be part of MVC pattern. I tend to put such structures in `/lib/common/`. Especially since they are usually reusable. – tereško Feb 09 '14 at 21:31

2 Answers2

6

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.

Henrique Barcelos
  • 7,670
  • 1
  • 41
  • 66
  • Thanks for taking the time to write all of this. I'm still trying to figure out all the bits and pieces of this puzzle. – LifeQuery Feb 10 '14 at 22:52
  • I know this can sound a little complex, but the essence is to put less and less information INSIDE your objects, make all (or almost) information come from the "outter world". This way, you will prime for flexibility. – Henrique Barcelos Feb 10 '14 at 22:56
1

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.

thrau
  • 2,915
  • 3
  • 25
  • 32
  • why is a database connection a service provided across MVC tiers? I thought the view should have no relation to the database, isn't it strictly the model's job to deal with database related responsibilities? – LifeQuery Feb 09 '14 at 13:44
  • in a lot of php web frameworks this is the case, as model objects tend to be [active records](https://en.wikipedia.org/wiki/Active_record). when you don't use active records, you have some sort of service that provides you with CRUD functionality, which is usually called by the contorller. most explanations of MVC state that "the controller manipulates the model", this does not imply, that the model has a database connection. – thrau Feb 09 '14 at 23:35