0

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:

  1. 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)?

  2. 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)...

Community
  • 1
  • 1
Irfan jamal
  • 529
  • 2
  • 8
  • 21

2 Answers2

1

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

Your Common Sense
  • 156,878
  • 40
  • 214
  • 345
  • if I understand you correctly, you're saying that I should create a Model class with CRUD methods...but then, the CRUD methods for each model would need to be different, wouldn't they? So would the SQL be in each model? And how would I pass the PDO object between models? – Irfan jamal Apr 07 '14 at 13:45
  • Why so? create() will take just array of data. update() will take just array with data and identifier. read() and delete() just identifier. – Your Common Sense Apr 07 '14 at 13:54
  • Well, modern frameworks, like `Symfony2` and `Zend2` pass `$pdo` as an instance via `Service Manager` for some reason.. – Yang Apr 08 '14 at 08:35
  • @YourCommonSense, I'm not saying I don't like the approach...but when it comes to the base CRUD models, what if the READ method is complex for a certain bit of data (i.e. spanning multiple tables, etc.). I thought, for readability, it would be easier to have a set of specific functions in the data mapper that would gather that data... – Irfan jamal Apr 08 '14 at 12:59
  • @Irfanjamal that's what particular models are for. You can always extend prototype method or create several additional methods. But we are talking of the base mapper where only base methods should be – Your Common Sense Apr 08 '14 at 13:12
  • @YourCommonSense, Yeah...i get it..I think I will implement the abstract model...but for the PDO object, I just spent some time reading [link](http://stackoverflow.com/questions/4595964/is-there-a-use-case-for-singletons-with-database-access-in-php/4596323#4596323) and I think I'm going to go with the static method for now, rather than a singleton...thanks for your help :) – Irfan jamal Apr 08 '14 at 13:32
  • @Irfanjamal what is that "static method" you are talking about? Does it have something to do wit static variables? If so - it would be the same singleton then. – Your Common Sense Apr 08 '14 at 13:36
  • @YourCommonSense it's explained [here](http://stackoverflow.com/questions/4595964/is-there-a-use-case-for-singletons-with-database-access-in-php/4596323#4596323). Static is slightly different from a singleton (I think?...lol) – Irfan jamal Apr 08 '14 at 18:27
  • @YourCommonSense Just curious, does the BaseModel in my answer fit the Datamodel description? What is a good and simple prototype/base model? – Barry Staes Apr 14 '14 at 06:27
  • @BarryStaes I wouldn't make it abstract. quite contrary, in most models you will need exactly the same code that will differ only in table names. So - why writing repeated code again and again? Just set table name as a private property and use it in methods – Your Common Sense Apr 14 '14 at 06:32
  • "prototype Model class, from which all application models are inherited" - smells like active record to me, which opposes data mapper. – Jimbo May 15 '15 at 14:23
1

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.

Barry Staes
  • 3,890
  • 4
  • 24
  • 30
  • Perhaps I am over-complicating things...I think I'll drop the data-mapper and just use the inheritance idea with CRUD functions...I like the suggestion of not reading TOO much (that tends to slow me down, and now that I have a basic grasp of things I just want to get the project complete)...appreciate the suggestions and the code samples :) – Irfan jamal Apr 08 '14 at 13:02
  • Indeed you got it. The fact that you know solutions exists doesnt mean you must apply them. I find that hard to apply myself, but its solid advise. – Barry Staes Apr 08 '14 at 13:10
  • My favorite DIY framework is like that, with only 2 more significant kinds of objects: Listers, and a UserMessageLog that accumulates Models Validate() and LoadFromFormParams(). The page can output UserMessageLog if it wants. If some page needs specialized functionality, it can include and use a library itself. The framework stays rather simple, its just a peg to attach things to. – Barry Staes Apr 08 '14 at 13:17
  • 1
    One developer's over-engineering is another developer's much needed abstraction. The data mapper is a simple pattern that certainly *isn't* over-engineering, however, and is used widely by many frameworks and libraries. Also, sounds like `Validate()` should be in a `Validator` composite or similar for decent SoC, and `UserMessageLog()` decorated via AOP. Not over-complicating, but if you want to do OOP, then do it :-) Finally, the model is a *layer*, we don't have models. Also, use composer for autoloading, please! You'll love it and it'll save you *so much time*, you only need 1 require. – Jimbo May 15 '15 at 14:24