matthijs wrote:This only works when you already have the author_id. My problem is that I start with an author name. So the model you're working with, Post, gets data (posttitle, postbody, author, categories, tags), and has to deal with all that data. But preferably without replicating all the domain logic of the Author, Category and Tag models.
I wasn't really intending it to work, I was just trying to understand Pytrin's method. It sounds to me like, for some reason, you have unpersisted data for both a Post and an Author. In that case, I would do something like this:
Code: Select all
$author = new User();
$author->setName ($_POST['author_name']);
$author->save();
$post = new Post();
$post->setTitle ($_POST['title']);
$post->setBody ($_POST['body']);
$post->setAuthor ($author);
$post->save();
Keep in mind I don't use ActiveRecord, so i may be doing something wrong.
matthijs wrote:Say a new post is posted, complete with a whole bunch of tags. Now for each of those tags you have to check if they already exist, if so then get the tag_id's belonging to those, if not insert them in the tag db table and get the tag_id's, then insert those tag_id's in the reference table posts_tags, etc. That's a lot of logic, and it's difficult to find ways to deal with that in a clean way (not repeat everything all over the place).
That sounds to me like a perfect candidate for a Gateway or a Mapper, and not ActiveRecord. In my opinion you're seeing where AR falls apart... dealing with complex relationships. Others may argue on that point, and maybe I just don't know of the proper way to handle them with AR, but I find separating the persistence logic from the domain logic much cleaner.
For your tag example, here are is an idea:
Client Code:
Code: Select all
$post = new Post();
foreach ($_POST['tags'] as $tag):
$post->addTag (new Tag ($tag));
endforeach;
$postsMapper->save (Post $post);
PostsMapper:
Code: Select all
function save (Post $post) {
$this->mapperRegistry->get ('PostTag')->saveTagsForPost ($post->getTags(), $post);
}
}
PostTagsMapper
Code: Select all
function saveTagsForPost (Collection $tags, Post $post) {
$postTags = new Collection();
foreach ($tags as $tag):
$postTags->add (new PostTag ($post, $this->mapperRegistry->get ('Tag')->save ($tag)));
endforeach;
$this->save ($postTags);
}
}
Each mapper has its own responsibility, and when it can't handle something, it passes it off to another mapper. The PostMapper gets a Post, and sends its Tags to PostTagMapper, which maps the relationship between Posts and Tags. (PostTag is just a value object... not a full fledged domain object). PostTagMapper doesn't know if the post exists, and doesn't care... it needs to exist. So it sends the tag off to TagMapper, which takes care of deciding whether or not a given Tag exists. If it does, great. If not, create it. Back in the PostTagMapper, it receives a fully-persisted Tag and adds it to a collection of PostTags. When it saves, it just deletes all the other PostTags in the database (since they're value objects) and inserts each PostTag anew.
Maybe that's not what you want... but I would have killed for datamapper examples like this a year ago. If it inspires somebody, then I'm happy.