I second that. I always found it really counter-intuitive to have the record and the finder in the same class.Originally Posted by 33degrees
| SitePoint Sponsor |
I second that. I always found it really counter-intuitive to have the record and the finder in the same class.Originally Posted by 33degrees
Not to be rude - but I quote myself:Originally Posted by 33degrees
Originally Posted by thr


That has been my traditional take on it as well; more recently I discovered that it is actually rather convenient to have both in the same class as it means fewer application objects to deal with and gives a more complete encapsulation for an area of responsibility. So far, I'm quite happy with this refactorization in my codebase.Originally Posted by kyberfabrikken


Maybe using __autoload you can make a hack to work-around this problem. Here is some code in theory that may help (completetly untested in the sense that it is completely unrun, no PHP interpetator immediately handy at the moment).
Now, I can envision possible problems here already (i.e. if there is a chain of classes inheriting from ActiveRecord), and I don't now if even works (never tried it), but it is just an idea.PHP Code:function __autoload($class)
{
// Simplified inclusion of class for example.
include($class .'.php');
// Check if class inherits from ActiveRecord
$class = new ReflectionClass($class);
if ($class->isSubclassOf(new ReflectionClass('ActiveRecord'))) {
// Add a setClassName to ActiveRecord implementation
$class::setClassName($class);
}
}
For people who a) already use __autoload and b) want to use ActiveRecord as described, adding this in may allow them to do so, then when (and if) the syntax is supported, all they will have to do is change __autoload.
The question is, though, does it work? I'll try it out myself when I get a chance.




For what it's worth, this is the closest approximation I've found:
PHP Code:class ActiveRecord {
protected static function find($class,$id) {
// get stuff from the database
return new $class(/* stuff */);
}
}
class Post extends ActiveRecord {
public static function find($id) {
return parent::find('Post',$id);
}
}
$post = Post::find(1);
print_r($post);
/*
Output:
Post Object
(
)
*/
Dagfinn Reiersøl
PHP in Action / Blog / Twitter
"Making the impossible possible, the possible easy,
and the easy elegant" -- Moshe Feldenkrais

My understanding of ActiveRecord is that a DataMapper becomes an ActiveRecord when you add business logic to it. Conversely, a domain object becomes an ActiveRecord when you add database logic to it.Originally Posted by thr
Where you put the finder logic doesn't really matter; static find methods or a separate finder are both fine. Of course, I could be wrong![]()




It's actually a Row Data Gateway that becomes an ActiveRecord when you add business logic to it.Originally Posted by akrabat
Not sure why you feel a need to defend yourself, I was just pointing out (for those unfamiliar with ruby) the reason why the finder-in-the-same-class approach works better in ruby than in php.Originally Posted by thr
Fewer objects is definitely a concern of mine; domain models can get pretty complicated, and doubling the number of objects by implementing finder classes for each one seems like an inefficient approach to me.Originally Posted by jayboots
It's clearly two separate responsibilities, so stuffing them into one place, makes things more complicated than needed. It's bad enough to put persistance and applicationlogic in one place.Originally Posted by 33degrees
You could have a generic finder, which can be used for the majority of classes, and just write specific finders for thoose special cases.
Or perhaps it would be a better approach to think the other way around. Have a generic record-class, and just implement a gateway (finder+attributes metadata) for each type. That's not the activerecord ofcourse, but if you want simplicity ?
Doesn't that depend entirely on how its implemented? To take the Ruby example, I wouldn't call it complicated at all because the entire ActiveRecord implementation is 99% transparent because all you have to do is extend the ActiveRecord class. This means you can still focus fully on your business logic without cluttering up your class.Originally Posted by kyberfabrikken
You're right inasmuch that the userland code extending the baseclass is simpler for the all-in-one solution. I was refering to the usage though. Aslong as the record and the gateway are used close to eachother, there's no problem, but if you have to pass the gateway around, it can easily become rather confusing. From the method's signature you won't be able to tell whether you're getting a finder or an actual record, since they share the same classtype.Originally Posted by Luke Redpath
I agree that the responisibilities are seperate, but not that it neccesarily makes things more complicated; the implementation may be more complicated, but on the other hand it's easier to use. That's the appeal of the ActiveRecord approach. Single responsibility for objects is an worthy goal, but I think it's a rule that can be broken in certain cases.Originally Posted by kyberfabrikken
That's the approach I'm currently considering, combined with some form of metadata mapping.Originally Posted by kyberfabrikken
In reality, I think the ideal approach lies somewhere in between active record and data mapper, combining the simplicity of the former with the flexibility of the latter. One possibility would be to place persitance logic in a seperate object, but keep that object inside the data class. Something like:Originally Posted by kyberfabrikken
PHP Code:
class Model {
var $mapper;
var $meta_data;
var $data;
function Model($mapper, $meta_data) {
$this->mapper = $mapper;
$this->meta_data = $meta_data;
}
function save() {
$this->mapper->save($meta_data, $data);
}
}
In Rails the finder is the class itself, and records are instances of the class, so there's no confusion between the two; and since the finder is essentially a singleton, there's no reason to pass it around.Originally Posted by kyberfabrikken


