[Solar-talk] The new model class discussion, some questions
Paul M Jones
pmjones at solarphp.com
Sun Feb 18 20:06:46 PST 2007
On Feb 18, 2007, at 6:22 PM, Andreas Ravnestad wrote:
>>>> The idea is to start developing something along the lines of the
>>>> Ruby- on-Rails "ActiveRecord" class.
>>>
>>> One of these days, I tell ya... I'm going to collect all of my
>>> lamentations on RoR's "ActiveRecord" (as opposed to the "Active
>>> Record" which is useful) and it'll be enough to change the tide. I
>>> swear it :-)
>> Man, I *so* know what you're saying. I hate that they adopted that
>> name.
>
> This first question is slightly off-topic, but what's wrong with
> the Ruby ActiveRecord package?
There's nothing wrong with it at all; my objection is only to the
name. An "ActiveRecord" design pattern, formally defined, is much
more limited in scope than Rails' "ActiveRecord" package. You can
see an intro to the pattern here ...
<http://www.martinfowler.com/eaaCatalog/activeRecord.html>
... or you can purchase "Patterns of Enterprise Application
Architecture" by Martin Fowler.
Technically, Rails' "ActiveRecord" package combines a number of
design patterns into one system:
* TableModule
* ActiveRecord
* AssociationTableMapping
* DomainModel (at least in a limited sense)
* ... and others
So the *name* ActiveRecord in Rails is a bit misleading, and gives
rise to many misunderstandings of the ActiveRecord pattern as
described by Fowler. It leads to misconceptions as to what an
ActiveRecord is, and what it should behave like.
> What is the distinction between sequences and auto_increment, and what
> are the implications of this change? From my working experience with
> databases, I know that there is auto_increment (mysql), there is
> sequence (postgresql), generator (firebird), identity (mssql), and
> there
> is black magic (sqlite, which has implicit auto increment for
> primkeys).
> And they are basically the same concept with different names on them.
They are very similar. The essential difference is that you have to
ask for the next sequence number and then insert it with a new row,
but with auto-increment you insert the new row then ask what the last-
insert-id was. Systems using sequences can emulate auto-increment by
having default value settings that ask for a new sequence number, and
systems using auto-increment can emulate sequences using separate
tables for the sequence. Solar_Sql_Table currently uses sequences
for insert IDs, not auto-increment. The new Solar_Sql_Model will
support both.
SQLite auto-increment is not so much black magic as convenience; it
actually uses a sequence table to generate the auto-increment ID when
you define a column as INT PRIMARY KEY AUTOINCREMENT. See more here:
<http://www.sqlite.org/autoinc.html>
PostgreSQL supports auto-increment via a special datatype, SERIAL,
which does something similar to SQLite; that is, it sets the default
value on that column to "nextval(sequence-name)" so that a new
sequence number is generated each time. See more here:
<http://www.postgresql.org/docs/7.4/interactive/
datatype.html#DATATYPE-SERIAL>
My understanding is that MS-SQL's "IDENTITY" type is similar to
PostgreSQL's SERIAL type in its behavior.
As you can see, auto-increment is a MySQL-ism that turned out to be a
really useful idea, but that sequences are the more traditional and
common implementation. Thus the origin of my old habits. :-)
>> As far as Record/RecordSet, a Model (which models relationships in
>> addition to data) might well have its own Record to deal those
>> relationships. With that in mind, it seems to make sense to keep the
>> Record with its Model, and not the other way around -- the Record is
>> defined by the Model it comes from. Also, it allows for automated
>> fallbacks up the class hierarchy; if you extend a Model, you can
>> still get to its parent Record class as the default, until you need a
>> new extended Record as well.
>
> This is where I go from confusion to worse. What is the distinction
> between a Model and a Record?
The Model is a representation of the base table and its related
tables, and encapsulates the domain logic applied when working with
the table. The Record represents a single record from that Model
(including related data), and a RecordSet represents a collection of
Records corresponding to that model.
Because of the way Ruby works, a Rails AR represents *both* the table
and relationships (when its static methods are used) *and* an
individual record (when a static finder method returns an object).
In a rough PHP translation from what happens in Ruby, it would look
like this:
class Users extends ActiveRecord {}
$user = Users::find(1);
Note that find() is a static method of the Users model, and isn't
available to the $user record instance. The $user record cannot find
more records, but it is tied intimately to the model because they are
of the same class. (Again, you can't do this easily in PHP; it's a
rough translation of what the Ruby behavior would look like in PHP.)
The basic issue here is that we need a way to represent the table and
its relationships (the model), *and* a way to represent records
derived from that model. In Ruby it's relatively easy; in PHP,
without late static binding, it becomes more difficult -- to make the
distinction clear, you need two separate classes, one for the table
and one for the records from that table.
> I guess what I'm saying is that according
> to my understanding of Solar_Sql_Model (with friends), it is not a
> true
> implementation of the active record pattern. Yikes, yes I did say that
> out loud :o
You are exactly right; Solar_Sql_Model is not a formal ActiveRecord;
Solar_Sql_Model_Record is more like a formal ActiveRecord.
Solar_Sql_Model is more like a TableModule with
AssociationDataMapping, MetdataMapping, and other things.
Similarly, Rails' ActiveRecord isn't strictly an ActiveRecord either;
this is why I lament their choice of naming, because people think it
represents the ActiveRecord pattern proper, when it does not.
> Here is a code example from your original post:
>
> // object to work with the Nodes model
> $nodes = Solar::factory('Solar_Model_Nodes');
>
> // get a single record from the model
> $node = $nodes->fetchOne();
>
> Considering this code, I can not see why there must be a distinction
> between "$nodes" and "$node"? To me this is actually kind of
> upside-down. "$nodes" should be a list of Node's. And $node is a Node.
> To be honest, this code seems strangely counter-intuitive.
I can see why you would say that. Now that I've explained that the
model represents the table and its relationships, and thus all
possible records, does it make more sense? Or would the example make
more sense if $nodes was called, e.g., $model_nodes? I'd like to
make the examples more clear if possible.
> Again I am pointing to DB_DataObject as one example of a true and
> working active record implementation. Another elegant example is
> phpdoctrine (which has gained some peculiar popularity in
> #solarphp at freenode lately :) ).
Technically, DB_DataObject is not an ActiveRecord either. It too is
a combination of TableModule, ActiveRecord, DataMapper, etc.
The thing that bothers be about DBDO, the CakePHP model class, and
some other PHP model classes is that they try to represent *both* the
table *and* the record in one class. It doesn't make sense to me;
you end up with finders that return instances of themselves, which in
turn have finders that can also return instances of themselves. For
example ...
$dbdo = new DB_DataObject();
$record1 = $dbdo->find(1);
$record2 = $record1->find(2);
So is DBDO a record, or a recordset, or a table? It's all of them at
the same time, which bothers me.
> Continuing with doctrine, this is quoted from:
> http://lists.solarphp.com/pipermail/solar-talk/2007-February/
> 002368.html
>> ... looks very similar to the intended use of Solar_Sql_Model.
>>
>> Doctrine usage:
>>
>> $table = $conn->getTable("User");
>>
>> // find by primary key
>> $user = $table->find(2);
>> if($user !== false)
>> print $user->name;
>>
>> // get all users
>> foreach($table->findAll() as $user) {
>> print $user->name;
>> }
>>
>> Solar_Sql_Model usage:
>>
>> $users = Solar::factory('Solar_Model_Users');
>>
>> // find by primary key
>> $user = $users->fetch(2);
>> if ($user->id) {
>> print $user->name;
>> }
>>
>> // get all users
>> foreach ($users->fetchAll() as $user) {
>> print $user->name;
>> }
>
>
> It looks similar, but those two pieces of code are doing two very
> different operations (yikes again).
I don't think so at all: the Doctrine example uses a table instance
to return a record instance, and the Solar example uses a table
instance to return a record instance. What do you see as
dramatically different here?
> The doctrine example returns instances of User.
It returns a record from the model, the same way the Solar one
returns a record from the model.
> A User is a model whose instance maps directly to one record, and
> is extended from Doctrine_Record. The solar example returns
> instances of who-knows-what.
I see *Users* as the model, and it returns an instance of user
records. In the same way, the Solar example returns a
Solar_Sql_Model_Record tied back to the original Model class; that
is, instances of an individual record from the model. It seems like
the same thing to me. Am I missing something here?
> It might be Tipos_Sql_Model_Record_Blog.
> Or Solar_Sql_Model_Record. Who could possibly tell without peeking at
> the class stack?
You don't need to look at the class stack at all; you can check (1)
the class type, or (2) look at the $model_class property to see what
model it's tied back to.
> But the *model* is still Solar_Model_Users ($users and
> $user are different types) - and this is where my active record alarm
> bell (tm) goes ringing.
That's correct, $users and $user are different types -- one
represents the table and its relationships (the model of the data and
how it relates to other tables of data) and one represents a
particular record from that model. The $user record is internally
tied back to the $users model; you can see what model $user is tied
back to by reading $user->model_class, or get_class($user->model).
I get from all this that you see a Model as an individual record ...
but then what should one call the table and its relationships to
other tables? There needs to be something that represents the table
and relationships in abstract, and something else that represents a
concrete record that uses that table and relationships.
Again, I see this as a problem born from PHP's lack of late-static
binding, and thus its inability to conveniently use a single class to
represent both the table (via static methods and properties) and its
records (via instance methods and properties). As such, we need two
objects: one for the table, and one for its records.
Now, we can get around the late static binding problem by explicitly
setting every necessary static property by hand, but then you have a
lot more work every time you set up a new Model class. It becomes
very difficult to extend from a base Model class and have that
extension figure out its own name, what table it should relate to, etc.
I hope this helps to clarify the approach I'm taking, and why; please
let me know if it does not, or if there's something I'm missing
here. It's entirely possible that I'm over-thinking this or ignoring
something obvious.
--
Paul M. Jones <http://paul-m-jones.com>
Solar: Simple Object Library and Application Repository
for PHP5. <http://solarphp.com>
Savant: The simple, elegant, and powerful solution for
templates in PHP. <http://phpsavant.com>
More information about the solar-talk
mailing list