Observers: Included observers
Observer_Self
While not exactly good practice, in some cases it's probably cleanest to just have the event method with your model. That's where the Observer_Self comes in, it checks if you model has a method named after the event and prefixed with _event_. For example for the after_save event you need to add a public method _event_after_save() to your model so it can have the observer call on the model itself.
// Just add the Observer
protected static $_observers = array('Orm\\Observer_Self');
// But if you only observe some events you can optimize by only adding those
protected static $_observers = array('Orm\\Observer_Self' => array('after_save', 'before_insert'));
Observer_CreatedAt
This observer acts only upon before_insert and expects your model to have a created_at property which will be set to the Unix timestamp when saving for the first time.
// Just add the Observer
protected static $_observers = array('Orm\\Observer_CreatedAt');
// But adding it just for before_insert is enough
protected static $_observers = array('Orm\\Observer_CreatedAt' => array('before_insert'));
You can change the default property expected and use a mySQL timestamp instead of the defaults:
// Use mySQL timestamp instead of Unix timestamp
Orm\Observer_CreatedAt::$mysql_timestamp = true;
// Use property called just "created" instead of "created_at"
Orm\Observer_CreatedAt::$property = 'created';
Observer_UpdatedAt
This observer acts only upon before_save and expects your model to have a updated_at property which will be set to the Unix timestamp when saving (also during the first time).
// Just add the Observer
protected static $_observers = array('Orm\\Observer_UpdatedAt');
// But adding it just for before_insert is enough
protected static $_observers = array('Orm\\Observer_UpdatedAt' => array('before_save'));
You can change a $mysql_timestamp and $property value on this observer just like on the CreatedAt observer.
Observer_Validation
This observer only acts on before_save. It is used to prevent the model from saving if validation rules fail. It is best used with the FieldSet class.
The observer can be loaded as follows:
// Just add the Observer
protected static $_observers = array('Orm\\Observer_Validation');
// But adding it just for before_save is enough
protected static $_observers = array('Orm\\Observer_Validation' => array('before_save'));
Validation rules should be defined in your model in $_properties. This is demonstrated in Creating Models. After you have added the Validation Observer, the Orm\ValidationFailed exception will be thrown when the model's data fails to validate before save. As such, you must try/catch your calls to the save function of a model.
A complete CRUD functionality/scaffolding example controller is shown below:
class Controller_Articles extends Controller_Template {
public function action_index()
{
$data['articles'] = Model_Article::find('all');
$this->template->title = "Articles";
$this->template->content = View::factory('articles/index', $data);
}
public function action_view($id = false)
{
if (!$data['article'] = Model_Article::find($id))
{
Request::show_404();
}
$this->template->title = "Article";
$this->template->content = View::factory('articles/view', $data);
}
public function action_create()
{
$article = Model_Article::factory();
if (Input::method() == 'POST')
{
$article->name = Input::post('name');
$article->url = Input::post('url');
try
{
$article->save();
Session::set_flash('notice', 'Created article #' . $article->id);
Response::redirect('articles');
}
catch (Orm\ValidationFailed $e)
{
Session::set_flash('notice', 'Could not create article #' . $article->id);
}
}
$this->template->set_global('article', $article, false);
$this->template->title = "Articles";
$this->template->content = View::factory('articles/create');
}
public function action_edit($id = false)
{
if (!$article = Model_Article::find($id))
{
Request::show_404();
}
if (Input::method() == 'POST')
{
$article->name = Input::post('name');
$article->url = Input::post('url');
try
{
$article->save();
Session::set_flash('notice', 'Updated article #' . $id);
Response::redirect('articles');
}
catch (Orm\ValidationFailed $e) {
Session::set_flash('notice', 'Could not update article #' . $id);
}
}
$this->template->set_global('article', $article, false);
$this->template->title = "Articles";
$this->template->content = View::factory('articles/edit');
}
public function action_delete($id = null)
{
if ($article = Model_Article::find($id))
{
$article->delete();
Session::set_flash('notice', 'Deleted article #' . $id);
}
else
{
Session::set_flash('notice', 'Could not delete article #' . $id);
}
Response::redirect('articles');
}
}
This can be effectively used with the FieldSet class to provide CRUD forms for a model. In the following example, the create and edit forms are defined in a common view however you can just as easily define it in the model and obtain an instance of it in the view using Fieldset::instance.
if ($fs = Fieldset::instance(get_class($article)))
{
echo $fs->show_errors();
}
$fieldset = Fieldset::factory('article')->add_model($article, null, null);
$fieldset->add('name', 'Article Name', array('type' => 'text'));
$fieldset->add('url', 'Article\'s URL', array('type' => 'text'));
$fieldset->add('submit', ' ', array('value' => 'Submit', 'type' => 'submit'));
$fieldset->populate($article, TRUE);
echo $fieldset->build();
Observer_Typing
This is for 2 things: type enforcement for input and type casting for output from the DB. That means that when you're saving the Typing observer will try to cast the input value to the expected type and throw an exception when it can't. And when you're retrieving DB data, normally it would all be strings (even integers and floats) but with the typing observer those will be cast to their scalar type.
In addition to the above the Typing observer also adds support for serialized & json fields. Both should be string type fields ("text" preferably) but will have their value encoded for saving (using serialize() or json_encode()) and decoded when retrieving from the DB (using unserialize() or json_decode()).
The Observer_Typing isn't meant as an alternative to validation, don't try to use it as such. Neither are the exceptions thrown by this observer meant to be read by the visitor of your site, they're meant to help you debug your code.
// Just add the Observer
protected static $_observers = array('Orm\\Observer_Typing');
// But adding it just for these specific events is enough
protected static $_observers = array('Orm\\Observer_Typing' => array('before_save', 'after_save', 'after_load'));
For this observer to work you must have your the $_properties static variable set in your model, or not set at all using detection with DB::list_columns() (mySQL only!). When configuring it yourself the following settings are available:
Param | Valid input | Description |
---|---|---|
data_type | varchar, int, integer, tinyint, smallint, mediumint, bigint, float, double, decimal, text, tinytext, mediumtext, longtext, enum, set, bool, boolean, serialize, json | The SQL data type, Required to have the typing observer used on a field. |
is_nullable | bool | Whether null is allowed as a value |
character_maximum_length | int | The maximum number of characters allowed for a string data type (varchar, text) |
min | int | The minimal value for an integer |
max | int | The mixamum value for an integer |
options | array |
Array of valid string values for set or enum data type Note: currently the options themselves cannot contain comma's. |