Let's assume the following tables setup for a Zend Framework app.
user (id)
groups (id)
groups_users (id, user_id, group_id, join_date)
I took the Data Mapper approach to models which basically gives me:
Model_User, Model_UsersMapper, Model_DbTable_Users
Model_Group, Model_GroupsMapper, Model_DbTable_Groups
Model_GroupUser, Model_GroupsUsersMapper, Model_DbTable_GroupsUsers (for holding the relationships which can be seen as aentities; notice the "join_date" property)
I'm defining the _referenceMap in Model_DbTable_GroupsUsers:
protected $_referenceMap = array (
'User' => array (
'columns' => array('user_id'),
'refTableClass' => 'Model_DbTable_Users',
'refColumns' => array('id')
),
'App' => array (
'columns' => array('group_id'),
'refTableClass' => 'Model_DbTable_Groups',
'refColumns' => array('id')
)
);
I'm having these design problems in mind:
1) The Model_Group only mirrors the fields in the groups table. How can I return a collection of groups a user is a member of and also the date the user joined that group for every group? If I just added the property to the domain object, then I'd have to let the group mapper know about it, wouldn't I?
2) Let's say I need to fetch the groups a user belongs to. Where should I put this logic? Model_UsersMapper or Model_GroupsUsersMapper?
I also want to make use of the referencing map (dependent tables) mechanism and probably use findManyToManyRowset or findDependentRowset, something like:
$result = $this->getDbTable()->find($userId);
$row = $result->current();
$groups = $row->findManyToManyRowset(
'Model_DbTable_Groups',
'Model_DbTable_GroupsUsers'
);
This would produce two queries when I could have just written it in a single query. I will place this in the Model_GroupsUsersMapper class.
An enhancement would be to add a getGroups method to the Model_User domain object which lazily loads the groups when needed by calling the appropriate method in the data mapper, which begs for the second question. Should I allow the domain object know about the data mapper?
This can be pretty confusing issue because a relational database can be hard to map to an object model.
I'd be inclined to focus on your object model requirements first. For example if in your object model it makes sense for your user object to (almost) always be aware of the groups then this relationship should be incorporated into the user class. If on the other hand you often need to use users without needing to know what groups they are a part of perhaps you can have two user classes, a base class and a group aware extended version (base_user and group_user).
I would try to avoid letting the domain object know about the data layer as that's kind of the whole point to using this pattern. The data layer should pretty much just be a dumb factory that instantiates your domain objects.
Just my take on it :)
Related
I have a Movie model with the following associations (belongsToMany):
Actor
Country
Genre
...
When a form is posted, I have this data (skipping a lot of details here):
'actors' => array(
'Cary Grant',
'Grace Kelly',
...
),
'genres' => array(
'Drama',
...
),
...
I'd like my update()/store() controller function to easily associate these Models.
An actor with the name 'Cary Grant' may or may not exist and may or may not be already associated with the movie I'm editing. Also I could remove him from this movie, so I'd need to remove the association. Same with Genre and everything else.
So I thought I'd do a BaseModel and do all of this only once in there, like this:
1. get the Movie Model's defined associations.
2. check if POST data contains those associations.
3. for each of them, check if they exist (if not create them) and return an array of ids. the column I'm checking is 'name', but it could be configurable.
4. sync() those ids.
For now, I don't need to add more stuff to the related model from the movie form (ex. an actor's birthdate).
I'm stuck at n.1 ($movie->getRelations() only works for existing movies) and in general I'm not sure if this is the right approach. Hints?
use direct relations: $model->actors
http://laravel.com/docs/4.2/eloquent#relationships
All sync() stuff will be done by ORM, so the most complex thing you should do is n.3. You must create diff between $model->actors and $_POST['actors'] and understand what actors you need to insert (create new actors).
I've met the same problem and this is what I do in my project:
Instead of retrieving all defined relations of the models, I white-listing relations that can be updated by adding a static member
Movie::$editableRelations = ['actors', 'genres', 'countries'];
Loop through the post data, and match with the $editableRelations arrays. If the data of a relation exists, following below steps, otherwise we simply do not touch this relation.
Step 3 and step 4 are the same as yours.
Recently started working with OOP in PHP. Following the "code to an Interface" principle, i got confused as to the type hint to use when passing a single object or multiple as argument to a method.
Currently, i have a "Student" class - represents a row in my students table, i also have a "Students" class that holds multiple student objects in an array.
To fetch the profile of one student, i pass the Students object (holding a single student object) to the profile class. I set a Students type hint in the profile class.
Now i feel this is bad code as i have lines like this
student = new Students();
and students = new Students();
question is,
am i on the right path?
if i remove the Students class and work with Student alone, based on the principle, how do i pass multiple Student objects (assuming array) to the profile class if it accepts a Student type hint?
what options do i have?
Thanks.
If by Students you mean a collection of Student objects, perhaps a better name would be StudentCollection or StudentSet.
There are two ways around the type hint problem:
Introduce a method on StudentCollection called ->getProfiles(); it would return an array of profiles for each Student instance it's managing by calling methods on Profile.
Introduce a (static) method on Profile that operates on a StudentCollection instance.
The first option has feature envy, which is why I've included a workaround.
Instead of reinventing the wheel you might want to try Doctrine or at least take a look at its architecture.
I'm not sure if I get your exact issue... But if you want to go for your own code I would first abstract the DB layer as well and have some base classes like Database, Table, Row, Field that an describe the DB stack and extend them as needed with some magic methods. So when you do Student extends Table it would automatically check for a "students" table or whatever else convention you like to implement. Alternatively you could just pass the table name as arg.
Whatever Object is returning the result set from the database would have to construct a single Row object for each row and add it to a collection of rows that I would name ResultSet and contains all the row objects and return that collection.
I have three classes to define objects: Users, Members and Projects.
The User class defines details such as id, email_address and
name.
The Member class defines details such as the id, user_id,
project_id and datetime_accepted.
The Project class defines details such as id and title - this
isn't important though.
The system has Users and Projects. A Member is a User working on a Project. In other words, the Member class defines a link between two objects.
My question is this:
I want to get a list of members belonging to a certain project, and I want to collect variables from both classes (Member and User - such as User:name and Member:datetime_accepted) in my result set.
Do I need to define a new class that has all the variables from both classes, or is there some other, more efficient structure that I can use to handle this neatly and in an object oriented manner?
You can easly cast StdObject into array by:
$result = array_merge((array)$user, (array)$member);
then you will have an array of variables you need. Add this to new function in Project class,
or consider using Member as child of User class.
When working with Active Record Models, you would generally have a method in one object to get related objects. If you wanted to simplify the SQL to one query, you're getting away from the Active Record Model. This is fine, it just changes the way you approach the problem.
What I have done in the past is one of two approaches:
To add 'virtual properties' to an AR, for example, my Login (User) class has a property "Roles" which is populated by a JOIN in its standard loading query.
To create a Report object which I extend for more complex situations. The Report subclasses have a property that is the multi-table query, and other properties which represent parameters for the WHERE clause. The class produces an array of arrays.
What I ended up doing to solve this issue was to create a Project and a User object inside the Member. The objects were created when the member was constructed. Works okay.
Have just started to get into CakePHP since a couple of weeks back. I have some experience with MVC-frameworks, but this problem holds me back a bit.
I am currently working on a model foo, containing a primary id and some attributes. Since a complete history of the changes of foo is necessary, the content of foo is saved in the table foo_content. The two tables are connected through foo_content.foo_id = foo.id, in Cake with a foo hasMany foo_content-relationship.
To track the versions of foo, foo_content also contains the column version, and foo itself the field currentVersion. The version is an number incremented by one everytime the user updates foo. This is an older native PHP-app btw, to be rewritten on top of Cake. 9 times out of 10 in the app, the most recent version (foo.currentVersion) is the db-entry that need to be represented in the frontend.
My question is simply: is there someway of representing this directly in the model? Or does this kind of logic simply need to be defined in the controller?
Most grateful for your help!
For always getting the latest version automatically, you can easily make another association:
public $hasOne = array(
'CurrentContent' => array(
…
'condition' => array('FooContent.version' => 'Foo.currentVersion')
)
)
That, or you add a 'order' => array('FooContent.version' => 'desc') clause to your hasMany relationship and always work with $foo['FooContent'][0].
For automatically creating archived versions, you can work with beforeSave and/or afterSave callbacks.
In an ActiveRecord (CakePHP flavored) setup I have a HasAndBelongsToMany association setup on Videos and Bins: A Bin can store n references to Videos, and Videos can belong to n Bins.
I need to be able to manually set and store the display order of the Videos within a particular Bin (so the client can have his Videos in a particular order within the Bin.) My initial thought is to create a field in Bin that stores an array of Video IDs in the order they need to appear. Is this the most efficient way to handle this?
If so, when I then get the HABTM query result, what is the cleanest/most efficient way to re-order the returned query to match the sorted array of ID's?
The Videos associated with a Bin are returned as an array:
[Video] => Array
(
[0] => Array
(
[id] => 49b2de95-0338-48b9-bc88-01ba35664358
...
)
[1] => Array
(
[id] => 49b1ccad-b770-410e-be46-03a035664358
...
)
Or is there a better way to achieve what I'm trying to do without using HABTM associations?
Thanks in advance -
What to do when HABTM becomes complicated?
By default when saving a HasAndBelongsToMany relationship, Cake will delete all rows on the join table before saving new ones. For example if you have a Club that has 10 Children associated. You then update the Club with 2 children. The Club will only have 2 Children, not 12.
Also note that if you want to add more fields to the join (when it was created or meta information) this is possible with HABTM join tables, but it is important to understand that you have an easy option.
HasAndBelongsToMany between two models is in reality shorthand for three models associated through both a hasMany and a belongsTo association.
Consider this example:
Child hasAndBelongsToMany Club
Another way to look at this is adding a Membership model
Child hasMany Membership
Membership belongsTo Child,
ClubClub hasMany Membership.
These two examples are almost the exact same. They use the same amount and named fields in the database and the same amount of models. The important differences are that the "join" model is named differently and it's behavior is more predictable.
In your example, you need a way to add and remove without editing other users Video links, therefore standard habtm will not suit you very well. Create a model for this "join" similar to the Membership model described above. Further, if you added a weight field, you could use the ordered behavior (that I wrote) to order each set of videos per bin. Then you would use the following fields
id, bin_id, video_id, weight
And set up bin_id as the 'foreign_key' in the behavior configuartion. Good luck
Well I tried to solve just this problem and think I found the simplest solution possible:
When saving a new order of the related models you delete the existing relations and add the new ones in the desired order. This means the ids created run in your sort order and you can simply sort by id to display the order.
public $hasAndBelongsToMany = array(
'Item' => array(
'order' => 'ItemFoldersItem.id ASC, Item.name DESC',
)
);
Pretty simple isn't it?
Don't know about "most efficient" way to implement what you want, that depends on your whole application and what you want to accomplish. I'd just keep in mind that the most simple solution is often the best.
However, I can give you a tip for sorting your Video array! It is fairly easy:
$yourData = Set::sort($yourData, '/Video/id', 'asc');
Take a look at the Set class for more candy. :)