ActiveRecord. What? Model for database records. Grouping of data and behaviour. “Active Record...

32
ActiveRecord

Transcript of ActiveRecord. What? Model for database records. Grouping of data and behaviour. “Active Record...

ActiveRecord

What?

Model for database records.

Grouping of data and behaviour.

“Active Record Pattern” recorded by Martin Fowler, circa 1857 -->

What?

1) Object that represents a single row.

ModelInstance

id

customer_full_name

customer_gender

create_time

setPaid()

What?

2) Object that interacts with multiple rows.

Model

Find

ModelInstances

Delete

I think I'm using this already.

Wordpress' WPDB can interact with tables via insert(), update() and delete().

WPDB can retrieve records as objects with get_row().

I think I'm using this already.

PHP itself has mysql_fetch_object(), which gives me back an object the data as properties.

I think I'm using this already.

CMSClass from our VanillaCMS is a type of Active Record.

Can create a new object, give it data, and save it.

Can retrieve records and set them up as objects.

Doesn't do direct record munging, eg. update() or delete(); that's usually left to manual sql_query() calls.

Yeah? And?

CMSClass is a generic model. We don't sub-class it and use it as a base

for anything else. It's always used as a data store, but hardly

ever as a way to bundle behaviour. Behaviour is currently almost always put in the

“controller” or “module” that uses it. Behaviour for specific components (eg. FOX8

Video) is currently spread throughout the system.

Yeah? And?

The real power lies in being able to take a generic construct (eg. a base Active Record class), and augment it for a specific purpose.

Base Active Record Class Extended “Ticket” Class

Example Time

Let's have a look at how other systems use Active Record.

Detailed example: Yii Framework's CActiveRecord

The Basics

Setup: class Order extends CActiveRecord {

public function tableName() {return 'order';

}

}

The Basics

Creating: $order = new Order;

$order->payer_full_name = 'Rob Howard';

$order->payer_email = '[email protected]';

$order->save();

The Basics

Reading: $an_order = Order::model()->findByPk(27); $deep_orders = Order::model()->findAll(

'payer_email LIKE :email', array(':email'=>'%@deepend.com.au') );

$orders = Order::model()->findAll(); // everything

The Basics

Updating: $order->payer_email =

'[email protected]';

if ($order->validate()) {$order->save();

}

Deleting: $order->findByPk(56)->delete();

Validation

Function inside the Order class: public function rules() {

return array(array('payer_full_name', 'required'),array('payer_email', 'email'),array('create_time', 'safe'),

// id (and any other fields) are, by omission,

// deemed “unsafe” to set values to.// You don't change a record's primary// key value.

);}

Validation

The validation is useful to have here tied with the object.

CMSClass already has field rules tied together with its GUI configuration, but unfortunately doesn't take advantage of that to do validation.

Events and Hooks

CActiveRecord throws events, and runs hook functions as it finds, saves, deletes (etc) records.

We can pick up on these, and act as we want, eg. // Set up “soft deletion” for this model

public function beforeDelete() {$this->deleted = 1;$this->save();return false;

}

Relations

Define a relationship in the model; anything using that model has quick access to parents, children, many-to-many-related items, etc. class Order {

// …

public function relations() {return array(

'tickets' => array(self::HAS_MANY, 'Ticket', 'ticket_type_id'),

);}

Relations

Now that Order HAS_MANY Ticket models: $order = Order::model()->findByPk(123);

$tickets = $order->tickets;

$tickets is now just an array of the tickets belonging to $order.

It's lazy-loaded, so there isn't a performance hit until it's called for.

Filters

Set up named rules for retrieving parts of your data, eg. class TicketType {

// ...function scopes() {

return array('availableToPublic' => array(

'condition' => 'closing >= CURDATE()',

));

}

Filters

Set up parametised filters for when you want to accept values in a filter call: function createdBetween($start, $end)

{$this->getDbCriteria()->mergeWith(array(

'condition' => 'create_time BETWEEN :start AND :end',

'params' => array(':start'=>$start, ':end'=>$end),

));}

Filters

Set up default filters for things like soft deletion, where you don't wantModel->findAll() to retrieve records. function defaultScope() {

return array('condition' => 'deleted = 0'

);}

Filters

Why all this? $orders = Order::model()

->createdBetween('2010/06/01','Today') ->findAll();

$ticket_options = TicketTypes::model() ->availableToPublic() ->findAll();

ie. Neatly-encapsulated domain-specific logic.

Behaviours

Modular bits of code you can plug into a model which react on model events.

This one automatically handles timestamping of creation and saving events: public function behaviors() {

return array('CTimestampBehavior' => array(

'class' => 'behaviors.CTimestampBehavior',

'updateAttribute' => 'update_time',)

);}

Regular, Plain Functions

Functions that belong to the model. You put domain-specific actions inside,

rather than treating the record like a dumb storage box.

You call them on model instances. Wow.

public function addPayment($amount) {// Creates payment records.// Secret Order model business, keep out.

}

All Together Now Inside a controller somewhere:

// will not load soft-deleted items (defaultScope)$orders = Order::model ->purchasedAtSupanova(2010) ->findAll();

foreach ($orders as $order) {foreach ($order->tickets as $ticket) {

$order->addPayment($ticket->type->price);

}$order->generateTicketPDF();

}

So What?

The point of all this: Brief, neat code, with domain-specific logic

tidied away somewhere: Close to the data it needs to interact with. Reusable. … But not trying too hard to be reusable by

getting in everyone's way.

So What?

The point of all this: Although it'd be possible to create more

generic-ification layers on top of CMSClass (eg. GUI-configurable events and filtering), unless it's done cleverly it's very easy to obstruct the developer further.

You want to make it easier for the developer to set up and get access to relations, events, behaviours, rather than having the developer puzzle their way through a generic object/class interface.

I'm Interested.

PHP implementations to check out: www.phpactiverecord.org www.propelorm.org www.doctrine-project.org www.yiiframework.com/doc/guide/data

base.ar

I'm Interested.

PHP implementations to ignore: CodeIgniter's ActiveRecord

(It doesn't even vaguely resemble either Fowler's or Ruby on Rails' ActiveRecord. Sorry guys.)

I'm Interested.

.NET: Uhh... ADO.NET Entity Framework (w/ .NET 3.5) www.castleproject.org/activerecord/