Agile Toolkit All In One Digest – Part 2

Standard
Part 1 of all in one series:
http://webelizer.net/211/agile-toolkit-all-in-one-digest.html

Database Integration

Agile Toolkit features two basic classes for model creation. The class “Model” is a lightweight implementation for NoSQL and non-relational databases. “Model_Table” class provides a more powerful model implementation which can rely on the power of relational database…

Expressions

This is a good start, but there are other features your favorite database engine has — functions, expressions and stored procedures. Agile Toolkit allows you to embed expressions into your model in a very reliable way. Here are just a few ways how to define a expression

$book->addExpression('random_number')->set('rand()');

$book->addExpression('records_in_test_table')->set(function($m,$q){
  return $q->dsql()->table('test')->field('count(*)');
});

======

$sold_books = $book->count()->where('is_sold',1)->getOne();

=======

After you load a record into a model, you may traverse dependencies using the rel() method

$book->load(1);
$author = $book->rel('author_id');

//-----------------------

/*
This time we are traversing one to many relation, therefore "$books" model will not contain a pre-loaded entry. Instead it will have a condition which will limit the range of accessible records to the books written by author with id=8.
*/
$author->load(8);
$books = $author->rel('Book');

//---------------------

$author->load(8);
$books = $author->rel('Book');
$books -> dsql() -> set('is_sold',true) -> update();

/*
This will mark all the books "sold" for that particular author. Agile Toolkit takes extra care not to mark any other books with the update.
*/

=======

is is all the code you need to implement soft-delete:

function init(){
  parent::init();
  $this->addField('is_deleted')->type('boolean');
  $this->addCondition('is_deleted',false);
}
function delete($id=null){
  if($id)$this->load($id);
  if(!$this->loaded())throw $this->exception('Unable to determine which record to delete');
  $this->set('is_deleted',true);
  $this->saveAndUnload();
}

=======

Assume that the next requirement is to introduce “state” of the book with two possible values “draft” and “active”. All of your code now must not see the “draft” books. How wil you do that?

class Model_Book extends Model_Table {
  function init(){
    parent::init();
    $this->addField('status')->enum(array('active','draft'))->defaultValue('active');
    $this->setConditions();
  }
  function setConditions(){
    $this->addCondition('status','active');
  }
}

class Model_Book_Draft extends Model_Book {
  function setConditions(){
    $this->addCondition('status','draft');
  }
  function publish(){
    $this->set('status','active');
    return $this->saveAs('Book');
  }
}

==========

DSQL is a model class in Agile Toolkit which is a object-oriented model for SQL queries.

==========

Hooks

class Model_Book extends Model_Table {
  function init(){
    parent::init();

    $this->addHook('beforeSave,afterLoad',$this);
  }

  function beforeSave(){
    // manually update some fields before saving. This is to create a indexable field for full-text search
    $this['book_search_field'] = $this['title'].' '.$this['descr'].' '.$this['author_name'];

    // let's also perform some validation
    if(strlen($this['book_name']<10))throw $this->exception('Name of the book is too short');

    // normalization will modify field to match some internal rules
    $this['book_url']=$this['book_url'] ?: preg_replace('/[^a-zA-Z0-9]/','-',trim($this['name']));
  }
  function afterLoad(){
    $this['name'] = $this->api->_($this['name']); // wraps name through localization function
  }
}

====

