I'm building a small and simple PHP content management system and have chosen to adopt an MVC design pattern.
I'm struggling to grasp how my models should work in conjunction with the database.
I'd like to separate the database queries themselves, so that if we choose to change our database engine in the future, it is easy to do so.
As a basic concept, would the below proposed solution work, is there a better way of doing things what are the pitfalls with such an approach?
First, I'd have a database class to handle all MySQL specific pieces of code:
class Database
{
protected $table_name;
protected $primary_key;
private $db;
public function __construct()
{
$this->db = DatabaseFactory::getFactory()->getConnection();
}
public function query($sql)
{
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll();
}
public function loadSingle($id)
{
$sql = "SELECT * FROM $this->table_name WHERE $this->primary_key = $id";
return $this->query($sql);
}
public function loadAll()
{
$sql = "SELECT * FROM $this->table_name";
return $this->query($sql);
}
}
Second, I'd have a model, in this case to hold all my menu items:
class MenuItemModel
{
public $menu_name;
public $menu_url;
private $data;
public function __construct($data)
{
$this->data = $data;
$this->menu_name = $data['menu_name'];
$this->menu_url = $data['menu_url'];
}
}
Finally, I'd have a 'factory' to pull the two together:
class MenuItemModelFactory extends Database
{
public function __construct() {
$this->table_name = 'menus';
$this->primary_key = 'menu_id';
parent::__construct();
}
public function loadById($id)
{
$data = parent::loadSingle($this->table_name, $this->primary_key, $id);
return new MenuItemModel($data);
}
public function loadAll()
{
$list = array();
$data = parent::loadAll();
foreach ($data as $row) {
$list[] = new MenuItemModel($row);
}
return $list;
}
}
Your solution will work of course, but there are some flaws.
Class Database
uses inside it's constructor class DatabaseFactory
- it is not good. DatabaseFactory
must create Database
object by itself. However it okay here, because if we will look at class Database
, we will see that is not a database, it is some kind of QueryObject
pattern (see link for more details). So we can solve the problem here by just renaming class Database
to a more suitable name.
Class MenuItemModelFactory
is extending class Database
- it is not good. Because we decided already, that Database
is just a query object. So it must hold only methods for general querying database. And here you mixing knowledge of creating model with general database querying. Don't use inheritance. Just use instance of Database
(query object) inside MenuItemModelFactory
to query database. So now, you can change only instance of "Database", if you will decide to migrate to another database and will change SQL syntax. And class MenuItemModelFactory
won't change because of migrating to a new relational database.
MenuItemModelFactory
is not suitable naming, because factory purpose in DDD (domain-driven design) is to hide complexity of creating entities or aggregates, when they need many parameters or other objects. But here you are not hiding complexity of creating object. You don't even "creating" object, you are "loading" object from some collection.
So if we take into account all the shortcomings and correct them, we will come to this design:
class Query
{
protected $table_name;
protected $primary_key;
private $db;
public function __construct()
{
$this->db = DatabaseFactory::getFactory()->getConnection();
}
public function query($sql)
{
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll();
}
public function loadSingle($id)
{
$sql = "SELECT * FROM $this->table_name WHERE $this->primary_key = $id";
return $this->query($sql);
}
public function loadAll()
{
$sql = "SELECT * FROM $this->table_name";
return $this->query($sql);
}
}
class MenuItemModel
{
public $menu_name;
public $menu_url;
private $data;
public function __construct($data)
{
$this->data = $data;
$this->menu_name = $data['menu_name'];
$this->menu_url = $data['menu_url'];
}
}
class MenuItemModelDataMapper
{
public function __construct() {
$this->table_name = 'menus';
$this->primary_key = 'menu_id';
$this->query = new Query();
}
public function loadById($id)
{
$data = $this->query->loadSingle($this->table_name, $this->primary_key, $id);
return new MenuItemModel($data);
}
public function loadAll()
{
$list = array();
$data = $this->query->loadAll();
foreach ($data as $row) {
$list[] = new MenuItemModel($row);
}
return $list;
}
}
Also consider reading this: