Properly calling the database from Model in an MVC application?

15,267

Solution 1

Warning:
The information in this posts is extremely outdated. It represents my understanding of MVC pattern as it was more then 2 years ago. It will be updated when I get round to it. Probably this month (2013.09).

Damn it! (2017.11).

Model itself should not contain any SQL. Ever. It is meant to only contain domain business logic.

The approach i would recommend is to separate the responsibilities, which are not strictly "business logic" into two other other sets of constructs : Domain Objects and Data Mappers.

For example, if you are making a blog, then the Model will not be Post. Instead most likely the model will be Blog , and this model will deal with multiple Domain Objects: multiple instances of Post, Comment, User and maybe other objects.

In your model, the domain objects should not know how to store themselves in database. Or even be aware of the existence of any form of storage. That is a responsibility of Data Mappers. All you should do in the Model is to call $mapper->store( $comment );. And the data mapper should know how to store one specific type of domain objects, and win which table to put the information ( usually the storage of of single domain object actually affects multiple tables ).


Some code

(only relevant fragments from files):

  • I assume that you know how to write a good constructor .. if you have doubts, read this article
  • nothing is namespaced in example, but it should be
  • anything that begins with _ in example is protected

from /application/bootstrap.php

/* --- snip --- */

$connection = new PDO( 'sqlite::memory:' );
$model_factory = new ModelFactory( $connection );

$controller = new SomeController( $request , $model_factory );

/* --- snip --- */

$controller->{$action}();

/* --- snip --- */
  • controller does not need to be aware of database connection.
  • if you want to change DB connection for whole application, you need to change single line
  • to change the way how Model's are made, you create different class which implements same interface as ModelFactory

from /framework/classes/ModelFactory.php

/* --- snip --- */

class ModelFactory implements ModelBuilderInterface
{
   /* --- snip --- */

   protected function _prepare()
   {
      if ( $this->_object_factory === null  )
      {
         $this->_object_factory = new DomainObjectFactory;
      }
      if ( $this->_mapper_factory === null )
      {
         $this->_mapper_factory = new DataMapperFactory( $this->_connection );
      }
   }

   public function build( $name )
   {
      $this->_prepare();
      return new {$name}( $this->_object_mapper , $this->_data_mapper );
   }

   /* --- snip --- */

}
  • only data mappers will use database , only mapper factory need connection
  • all the dependencies of Model are injected in constructor
  • every DataMapper instance in the application uses same DB connection, no Global State (video) required.

file /application/controllers/SomeController.php

/* --- snip --- */

   public function get_foobar()
   {
      $factory = $this->_model_factory;
      $view = $this->_view;

      $foo = $factory->build( 'FooModel' );
      $bar = $factory->build( 'BarModel' );

      $bar->set_language( $this->_request->get('lang') );

      $view->bind( 'ergo' , $foo );

      /* --- snip --- */

   }

/* --- snip --- */
  • controller is unaware of model creation details
  • controller is only responsible for wiring and changing the state of elements

file /application/models/FooModel.php

/* --- snip --- */

   public function find_something( $param  , $filter )
   {
      $something = $this->_object_factory('FooBar');
      $mapper = $this->_mapper_factory('FooMapper');

      $something->set_type( $param );
      $mapper->use_filter( $filter )->fetch( $something );

      return $something;
   }

