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.
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.
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. ); }
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.
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; }
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'), ); }
= 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.
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), )); }
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', ) ); }
• 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. }
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.
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.