Agreed although I don't even see different responsibilities from the point of view of the application. Say we have a User AR class -- why shouldn't that single class alone be responsible for all access to "User" data? Sure, internally it can be implemented as several distinct and more generalized types but to the application I think it makes sense to unify the interface for data access. Kyb makes a good point, but I find that the general case is to use the two "close-by".Originally Posted by 33degrees
More agreement. In fact, my implementation does something very nearly like that and is similarly constructed by model-driven meta-data (both the crud and the available available finders are entirely described that way). So far, I have yet to need to actually make a custom extension of my AR class -- I just need to update my metadata for a given class type and my autoloader automatically extends the AR class for me. Furthermore, this allows me to define transformations, validations and other criteria declaratively, which I find appealing.Originally Posted by 33degrees
Originally Posted by 33degrees
I wouldn't say it's that far-fetched.Originally Posted by jayboots
Say I have a class ListController, which produce a table of records from a datasource. The natural approach for me would be something like this :
See the ambiguity if $gateway is an all-in-one type activerecord ?PHP Code:$lister =& new ListController($gateway);
echo $lister->execute();
I know that I could pass the classname rather than an actual object, and let the ListController instantiate the class itself (or pull it through a static factory/singleton), but that really makes me uncomfortable.


I think I do it a little differently -- my finders return recordsets (plain arrays), not collections of or singular ARs.
$user = new ADC_User;
$all_users = $user->findAll();
If I need a specific user:
$user->find(array('id'=>'foo'));
I also have a few reflection methods on the AR class to let me inspect the requirements of the specific AR instance based on the model it is derived from.
The upshot is that if I had a Lister component, it wouldn't be responsible for acquiring the data -- it gets passed a normal recordset. I'll definately be using some of the arguments in this thread as input when I go to reevaluate my implementation, but so far I find it satisfactory for my needs![]()
ORDER BY and LIMIT ?Originally Posted by jayboots
Anyway - The point is not as much if such a widget is the best possible implementation, but rather that the ambigouity may cause confusion.
Well in the case of Ruby's ActiveRecord, the finders are class methods, so you wouldn't need to pass around any objects to use them, you'd just call them against the appropriate class. There would never be any need to pass round a finder. Records, on the other hands, would be instantiated instances of this class.Originally Posted by kyberfabrikken
Wouldn't it be better to pass the ListController an array of records/AR objects therefore you aren't binding the ListController directly to any gateway/AR class. In Ruby:Originally Posted by kyberfabrikken
Code:posts = Post.find(:all) lister = ListController.new(posts) lister.execute
I'm still not sure I can see this ambiguity.Originally Posted by kyberfabrikken
There is no ambiguity because finders are called against the class itself, not against objects of that class.Code:@post = Post.new @lots_of_posts = Post.find(:all)
That depends entirely on what the purpose of the component is. The above example isn't all made-up. I have one such class in an application I'm working on currently. It's basically a specialized controller, for handling pagination/sorting of results from a gateway. The component will read the request for page-offset, column to sort by and direction, and will use theese as arguments when calling the findAll method of the gateway. Since I'm reusing the component for multiple gateways (~finders), I need the gateway to be dynamic - A classmethod would tie the class into working with just one gateway.Originally Posted by Luke Redpath
In short the problem boils down to this; To distinguish between the finder and the instance, classmethods are used for the former, while instance methods are used for the latter. Classmethods are static calls, and static calls give tight coupling. I like loose coupling.Originally Posted by Luke Redpath
Except that in Ruby, class methods are NOT static calls, they are dynamic, so they're inherited by subclasses, and they can be mixed in from other modules. The coupling is in fact quite loose.Originally Posted by kyberfabrikken
In any case, it's possible to have your cake and eat it, too. Ergo:
Of course, this is ignoring the "knowing which class the static call was made on" issue...PHP Code:
class ActiveRecord {
function find() {
$args = func_get_args();
$finder = new Finder();
return call_user_func_array(array($finder, 'find'), $args);
}
}


I've just finished a project at work in which I've used a dynamic ActiveRecord in this way. The AR provides insert, update and delete methods, finder methods are handled by the subclass of AR. The subclass passes some meta data to the AR method, which can generate the queries from the protected properties of the subclass.
This was a passable solution until objects started using multiple tables, and the need to create sets of objects appeared. Now it feels like a big kludge. - the AR subclass passes a resource back to the client in the case of multiple records. Time to refactor. I've started writing a generic mapper - I'd be interested to see where you go with this idea 33degrees.PHP Code:class ActiveRecord
{
private static $db; // ADODB
public function save( $table, $primary )
{
// Called from subclass context
$props = get_object_vars( $this );
$this->db->Execute( ... );
}
}
class ValueObject extends ActiveRecord
{
const TABLE = 'SOME_TABLE';
const PRIMARY = 'PRIMARY_KEY';
protected $prop;
public function findByPrimaryKey( $pk )
{
$row = $this->db->Execute( ... );
$this->bless( $row ); // Like perl's bless
}
}
$vo = new ValueObject();
$vo->findByPrimaryKey( 1 );
$vo->set( 'prop', 1 );
$vo->save( ValueObject::TABLE, ValueObject::PRIMARY );
Generic mappers is NOT the way to go imho, because it requires you to put all your special finder-methods in one big class.
Bookmarks