180 likes | 353 Views
Behaviors in CakePHP 1.2. Making your models behave. CakeFest 08. Mariano Iglesias. Purpose of Models. Models encapsulate business logic The more models do, the less controllers have to They are not necessarily the PHP representation of a database table
E N D
Behaviors in CakePHP 1.2 Making your models behave CakeFest 08 Mariano Iglesias
Purpose of Models • Models encapsulate business logic • The more models do, the less controllers have to • They are not necessarily the PHP representation of a database table • They keep our code more manageable and readable
Purpose of Models • The skinny controllers / fat models approach • Taking it further: sexying up our application $comments = $this->Comment->find(‘all’, array( ‘conditions’ => array( ‘Comment.date’ => ‘<= ‘ . strtotime(‘-1 day’), ‘Comment.published’ => true ) )); foreach($records as $index => $record) { $records[$index][‘Comment’][‘by’] = $record[‘Author’][‘last’] . ‘, ‘ . $record[‘Author’][‘first’]; } versus: $comments = $this->Comment->last(‘1 day’);
Keeping Models DRY • The fatter models get, the more DRY they need to be • DRYing models the 1.1 way: AppModel • Orginizing AppModel by hierarchy AppModel MyModel method() AppModel SluggableModel method() MyModel
And then there was light: Behaviors • Behaviors are all about reusable model logic • They help us put models to diet: the skinny controllers / chubby models / athletic behaviors approach • They make our code even more readable, and help us solve the 1.1 AppModel multi-hierarchy problem
And then there was light: Behaviors • There are already a lot of behaviors out there: • Acl • Bindable / Containable • Sluggable • SoftDeletable • Taggable • Translate • Tree • Upload
Using Behaviors • Behaviors are attached to models through the actsAs model attribute: • A behavior can receive settings as an indexed array: class MyModel extends AppModel { var $name = ‘My’; var $actsAs = array(‘Bindable’); } class MyModel extends AppModel { var $name = ‘My’; var $actsAs = array(‘Bindable’ => array( ‘parameter’ => ‘value’, ‘parameter2’ => ‘value2’ )); }
Using Behaviors MyModel OtherModel BindableBehavior • There’s only one behavior instance per CakePHP instance: • A model can use several behaviors • Behaviors can also be specified at the AppModel level • Fresh from the owen: attach(), and detach()
Using Behaviors • Callbacks in behaviors: • beforeFind(&$Model, $query) • afterFind(&$Model, $results, $primary) • beforeValidate(&$Model) • beforeSave(&$Model) • afterSave(&$Model, $created) • beforeDelete(&$Model, $cascade) • afterDelete(&$Model) • onError(&$Model, $error) • Extending the model with methods in behaviors • Extending the new find syntax
Getting inside behaviors • Naming your behavior classes and files • A simple behavior skeleton • The $settings behavior property class MyBehavior extends ModelBehavior { function setup(&$Model, $settings = array()) { } function cleanup(&$Model) { } } class MyBehavior extends ModelBehavior { var $settings; function setup(&$Model, $settings = array()) { } // ……… }
Getting inside behaviors • Implementing callbacks in behaviors • Implementing model accesible methods in behaviors • Using other behaviors from a behavior: • App::import($behavior, ‘Behavior’) • Calling attach() and detach() class MyBehavior extends ModelBehavior { function beforeSave(&$Model) { $data = $Model->data[$Model->alias]; // ……… } }
Getting inside behaviors • Modifying and extending the find syntax $Model->find(‘all’, array( ‘parameter’ => ‘setting’ )); class MyBehavior extends ModelBehavior { function beforeFind(&$Model, $query) { if (isset($query[‘parameter’])) { // ……… } } }
Learning by example: SoftDeletable • Defining the purpose of the behavior • Identifying callbacks that need implementation: • beforeDelete: actual soft delete • beforeFind: get non soft-deleted records • beforeSave: disable soft deleted check before saving • afterSave: re-enable soft deleted check • Custom methods: • hardDelete • purge • undelete
Learning by example: SoftDeletable • Taking care of initialization and cleaning class SoftDeletableBehavior extends ModelBehavior { var $__settings = array(); function setup(&$Model, $settings = array()) { $default = array('field' => 'deleted', 'field_date' => 'deleted_date', 'delete' => true, 'find' => true); if (!isset($this->__settings[$Model->alias])) { $this->__settings[$Model->alias] = $default; } $this->__settings[$Model->alias] = array_merge($this->__settings[$Model->alias], ife(is_array($settings), $settings, array())); } function cleanup(&$Model) { } }
Learning by example: SoftDeletable function beforeDelete(&$Model, $cascade = true) { if ($this->__settings[$Model->alias]['delete'] && $Model->hasField($this->__settings[$Model->alias]['field'])) { $attributes = $this->__settings[$Model->alias]; $id = $Model->id; $data = array($Model->alias => array( $attributes['field'] => 1 )); if (isset($attributes['field_date']) && $Model->hasField($attributes['field_date'])) { $data[$Model->alias][$attributes['field_date']] = date('Y-m-d H:i:s'); } foreach(array_merge(array_keys($data[$Model->alias]), array('field', 'field_date', 'find', 'delete')) as $field) { unset($attributes[$field]); } if (!empty($attributes)) { $data[$Model->alias] = array_merge($data[$Model->alias], $attributes); } $Model->id = $id; $deleted = $Model->save($data, false, array_keys($data[$Model->alias])); if ($deleted && $cascade) { $Model->_deleteDependent($id, $cascade); $Model->_deleteLinks($id); } return false; } return true; }
Learning by example: SoftDeletable function beforeFind(&$Model, $queryData) { // ……… if (!empty($queryData['conditions']) && is_string($queryData['conditions'])) { $fields = array( $Db->name($Model->alias) . '.' . $Db->name($this->__settings[$Model->alias]['field']), $Db->name($this->__settings[$Model->alias]['field']), $Model->alias . '.' . $this->__settings[$Model->alias]['field'], $this->__settings[$Model->alias]['field'] ); foreach($fields as $field) { if (preg_match('/^' . preg_quote($field) . '[\s=!]+/i', $queryData['conditions']) || preg_match('/\\x20+' . preg_quote($field) . '[\s=!]+/i', $queryData['conditions'])) { $include = false; break; } } if ($include) { if (empty($queryData['conditions'])) { $queryData['conditions'] = array(); } if (is_string($queryData['conditions'])) { $queryData['conditions'] = $Db->name($Model->alias) . '.' . $Db->name($this->__settings[$Model->alias]['field']) . '!= 1 AND ' . $queryData['conditions']; } else { $queryData['conditions'][$Model->alias . '.' . $this->__settings[$Model->alias]['field']] = '!= 1'; } } } return $queryData; }
Conclusion • Questions? • Comments? • Standing round of applause? Mariano Iglesias mariano.iglesias@cricava.com