/* --- snip --- */
  • domain object is responsible for validating the given parameters
  • view receives and decides how to present it
  • mapper takes the object and puts in it all the required information from storage ( it doesn't have to be DB .. it could be taken from some file, or an external REST API )

I hope this will help you understand the separation between DB logic and business logic ( and actually , presentation logic too )


Few notes

Model should never extend Database or ORM, because Model is not a subset of them. By extending a class, you are declaring that has all the characteristics of the superclass, but with minor exceptions.

class Duck extends Bird{}
class ForestDuck extends Duck{}
// this is ok

class Table extends Database{}
class Person extends Table{}
// this is kinda stupid and a bit insulting

Besides the obvious logic-issues, if your Model is tightly coupled with underlaying Database, it makes the code extremely hard to test (talking about Unit Testing (video)).


I personally think, that ORMs are useless and in large project - even harmful. Problem stems from the fact that ORMs are trying to bridge two completely different ways of approaching problems : OOP and SQL.

If you start project with ORM then, after short learning curve, you are able to write simple queries very fast. But by the time you start hitting the ORM's limitations and problems, you are already completely invested in the use of ORM ( maybe even new people were hired , who were really good at your chosen , but sucked at plain SQL ). You end up in situation where every new DB related issue take more and more time to solve. And if you have been using ORM based on ActiveRecord pattern, then the problems directly influence your Models.

Uncle Bob calls this "technical debt".


Few books

loosely related to subject

Solution 2

In my experience different frameworks interpret MVC somewhat loosely and usually with some deviations. However they usually agree that MVC is divided like this:

  • Model - Business Logic + Data storage / retrieaval / structuring
  • View - Presentation of data
  • Controller - Calling methods on model after analyzing request

I use Symfony a lot and can give you a couple of small examples. Mind you, these are hugely simplified. :p

The Model:

class MyUnitTable extends Doctrine_Table
{
    // .. various other pieces of code added by Symfony ... //
    public function getForAccount( Account $_account ) {
    $q = $this
         ->createQuery('e')
         ->leftJoin('e.User u')
         ->leftJoin('u.Profile p')
         ->leftJoin('p.Account a')
         ->where('a.accountid = ?', $_account->getAccountid());
    return $q->execute();
    }
}

class myUnitActions extends sfActions
{
    public function executeIndex(sfWebRequest $request)
    {
        $this->units = Doctrine_Core::getTable('MyUnit')->getForAccount($foo);
    }
}

The View (just showing a snippet here):

<div id="my-unit-list-container">
    <ul>
    <?php foreach( $units as $unit ): ?>
        <li><?php echo $unit->getName(); ?></li>
    <?php endforeach; ?>
    </ul>
</div>

The Controller:

The controller is the place where a request gets handled. It will analyze the request and figure out which action (eg. myUnitActions::executeIndex() from above) in a model that should be called.

Last notes:

I'm sure you can see whats happening in the code above (a lot of convenience is added by the ORM). You have the controller dispatching a request to the model, the model operating withing your problem-domain plus actually pulling data from a database and lastly the view displaying the data.

This has many benefits to you as a developer as it allows for easier and more reliable testing, among other things.

An excellend read for you would be the 21 Days With Jobeet guide from the people behind Symfony. It's a nice piece of work.

You should also have a look at the code for both Symfony and Zend. They are both excellend. Also have a look at a couple of ORM's like Doctrine and Propel.

Also see the Wikipedia article on MVC.

Share:
15,267

Related videos on Youtube

Tek
Author by

Tek

Updated on May 31, 2022

Comments

  • Tek
    Tek almost 2 years

    I'm building a tiny MVC framework for learning/experimenting and small project purposes. I needed to find out the basics of the internals of the Model since a full MVC framework and ORM is overkill for just a few database calls.

    Class Model
    {
    }
    

    Using an empty class where would I have to call a new PDO object for database calls?

    What would calling a query look like inside the Model?

    Additionally, where can I find a beginner's web/book resource to MVC (with lots of example code)? I've heard a lot of terms such as business logic and database logic. I remember reading somewhere that you should separate business logic and database logic. I can understand the concept somewhat, I just wonder what it looks like or what they mean in code itself. I'm confused how business logic and database logic should be separated but still be inside the Model.

    I'm mostly looking for code/logic examples as answers, except maybe the latter paragraph.

  • Tek
    Tek about 13 years
    Googling MVC is the kind of thing I'm trying to stay away from. I don't know the validity of the sources or if they're correct or just an oversimplification of it all.
  • Sander Marechal
    Sander Marechal about 13 years
    You think a PDO object per model class is slow? I once came across a legacy app where a separate Query class extended the database connection class so, it tried to open a new database connection for every query! I should have sent it to TheDailyWTF.
  • Tek
    Tek about 13 years
    This answers most of my questions except the first one. I also appreciate the links to the Symphony/Zend, but it's too much to dig through and I was looking for a straightforward example for the most part. As for an ORM like I had already said, it's overkill for this particular setting.
  • GordonM
    GordonM about 13 years
    Yeah, speaking of dailyWTF, one of my first programming jobs was building a CMS to replace the one that the original developer must have paid a train monkey to make in bananas. It would connect to the DB, do a query, then disconnect again for EVERY QUERY in the script! And it did it in a loop as well! Fun times.
  • Tek
    Tek about 13 years
    Is a mapper strictly needed for MVC?
  • tereško
    tereško about 13 years
    @Tek no it is not mandatory, but having such separation makes the code easier to maintain.
  • tereško
    tereško about 13 years
    -1 Your list for the responsibilities of MVC elements is wrong. You probably should refresh your knowledge about meaning and roles for each part of MVC triad.
  • Christopher
    Christopher about 13 years
    @teresko Thanks for pointing that out. I must surely have had my head screwed on backwards yesterday.
  • Wex
    Wex about 12 years
    Where would you want to store your Domain Object files (following the loose architecture you defined in this post)?
  • tereško
    tereško about 12 years
    I have a habit of using namespaces that mimic directories , so i would have something like Domain\UserObject and Persistence\FooMapper in the /application/domain/ and /application/persistence directories.