Thanks Chris,
Chris Hartjes wrote:
>> - join models
> CakePHP offers that via the same sort of hasMany, belongsTo
> and hasManyAndBelongsTo functionality.
That seems to have been misunderstood. I was thinking of using
real join Models in the Domain Model for many-to-many
relationships instead of "mere" join tables at the database level
that have no representation whatsoever in the object-oriented
layer. So, instead of this:
CREATE TABLE `groups_users`
( `group_id` INTEGER UNSIGNED NOT NULL DEFAULT 0
, `user_id` INTEGER UNSIGNED NOT NULL DEFAULT 0
, PRIMARY KEY `groups_users_id` (`groups_id`, `users_id`)
, FOREIGN KEY (`group_id`) REFERENCES `groups`(`id`)
, FOREIGN KEY (`user_id`) REFERENCES `users`(`id`)
);
with
User has and belongs to many Groups
Group has and belongs to many Users
You would instead have this:
CREATE TABLE `memberships`
( `id` INTEGER UNSIGNED PRIMARY KEY auto_increment
, `group_id` INTEGER UNSIGNED NOT NULL DEFAULT 0
, `user_id` INTEGER UNSIGNED NOT NULL DEFAULT 0
, FOREIGN KEY (`group_id`) REFERENCES `groups`(`id`)
, FOREIGN KEY (`user_id`) REFERENCES `users`(`id`)
);
with
Membership belongs to User
Membership belongs to Group
User has many Memberhips
User has many Groups through Memberships
Group has many Memberships
Group has many Users through Memberships
Basically, our poor little anymous join table has been promoted
to a real Membership Model in our application.
Using a real full-fledged join model gives you much more power
and flexibility. Consider, for example, trying to design a
RESTful CRUDdy API for this application. In the first example
above, it is not immediately obvious how to join a User to a
Group. Of course, it's possible to add a joinGroup action to
your User model or an acceptUser action to your Group model. But
that wouldn't be RESTful and CRUDdy. In the second example, it's
blindingly obvious: joining a Group is the same as creating a new
Membership.
Modeling relationships between models as models in their own
right is a very powerful concept. Unfortunately, many courses
still teach this naive noun/verb modeling concept, where you
basically write up your use cases in plain English and then
underline all nouns which become your objects and all verbs which
become your actions. In this way you usually overlook concepts
such as Relationship, Authorship, Membership etc., which might
very well deserve their own objects, instead of being just
associations.
>> - object-relational mapping of inheritance
> I think CakePHP allows this through object chaining, which to
> me is a very powerful yet underutilized concept.
Can you elaborate on that? I just googled for object chaining
and only got a few results concerning fluent interfaces which is
probably not what you meant.
AFAICS, there are basically 6 possibilities how to do
object-relational mapping of inheritance:
1. cop-out #1: don't do inheritance
2. cop-out #2: don't do object-relational mapping, use a "real"
object-oriented database/persistence layer instead
3. map the complete inheritance hierarchy to a single table
(a.k.a. the "One Inheritance Tree - One Table", "Single
Table Mapping", "Flat Mapping", "Union Mapping", "Typed
Mapping", "Filtered Mapping" or "Single Table Inheritance"
design pattern)
4. map each concrete class to a single table (a.k.a. the "One
Inheritance Path - One Table", "Horizontal Mapping" or
"Concrete Table Inheritance" design pattern)
5. map each class to a single table (a.k.a the "One Class -
One Table", "Vertical Mapping" or "Class Table Inheritance"
design pattern)
6. use a generic "meta-mapping" schema
All of these have their advantages and disadvantages.
#3 is probably the simplest of those (except #1, of course), it
works like this: in your database you have one single table for
the entire inheritance hierarchy which includes the union of all
attributes of the classes in the hierarchy plus an additional
type column. So, given a hierarchy like this:
class Article {
var $title;
var $body;
}
class BlogPost extends Article {
var $category;
}
class Comment extends Article {
var $blogpost;
}
your schema would look like this:
CREATE TABLE `articles`
( `id` INTEGER UNSIGNED PRIMARY KEY
auto_increment
, `title` VARCHAR(255) NOT NULL
, `body` TEXT NOT NULL
, `blog_post_category` VARCHAR(255) NULL
, `comment_blog_post_id` INTEGER UNSIGNED NULL
, `type` VARCHAR(255) NOT NULL
, FOREIGN KEY (`comment_blog_post_id`) REFERENCES
`articles`(`id`)
);
The way this works is that the object-relational mapping layer
looks at the `type`column to actually decide what type of model
to return. Note the "NULL" columns: since a BlogPost doesn't
have a parent $blogpost and since a Comment doesn't have a
$category and an Article has neither, those columns *must* allow
NULL values.
However, I'm not sure this belongs in a CakePHP application or a
plugin. It looks rather like it belongs into Active Record
proper.
>> - polymorphic associations
> PHP doesn't support the polymorphic stuff at this time as far
> as I'm aware.
Okay. That's rather nifty stuff though, it would be kind of cool
to have. For those that are not familiar with that concept, let
me explain a bit. A polymorphic association basically is an
association for which the type of the associated model is not
known. A typical example would be tagging, rating, voteing or
commenting: you can tag/rate/comment/vote a blog post, a photo,
a video, CakePHP plugin, ... whatever. If you just have Posts,
you can easily comment on them like this:
CREATE TABLE `comments`
( `id` INTEGER UNSIGNED PRIMARY KEY
auto_increment
, `title` VARCHAR(255) NOT NULL
, `body` TEXT NOT NULL
, `post_id` INTEGER UNSIGNED NOT NULL
, FOREIGN KEY (`post_id`) REFERENCES `posts`(`id`)
);
If we now add photos into the mix, it gets more complicated. One
solution would be to create a separate Model PhotoComment,
possibly inheriting from Comment. But actually, a PhotoComment
is the same as a Comment, so why have two models? If we want to
add more and more commentable Models we would also have to add
more and more Comment Models, all doing essentially the same.
Another solution would be to extend the Comment Model so that it
can associate either to a Post or to a Photo, this is the
resulting schema:
CREATE TABLE `comments`
( `id` INTEGER UNSIGNED PRIMARY KEY
auto_increment
, `title` VARCHAR(255) NOT NULL
, `body` TEXT NOT NULL
, `post_id` INTEGER UNSIGNED NULL
, `photo_id` INTEGER UNSIGNED NULL
, FOREIGN KEY (`post_id`) REFERENCES `posts`(`id`)
, FOREIGN KEY (`photo_id`) REFERENCES `photos`(`id`)
);
If we want to add more and more commentable Models, we would have
to add more and more associations to our Comment Model and
foreign key columns to our `comments` table.
The way Rails' Active Record layer solves this, is via
polymorphic (some would say "promiscous") associations. It works
by creating a kind of virtual interface out of thin air and using
that to connect (in my example) comments to commentable objects.
In your models you would say
Comment belongs to Commentable, polymorphic
Post has many Comments as Commentable
Photo has many Comments as Commentable
Please note that there doesn't actually exist a Model
"Commentable". In the database, this is implemented using a
similar technique as in Single Table Inheritance:
CREATE TABLE `comments`
( `id` INTEGER UNSIGNED PRIMARY KEY
auto_increment
, `title` VARCHAR(255) NOT NULL
, `body` TEXT NOT NULL
, `commentable_id` INTEGER UNSIGNED NOT NULL
, `commentable_type` VARCHAR(255) NOT NULL
, UNIQUE KEY `commentable` (`commentable_id`,
`commentable_type`)
);
Basically, `commentable_id` and `commentable_type` together form
a foreign key to the table that corresponds to the Model name
given in `commentable_type`, but that cannot be expressed in SQL.
>> - Acts (acts allow you to decorate a model's behaviour with
>> additional orthogonal aspects and encapsulate that behaviour
>> in a single, central place)
> Nope...but I believe that is due to the fact that PHP does
> not support closures, a key feature that makes all sorts of
> awesome feature available in Rails.
I'm not sure that an implementation of Acts would actually
require the sophisticated metaprogramming features of Ruby. It
sure would help a lot, but I don't think it's necessary.
>> - join models
> Not sure.
Yeah, I'm also not sure why I asked that twice (-;
>> - the migration DSL (in fact, all of Rails' DSLs, including but
>> not limitied to the Model DSL)
> There's no migrations...but that would be an interesting project.
> Maybe I should look into this as it would be something very
> interesting.
There is a migrations plugin somewhere on CakeForge, but its DSL
is based on YAML, not PHP. The interesting thing about Rails'
migration DSL is that it is based on Ruby, which means that you
have the full power of Ruby and Active Record at your fingertips
when writing migrations. Thus, you can not only do schema
migrations but also very complex data migrations. For example,
if you have an ASIN column in your books table and want to change
that to an ISBN column, you can actually use Ruby's Amazon.com
webservice bindings to call out to Amazon and fetch the ISBNs
from there, all in your migrations! This is, of course, a pretty
contrived and extreme example. However, this is only possible
because the migrations are written in Ruby instead of, say,
SQL/DDL/DML, XML or YAML.
Personally, I think that Rails' DSLs are both one of the most
important and most undervalued assets of Rails.
> Hope that helps.
Indeed it does! Thank you very much!
jwm
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups "Cake
PHP" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at http://groups.google.com/group/cake-php
-~----------~----~----~----~------~----~------~--~---