Active Record

There are couple ideas about re-structuring AR. Logically there are 3 objects involved in AR:

  1. Model — should hold data only and delegate methods such as save() or find() to the storage. Shouldn’t be aware of storage used. This way we’ll be able to create general purpose components that are able to work with models and will not depend on the data storage.

  2. Storage — should provide methods to save model, find model etc. Storage is DB-specific:




Storage

  SQLStorage

    MySQLStorage

    MSSQLStorage

  KeyValueStorage

    RedisStorage

  ObjectStorage

    MongoDBStorage



Since some storages are providing specific ways to work with data, we may expose these in specific classes.

  1. Results collection — a traversable collection that’s returned from query methods.

Post::model()->into('PostCollection')->findAll();

Possible usage is to create good looking API for related models management:




$tag = new Tag();

$tag->name = 'Yii';


$post = Post::model()->findByPk(1);

// add will save $tag into $forSaving array so AR will save just this one instead of deleting-saving every tag out there

$post->tags->add($tag);

$post->save();



I like the idea 1 and 2. Can you show some concrete use examples (how the API will look like, how the classes are written) when using SQL storage and KeyValue storage?

I like the storage idea. It could be also a good starting point for a proper JS framework for Yii (sort of collection objects in ExtJS)

Edit: What I tried to say is that, storage classes should also has methods to push objects to the client. I know that Yii is mostly concentrated on the server, but even though we have a strong framework that has very powerful widgets that render on the client nicely and automate lots of things, we lack of methods that would transform our objects for JS programmers to do their magic.

I like this design, as it will add more flexibility

I recently was discussing the same with Qiang in this issue, the abstraction of CDbConnection to allow different storage types

+1

I’ll just pitch in one (not so) brief thought here, and I don’t mean to be all down on your idea or anything, but just consider this for a moment:

Active Record is a pattern designed specifically to address the problem of object-relational impedance mismatch.

Query operations on relational databases are abstracted away to some extend, but they are never fully or perfectly abstracted away, because no matter how you twist and turn things, objects in-memory are a graph, and tables are two-dimensional matrices.

Some object-relational mappers go to excess to ensure that relational database "smells" are fully abstracted away, such as Hibernate and Doctrine - and still, with these massive codebases, you invariably run into situations where the abstractions can only get you 99% of the way.

Yii’s lightweight AR approach is of a different school - it’s not at all hiding the fact that the underlying storage is a relational database, we are openly doing bits of query syntax, joins, primary and multiple keys, table/column schema reflection, etc., etc.

Personally, when working with a relational database, I favor Yii’s approach - attempts to abstract away the fact that you’re working with a relational database, lead to overly complicated abstractions that take years to master, and many more years to implement and perfect. Hibernate was started in 2001, and they’re still struggling. In my opinion, they’re swimming against the stream.

My point is that, unless you’re willing to go to great lengths to fully abstract away all aspects of relational databases, your abstraction is always going to leave a scent of relational storage.

But more importantly, consider this: even if you did achieve the perfect abstraction, and there was no trace left of relational storage in user code - how much sense would it make for you to switch the storage provider to something like a graph database?

Most graph databases (and certainly document databases and key/value stores) still have a limited (if any) ability to perform projection-queries across multiple entity-types. If the storage-layer for you favorite key/value-store results in AR throwing exceptions at you when you issue a with() statement, the most you’ve achieved is a similarity between storage-layers - not compatibility.

For another, if you had a graph database, which was a perfect fit for your object-graphs to begin with, why would you want an AR-style abstraction on top of it? This would most likely make it less convenient, not more, as object-storage is already a near-perfect fit for object-graphs.

I think it’s better to accept the fact that new storage paradigms are not compatible with our old way of thinking, and that this is in fact a good thing, because the old way of thinking was not very sound to begin with. Just my personal opinion.

Food for thought, that’s all :slight_smile:

@mindplay: I totally agree with you. We have to be realistic. Our goal is NOT trying to make AR support different storage types under the same set of interfaces. Our abstraction should be very limited, perhaps only to how the fetched data to be retrieved so that they can be used to provide data to the same UI components.

@mindplay I agree, It is not possible to use the same interface or all. I like Yii’s way, and I also agree with @samdark’s item 2.

Would be really nice to ‘inject’ related objects -this is one of the things I loved from redbeanPHP (www.redbeanPHP.com), I would do it differently though as that method ‘add’ IMHO doesn’t really make sense the way it is used on the example.




// CITY is related to COUNTRY on a self::BELONGS_TO relation and viceversa with a self::HAS_MANY

$country = new Country();

$country->name = 'Spain';


$city->name = 'Inca';


// This method actually saves both objects and updates their relationship 

// from child to father

$city->connect($country);


// with a father adding a child

$city2 = new City();

$city2->name = 'Selva';


$country->connect($city);


// example within connect

CHECK RELATIONSHIP TYPES

DO FATHER FIRST

IS COUNTRY A NEW RECORD?

----Y: SAVE IT

UPDATE CITY->COUNTRY_ID TO COUNTRY->ID

SAVE CITY



