I am stuck trying to write good data models for my application. Let's take persons as an example of data. How would I make a non-repeatable, but reusable separation of a Person
class, as opposed to the People
class? It seems utterly wrong to have to define table (and their joined tables) twice, for both classes.
class Person extends CommonModel {
protected static $table = 'people';
protected static $join = array('city' => 'city.id = people.city_id');
public function __construct($id) {
// Initialize stuff
}
public function fetch($id) {
// Return person with id $id
}
public function isVeryTall() {
// Return boolean
}
}
class People extends CommonModel {
protected static $table = 'people';
protected static $join = array('city' => 'city.id = people.city_id');
public function __construct($id) {
// Initialize stuff
}
public function fetch() {
// Return all persons
}
}
But if I join them into a single class, then it won't make sense that some methods, which only make sense in a context of a single person, will be available when a list of people is returned. For example, the isVeryTall()
method only really works if I invoke it on an object that represents one person. But if I had called the fetchPeople()
method, then my isVeryTall()
method will be available in the returned object, and it will have no sense (actually it probably wouldn't even work correctly).
class Person extends CommonModel {
protected static $table = 'people';
protected static $join = array('city' => 'city.id = people.city_id');
public function __construct($id) {
// Return one person
}
public function fetchPeople() {
// Returns all people
}
public function isVeryTall() {
// Return boolean
}
}
And another question is, how do I implement the method that returns a list of people, to return objects of people, instead of just arrays? Would I manually have to loop through all the rows and then instantiate them into objects?
public function fetchPeople() {
$people = $this->fetchAll();
foreach($people as $id => $person) {
$people[$id] = new Person($person);
}
return $people;
}
I wouldn't put implementation-specific persistence code (your database stuff) in your domain classes. Keep them separate. Then you can have a Person class that does the stuff you need for one person. For a collection of Person objects, you can define a new class if there are specific methods you need for the group (that aren't persistence related), or you can use one of the built in collection types (array).
There are a number of ways to keep the persistence (database) code separate. I use the Repository pattern. I create a class with the methods necessary to translate between my domain objects and my persistence strategy (database). Methods like:
getPerson(PersonCriteria $pc);
getPeople(PersonCriteria $pc);
savePerson(Person $p);
deletePerson($personID);
etc()...
What you will end up doing is recreating the wheel for a lot of the pieces you need. From associations and collections which solves the last question asked. You will have to construct database queries from the associations and collections. You would be creating portions of a standard library which may not be what your goal is. I would look at standard ORMs out there through the pear library or take a look at https://github.com/gabordemooij/redbean. Some others are Doctrine and Propel.
If you would like to roll your own then and want to continue with the first approach and setup a collections class to do the pagination and queries for larger batches of data using where clauses.
So that
// in a base db class
// filters would be pagination and pairs to be used in where clause filtering
public function fetch($filters){
return $this->query($filters)
}
// ... in your child class .. using the existing join and table properties .. I would recommend moving
// the associations out into another class but as you can see the details of this can go on and on
public function fetchPeople($limit = 10, $page = 1) {
$associations = {'table'=> $this->table, 'join'=>$this->join}
$people = $this->db->fetch(array('limit'=>$limit,'page'=>$page,$associations);
foreach($people as $id => $person) {
$people[$id] = new Person($person);
}
return $people;
}