[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