Skip to content

Persistence

Romans Malinovskis edited this page Apr 18, 2016 · 2 revisions

We offer you ability to tweak how your Business Models are stored.

So far you know that model definition contains $table property and that we use the $db object's "add" method to create instance of an object. The reason why we do that is to "link" this Model with that specific database connection:

class Model_Admin extends Model_User
{
    function init() {
        parent::init();

        $this->addCondition('type', 'admin');

        $this->addField('can_edit_users')->type('boolean');
        $this->addField('can_edit_orders')->type('boolean');
    }
}
$users = $mysql_db->add('Model_Admin');
$users -> tryLoadAny();

The goal of DB->PM mapping is to make sure that you don't have to change your code if your database model has been changed.

Let's think what we could do if we refactor our "user" table by splitting "can_edit_users" and "can_edit_orders" into a separate table called "admin_permission". Now in order to retrieve data from this table we have to perform a join. More importantly, when new Admin record is created, both tables have to be populated.

Thankfully after your database changes you will only have to change your code a little:

class Model_Admin extends Model_User
{
    function init() {
        parent::init();

        $this->addCondition('type', 'admin');

        $j_adm = $this->join('admin_permission.user_id');

        $j_adm->addField('can_edit_users')->type('boolean');
        $j_adm->addField('can_edit_orders')->type('boolean');
    }
}

Now lets remember that we also used to stored Model_Admin inside $mongo database. The Mongo driver does not support joins and despite being really useful, the above code can introduce some confusion if our Model needs to be saved into different databases.

Fortunately when we create Model instance, we use add() method and it also links $model->connection property to $db. All we have to do now is to update our init() method:

class Model_Admin extends Model_User
{
    function init() {
        parent::init();

        $this->addCondition('type', 'admin');

        if ($this->connection->supports('join')) {
            $j_adm = $this->join('admin_permission.user_id');
        }else{
            $j_adm = $this;
        }

        $j_adm->addField('can_edit_users')->type('boolean');
        $j_adm->addField('can_edit_orders')->type('boolean');
    }
}

Now our model will create "join" only in the cases when connection claims to have "join" support. Lets look at the "Model_Client" model now and see what we can do there:

class Model_Client extends Model_User
{
    function init() {
        parent::init();

        $this->addCondition('type', 'client');

        $this->hasMany('Order');
        $this->hasMany('Order_Completed');

        if ($this->connection->supports('expressions')) {
            $this->addExpression('completed_orders')
                ->set($this->ref('Order_Completed')->count());
        } else {
            $this->addField('completed_orders');
        }
    }
}

Now we have a way to use Model_Client with databases that do not support expressions.

Lets modify Model_Order so that it would work with non-sql database:

class Model_Order extends atk4\data\Model
{
    public $table='order';
    function init() {
        parent::init();

        $this->hasMany('Order_Line');
        $this->hasOne('Client');

        if ($this->connection->supports('expressions')) {
            $this->addExpression('amount')
                ->set($this->ref('Order_Line')->sum('amount'));
        } else {
            $this->addField('amount');
        }
    }
    function complete() {
        $this['isCompleted']=true;
        $this->saveAndUnload();

        if (!$this->connection->supports('expressions')) {
            $this->ref('Client')->incr('completed_orders');
        }
    }
}

Finally, I want to point out that Models support wide range of hooks (some of them may be called by SQL drivers only):

  • beforeLoad
  • afterLoad
  • buildLoadQuery
  • beforeSave
  • afterSave
  • buildUpdateQuery
  • buildInsertQuery
  • beforeUpdate
  • afterUpdate
  • beforeInsert
  • afterInsert

When you call join() it will add some bindings, for example beforeInsert() it will insert record into joined table and will link up the IDs. It will also hook on buildLoadQuery and add necessary join there. This logic heavily relies on DSQL library to make sure that multiple joins do not end up messing up with other expressions or your custom code.

And since we started talking about query building - there are ways how you can [convert your Business Models into SQL](Derived Queries).

Clone this wiki locally