Active Record (2010)

Active Record (2010)

The fifth in a short series of presentations given at a PHP development shop.


Rob Howard

June 29, 2010


  1. ActiveRecord

  2. What? • Model for database records. • Grouping of data

    and behaviour. • “Active Record Pattern” recorded by Martin Fowler, circa 1857 -->
  3. What? 1) Object that represents a single row. Model Instance

    id customer_full_name customer_gender create_time setPaid()
  4. What? 2) Object that interacts with multiple rows. Model Find

    Model Instances Delete
  5. 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().
  6. I think I'm using this already. • PHP itself has

    mysql_fetch_object(), which gives me back an object the data as properties.
  7. 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.
  8. 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.
  9. 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
  10. Example Time • Let's have a look at how other

    systems use Active Record. • Detailed example: • Yii Framework's CActiveRecord
  11. The Basics • Setup: • class Order extends CActiveRecord {

    public function tableName() { return 'order'; } }
  12. The Basics • Creating: • $order = new Order; $order->payer_full_name

    = 'Rob Howard'; $order->payer_email = ''; $order->save();
  13. The Basics • Reading: • $an_order = Order::model()->findByPk(27); • $deep_orders

    = Order::model()->findAll( 'payer_email LIKE :email', array(':email'=>'') ); • $orders = Order::model()->findAll(); // everything
  14. The Basics • Updating: • $order->payer_email = ''; if ($order->validate())

    { $order->save(); } • Deleting: • $order->findByPk(56)->delete();
  15. 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. ); }
  16. 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.
  17. 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; }
  18. 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'), ); }
  19. 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.
  20. Filters • Set up named rules for retrieving parts of

    your data, eg. • class TicketType { // ... function scopes() { return array( 'availableToPublic' => array( 'condition' => 'closing >= CURDATE()', ) ); }
  21. 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), )); }
  22. 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' ); }
  23. 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.
  24. 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', ) ); }
  25. 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. }
  26. 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); }
  27. 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.
  28. 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.
  29. I'm Interested. • PHP implementations to check out: •

    • • •
  30. 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.)
  31. I'm Interested. • .NET: • Uhh... • ADO.NET Entity Framework

    (w/ .NET 3.5) •
  32. None