I am using the Data Mapper Pattern and I am wondering what is the best way to handle relationships with that pattern. I spent a lot of time searching for solutions in Google and Stack overflow, I found some but I am still not completely happy about them, especially in one special case that I will try to explain.
I am working with PHP so the examples of code that I will put are in PHP.
Let's say I have a table "team" (id, name) and a table "player" (id, name, team_id). This is a 1-N relationship.
By implementing the Data Mapper pattern, we will have the following classes: Team, TeamMapper, Player and PlayerMapper.
So far, everything is simple. What if we want to get all players from a team?
The first solution I found is to create a method getAllPlayers() in the Team class which will handle that with lazy loading and proxies. Then, we can retrieve the players of a team like that:
$players = $team->getAllPlayers();
The second solution I found is to directly use the PlayerMapper and pass the team ID as parameter. Something like:
$playerMapper->findAll(array('team_id' => $team->getId()));
But now, let's say that I want to display a HTML table with all the teams and with a column 'Players' with all of the players of each team. If we use the first solution I described, we will have to do one SQL query to get the list of teams and one query for each team to get the players, whcih means N+1 SQL queries where N is the number of teams.
If we use the second solutions I described, we can first retrieve all team IDs, put them in an array, and then pass it to the findAll method of the player mapper, something like that:
$playerMapper->findAll(array('team_id' => $teamIds));
In that case, we need to run only 2 queries. Much better. But I am still not very happy with that solution because the relationships are not described into the models and it is the developer who must know about them.
So my question is: are there others alternatives with the Data Mapper pattern? With the example I gave, is there a good way to select all teams with all players in just 2 queries with the description of the relationships into the model?
Thank you in advance!
If you look at Martin Fowler's text that describes how the DataMapper works, you'll see that you can use one query to get all the data that you need and then pass that data to each mapper, allowing the mapper to pick out only the data that it needs.
For you, this would be a query that joins from Team to Player, returning a resultset with duplicated Team data for each unique Player.
You then have to cater for the duplication in your mapping code by only creating new objects when the data changes.
I've done something similar where the equivalent would be the Team mapper iterating over the result set and, for each unique team pass the result set to the Player mapper so that it can create a player and then add the player to the team's collection.
While this will work, there are problems with this approach further downstream...
I have a possible solution to this problem that I have implemented successfully in one of my projects. It is not so complex and would use only 2 queries in the example described above.
The solution is to add another layer of code responsible for handling relationships.
For instance, we can put that in a service class (which can be used for other stuff as well, not only handling relationships).
So let's say that we have a class TeamService on top of Team and TeamMapper. TeamService would have a method getTeamsWithRelationships() which would return an array of Team objects. getTeamsWithRelationships() would use TeamMapper to get the list of teams. Then, with the PlayerMapper, it would get in only one query the list of players for these teams and set the players to the teams by using a setPlayers() method from the Team class.
This solution is quite simple and easy to implement, and it works well for all types of database relationships. I guess that some people may have something against it. If so, I would be interested to know what are the issues?
Related
I've modeled the following UML for the database of our website (uni project):
However, I can't seem to find how to convert to SQL the generalization case between Post, Story and Comment. My teacher suggested to use the same table, but I think that limits me if I want to add more features, like tags for stories. Right now I already have two extra relations: a post can have several child comments, and a story belongs to a specific channel.
Should I follow my teacher's suggestion? Or should I use 3 classes?
Since your class hierarchy has only two subclasses that come with only 3 additional properties (title, channel and post), you should use the Single Table Inheritance approach. Even when you may add a few more properties/methods later on, this will be the best choice, since implementing your class hierarchy with multiple tables would imply a much greater effort/overhead.
You can read more about this choice in the section Representing class hierarchies with SQL database tables (of a tutoral on handling class hierarchies in a JavaScript front-end web app authored by me).
As always it depends. Having a single table has the advantage of accessing related post info in a simple query and just looking at a flag inside the column telling what kind of object you're dealing with. Having separate tables is a more "academic" way of solving it since you (as you noticed) have object information separated.
I'd probably go with the single table approach in this case. But - YMMV - you can as well stick to your 3 table approach. There might come other opinions here. Just wait a few days to decide :-)
Is there any point in having both? I followed a Zend tutorial which told me to create a user model but can't I just model the user in the DB model class?
Thanks
It's part and parcel of the concept of "separation of concerns" http://en.wikipedia.org/wiki/Separation_of_concerns
Your model class takes care of business logic, validation, variable manipulation etc and your db model class should just handle dealing with the database, that way if you need to replace the db model it wont affect the main model as much, and if you replace the main model it wont affect the db model as long as the right parameters are passed into it.
Yes, you can model your user in Application_Model_DbTable_User (DbTable class) and it will work fine as long as all of the data you need is in that one table.
At some point you will likely start working with objects that should be represented in more then one table. This is when you will discover the need for a different way to do things.
A better simple example of where a mapper and domain object might matter:
The mp3 file.
At it simplest a single music file should probably be represented by at least 3 db tables:
album: a table representing the album the song comes from
artist: the artist that recorded the song
track: the information about the track itself
each artist can have multiple albums, each album will have one artist and will have multiple tracks, each track will belong to one album and one artist (different versions will be different tracks).
As you can see the database stucture of even a simple mp3 collection can become very complex very quickly. In most cases even a simple user record will spread across multiple tables depending upon how much and what kind of data is being persisted.
For some help in understanding how and why to implement mappers and models I found these resources very helpful.
Building a Domain Model, covers what a domain model is and how to build one in PHP.
Adding Mappers to a Domain Model, adds mapper function to the same domain model.
Survive the Deepend, A Zend Framework starter that focuses on models and testing.
The first two links are done in plain PHP but are still very helpful.
Let's say I have three tables: users, books, and users_books.
In one of my views, I want to display a list of all the books the current user has access to. A user has access to a book if a row matching a user and a book exists in users_books.
There are (at least) two ways I can accomplish this:
In my fetchAll() method in the books model, execute a join of some sort on the users_books table.
In an Acl plugin, first create a resource out of every book. Then, create a role out of every user. Next, allow or deny users access to each resource based on the users_books table. Finally, in the fetchAll() method of the books model, call isAllowed() on each book we find, using the current user as the role.
I see the last option as the best, because then I could use the Acl in other places in my application. That would remove the need to perform duplicate access checks.
What would you suggest?
I'd push it all down into the database:
Doing it in the database through JOINs will be a lot faster than filtering things in your PHP.
Doing it in the database will let you paginate things properly without having to jump through hoops like fetching more data than you need (and then fetching even more if you end up throwing too much out).
I can think of two broad strategies you could employ for managing the ACLs.
You could set up explicit ACLs in the database with a single table sort of like this:
id: The id of the thing (book, picture, ...) in question.
id_type: The type or table that id comes from.
user: The user that can look at the thing.
The (id, id_type) pair give you a pseudo-FK that you can use for sanity checking your database and the id_type can be used to select a class to provide the necessary glue to interact the the type-specific parts of the ACLs and add SQL snippets to queries to properly join the ACL table.
Alternatively, you could use a naming convention to attach an ACL sidecar table to each table than needs an ACL. For table t, you could have a table t_acl with columns like:
id: The id of the thing in t (with a real foreign key for integrity).
user: The user the can look at the thing.
Then, you could have a single ACL class that could adjust your SQL given the base table name.
The main advantage of the first approach is that you have a single ACL store for everything so it is easy to answer questions like "what can user X look at?". The main advantage of the second approach is that you can have real referential integrity and less code (through naming conventions) for gluing it all together.
Hopefully the above will help your thinking.
I would separate out your database access code from your models by creating a finder method in a repository class with an add method like getBooksByUser(User $user) to return a collection of book objects.
Not entirely sure you need ACLs from what you describe. I maybe wrong.
Is there a best practice in getting data from multiple database tables using Zend? I would like to know rather than end up wanting to refactor the code I write in the near future. I was reading the Zend documentation and it said that:
"You can not specify columns from a
JOINed tabled to be returned in a
row/rowset. Doing so will trigger a
PHP error. This was done to ensure
the integrity of the Zend_Db_Table is
retained. i.e. A Zend_Db_Table_Row
should only reference columns derived
from its parent table."
I assume I therefore need to use multiple models -- is that correct? If, for example, I want to get out all orders for a particular user id where the date is in between two dates what would I do?
I know that it would be possible to access the two different models from a controller and then combine their respective data in the action but I would not feel happy doing this since I have been reading survivethedeepend.com and it tells me that I shouldn't do this...
Where, why, and how? :)
Thanks!
If you're reading ZFSTDE, in chapter 9 (http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors) this problem is addressed by using a data mapper.
Also, you can join 2 tables, just be sure to first call on the select object the setIntegrityCheck(false) method. The docs say that a row should reference a parent table, doesn't mean it can not :)
Stop thinking about Zend_Db_Table as your "model".
You should write your own, rich, domain-centric model classes to sit between your controllers (and views), and your persistence logic (anything that uses Zend_Db/Zend_Db_Table/Zend_Db_Select) to load/store data from the database.
Sure, you can query several db tables at the same time. Take a look at the official ZF docs here http://framework.zend.com/manual/en/zend.db.select.html#zend.db.select.building.join
As for your example with getting all orders of a single user, table relationships are the answer http://framework.zend.com/manual/en/zend.db.table.relationships.html
I`ve been wondering this one thing about creating models.
If I make for example Page model. Is it the both: It can retrieve one row from the table or all the rows. Somehow Im mixing the objects and the database.
I have thought it like this:
I would have to make a Page-class that would represent one row in the table. It also would have all the basic CRUD-methods.
Then I would have to do a Pages-class (somekind of collection) that would retrieve rows from the table and instantiate a Page object from each row. Is this kind of weird?
If someone could explain to me the idea of model throughout.. Im again confused.
Maybe Im thinking the whole OOP too difficult..
And by the way this forum is great. Hopefully people will just understand my problems. Heh. I was a long time procedural style programmer and now in 3 months I have dived into OOP and MVC and PHP frameworks and I just get more excited day by day when I explore this stuff!
That depends on whether your Models represent instances or are just ORM objects.
If a Model represents an instance, then each record (row) in the database would become a new Model object. "All records" would simply be represented by an array of these objects. Ruby on Rails for example does it this way.
If your Model is rather an ORM object, it just represents the database as such and allows you to retrieve records from the database through it. The results may be in some container object or just a normal array. CakePHP for instance uses this method.
Is it the both: It can retrieve one
row from the table or all the rows.
You can use a model to interact with the database, there by you can do anything you like, for example getting one or more records, inserting records, updating, etc.
The way to go I would suggest you is to create a model for each of the distinct pages of your site that might interact with the database. You might want to create different functions inside a single model for a single page based on the page's requirements to interact with the database.