Of course, the example is a silly one, validation and making sure a object to be ‘connected’ is one of the relations should be in place

In my opinion, there’s no need to bloat models with delegate methods when models are fully independent of storage.

Something like this:


Yii::app()->mongoDbStorage->save($model);

would be a bit cleaner and more explicit than


$model->save(); // Where???

Storage could trigger validation and lifecycle events on model and could pass itself to event handlers as a property of CModelEvent.

Also, different storages may have different finder methods (findBySql(), for example). Models can’t provide delegate methods for every possible storage. EDIT: Actually, they can, via __call() or __callStatic() but it would make their API unreliable.

This is the common argument against the Active Record pattern - that you should not mix the storage concerns into your domain model.

I’m afraid you will find that a decoupled data-mapper such as you’re proposing, requires a radically different design from the AR approach taken by Yii - so we’re probably not talking about a simple transition, more like a complete redesign, from the ground up.

Decoupling storage is definitely technically “cleaner”, but in my experience, honestly it does not in itself guarantee that you end up with a model that is easier to understand or work with, performs better, or scales better in terms of complexity. For example, I’m working with NHibernate, which uses this approach - and it is horribly complicated and has a painful learning curve, which, in the end, does not seem to translate to any concrete benefits… in the end, it’s relational storage, and no matter how hard you try to hit it, with all your biggest guns and fancy architectural abstractions, two-dimensional matrices remain a poor fit for storing object graphs.

I think Yii’s AR works pretty well, and that’s as much as you can ask for - a complete redesign, in my opinion, would not be worthwhile.

By the way, there are plenty of other third-party data-mappers for PHP that do what you want - there’s nothing stopping you from integrating a third-party storage engine into Yii, and using a decoupled approach, if that’s what you fancy.

Personally, I’m hoping to see the 2.0 branch of Yii making other architectural changes, and perhaps making AR an optional package - so that you can deploy Yii without AR and choose your own storage engine.

That wasn’t me :)

Samdark’s proposal is already a large step away from Active Record pattern. Mine just goes a bit further in the same direction.

mindplay

Decoupling AR into a separate package looks like an interesting idea.

But now the application itself has dependencies. Is the application itself then a package now, too? (I don’t see why not)

Well, I don’t see any problems making an application/module another package either.

I don’t think we should make AR a separate package. What benefits will we get by doing so?

In fact, I’m against dividing the framework itself into packages. Following ZF or SF2 is NOT our goal. Framework size shouldn’t be a concern for most people, and it has nothing to do with performance or flexibility.

qiang

It’s not about size only. Benefits I can see:

  • Better and faster autocomplete in IDEs. Less files to parse, less variants to suggest. Overall it leads to a better experience.

  • Ability to bundle less with complete products such as forums or CMSs.

  • Core packages are the best example of what is a package and how to build / use it.

What are cons if we’ll still distribute AR as a whole but developer will be able to uninstall it?

Dividing framework core into packages won’t give you better IDE experience because ultimately you still need to have some code to support your application, whether you are using AR or some 3rd party alternative. They will still affect your IDE experience.

What’s the benefit of bundling less for forums/CMSs? There are always some unused code from framework, even if they are in the core.

I think Gii is a better example because it is totally optional.

The biggest con as I can tell is that it introduces confusion to end users. If you look at SF2 download, you will see they provide two types of download: standard and standard without vendors. This already minimizes the choices for end users, but I still feel confused at what I should choose if I’m a new user. In fact, most likely I would choose the former in order not to lose something important.

In case of monolithic core you’ll have both AR and, for example, Doctrine ORM. You’ll not use AR if you’re using Doctrine ORM. Still, IDE will have to index it and provide autocomplete suggestions.

Product developers prefer less sized downloads. I understand that it’s pretty marketing kind of a feature but still there it is. Requested by clients very very often.

I’m not talking about removing AR from framework download. You’re totally right that it will be not a vise move to do so. Most applications are using it. The idea is the ability to uninstall it from the full package downloaded.

IMO, the IDE efficiency is the least important factor and shouldn’t be a limiting factor for the framework itself. Take a look at other libraries (not necessarily in PHP) which are much much bigger than Yii. I rarely heard people complaining the size of the library is affecting IDE performance.

I know you are not requesting to remove AR or other features. My point is that AR is a crucial part of Yii and shouldn’t be made optional, even though you can choose not to use it. There are better candidates in Yii that can be turned into optional packages, such as gii and jui widgets. In fact, AR by itself only has a few class files (unless you also want to make DAO optional). If we make it optional, the next very natural question would be: should we turn something else into optional, such as different caching components, DB session storage, and so on. Why are we creating these artificial difficulties to ourselves and to end users? Why not concentrating on enabling more smooth development work flow so that users can be more productive?

I think the philosophy for Yii is a bit different from ZF and SF2. The latter seem to aim at providing a foundation for both applications and another layer of frameworks, while for Yii we mainly want to help users to build applications more rapidly and in a professional and secure way.

Makes sense. You’re totally correct about AR itself. A question “what should be a package” is probably easier to solve during alpha stage…