There are the following hooks available for the model (at least):

  • beforeLoad($model, $query) – called before loading SQL query is executed. You have a chance to modify that query. This is called for both model->load() and for iterating through model with foreach($model). This hook is great for applying extra options to your SQL query.
  • afterLoad($model) – called after data have been loaded from SQL. You can now access $model->get() and the model will appear to be loaded. Called for both model->load() and iterating. This hook is great for performing data manipulation and normalization.
  • beforeSave($model) – called when $model->save() is called. This is called inside SQL transaction, so database changes you perform here will be rolled back if save would be unsuccessful. This hook is great for performing data modification before it’s been saved. You can check $model->loaded() to see if a new record is being stored or updated
  • beforeInsert($model, $query) – called when inserting new data and after the insert query is being formed. That query is passed as 2nd argument. This hook is great for changing insert query options.
  • afterInsert($model,$id) – called after insertion method is performed successfully, but before model is re-loaded. You can break out of this hook and return a substitute model. Great for overriding how model is reloaded after insert.
  • beforeModify($model,$query) – called before update SQL query is executed. This hook is great for changing update query options.
  • afterModify($model) – called after SQL query is executed but before reloading has taken place. Note that if you access set() / get() here it will be reloaded by a subsequental reload.
  • afterSave($model) – called after model have been successfully reloaded. This is the last hook to be executed before SQL transaction is finished with commit. Please note taht beforeLoad / afterLoad will also be called during the reloading of a model. This hook is great for hiding some fields from a model after they are being saved such as wiping your password field.

Few more recommendation on query use.

  • to perform additional validation use beforeSave().
  • to measure speed of your query use beforeInsert / afterInsert and beforeModify and afterModify respectively.
  • beforeLoad will be called once per query, but afterLoad may be called several times when iterating through results.
  • apply SQL options in beforeLoad, beforeInsert and beforeModify
  • if you want to divert the query to a different database connection beforeLoad, beforeInsert and beforeModify are good place, but you must enhance DSQL to support switching before databsae handles. If this is what you need, please discuss on Agile Toolkit Development Forum.

Non-relational models

Above hooks also are used for non-relational models, however instead of $query they will receive $id of the record which is about to be loaded or modified.

===============

Debugging Models

An important thing about models you should understand, is that models on their own are here to serve 4 purposes:

  • Produce dynamic SQL objects
  • Load, hold and save a single record of data
  • Contain meta-information for other views
  • Perform business operations

Some actions are more likely to go wrong, usually because of incorrectly defined fields.

Debugging produced SQL statement

You can switch on debug mode for any model by calling:

$model->debug();

This will output queries as they are being generated by the underlying Dynamic SQL object(s). This method is good to see how dynamic SQL queries are being produced, and how data is being loaded, saved or otherwise accessed.

If your query seems to be overly complicated and you want to isolate individual fields, you can use setActualFields();

$model->debug();
$model->setActualFields(array('calculated_field_1'));
$model->load(123);

Get current row data from model

You can see what’s inside your model by calling var_dump($model->get()). This will output contents of the currently loaded record.

Transaction support

Agile Toolkit attempt to rely on SQL transactions. When model is being save()d, it will attempt to re-load record afterwards. If the load fails due to conditions, the whole transaction is rolled back undoing save.

Often you can also use transactions operations to improve your code

class Model_Book extends Model_Table {

// Skipped

function addMultipleBooks($data){
$this->_dsql()->owner->beginTransaction();

foreach($data as $row){
$this->unloadData();
$this->set($row);
$this->save();
}

$this->_dsql()->owner->commit();
}
}

If error happens during inserts, an exception will bubble up through the call-stack and data will not be committed.

Adding more information to exceptions

In Agile Toolkit error reporting is implemented using Exceptions. To improve reporting you can intercept the exception add more information to it.

    function addMultipleBooks($data){
$this->_dsql()->owner->beginTransaction();
try {
foreach($data as $row){
$this->unloadData();
$this->set($row);
$this->save();
}
}catch(BasicException $e){
throw $e->addMoreInfo('record_count',count(data));
}
$this->_dsql()->owner->commit();
}

Catching an exception will allow you to manipulate it – such as calling addMoreInfo(). This is chain-able method and therefor you can “throw” value returned by it.

exception() method

Agile Toolkit defines the exception() method globally. Each model can define which exception class it uses, and the exception() function will return an instance of this class. This also allows for a more flexible syntax in the PHP code: throw $this->exception()->addMoreInfo(..)

You can either redefine this method to add additional information – such as certain data from the model for exceptions which are being generated – or specify a different exception class through the $default_exception property.

 

One thought on “Agile Toolkit All In One Digest – Part 2

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>