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. ● Our own CMS has 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? ● Our class 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 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
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. ● Our class 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 want Model->findAll() to retrieve records. ● function defaultScope() { return array( 'condition' => 'deleted = 0' ); }
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. eg. ● 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->generateTickets($order->tickets); }
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 our class (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 ignore: ● CodeIgniter's ActiveRecord – (It doesn't even vaguely resemble either Fowler's or Ruby on Rails' ActiveRecord. Sorry guys.)