Skip to content

API Design

Romans Malinovskis edited this page Apr 17, 2016 · 3 revisions

(Request For Comments)

This page consists of several "use cases" showing how our object interface would work. Please feel free to comment and suggest improvements.

Defining DataSet / Model

Model represents a DataSet. It describes a "category of entities" that have some business-logic similarities. The class definition (init method) is declarative by nature.

class User extends dataset\Model {
    protected $table = 'user';
    protected function init() {
        parent::init();

        $this->addField('name');
        $this->addField('email');
        $this->addField('is_expired')->type('boolean');

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

    function getExpried() {
        return $this->addCondition('is_expired', true);
    }
}

The above example describes the following:

  • $table property is a default "table" or "collection" inside data engine and shall be used by persistence layer unless we define it differently.
  • addField() register a new field as a part of a model. Each time a new object is returned that describes field. We can further set type, validation, caption and other field for each field.
  • hasMany() describes relation to a different DataSet. In this case we would be able to use $user->ref('Order') to get set of order for a particular user.
  • getExpired() is our custom method, which also is designed to give us a different DataSet that is a sub-set to User.

Persistence Layer design

Model initially does not have a way to persist data, however it can rely on "Data Driver". Essentially it would approach a separate "object" asking "could you store my state?"

$driver = new dataset\Data\MySQL(['connection'=>$pdo]); 
$driver -> save($model); // second argument is table

This would save the model inside the MySQL database using default table 'user'.

Persistence layer would rely on Query Builder that would be able to put together "insert/update" query dynamically and safely. Additional logic can use call-backs in this process to split up data into multiple tables, add joins or entirely override the query generation.

When model is loaded from particular "driver" it will remember it's source. If you update some fields and call() on the same driver, then only modified fields are updated (if database supports this of course).

As far as model is concerned, it will simply pass list of modified fields and their values to 'Data\MySQL' which will in turn build optimal UPDATE query and maybe even delay it's execution.

Resolving differences between DM and PM

This means that our Domain Model ('Model class') is somehow different to the database that we are using. Let's look at what kind of differences there could be:

  • field transformations, e.g. two different fields - date / time stored in timestamp
  • table transformations, e.g. our model actually is stored in several joined tables
  • value transformations if we need to change value of a field, from php Date into MongoDate.

Those transformation can be achieved through call-backs and supplimentarry objects. Again - as far as model is concerned, it can store some information (e.g. join or which field belongs to which table), but it's up to storage driver to convert that into query.

We should also make sure that DM can have multiple PMs with unique logic.

Default Persistence Layer

While in theory we want persistence layer to be separate, the reality is that in most cases application will be developed with the device driver in mind, so we can assign defaurt Storage for the model.

// EXAMPLE - WORK IN PROGRESS
$st = $user->setStorage($mysql, 'user_main');

$u_c = $st->join('user_contact.user_main_id');  // user_contact.user_main_id = user_main.id
$u_c - >storeFields('email,phone');


$ca = $user->setStorage($cache);
$ca -> addHook('beforeSave', function($payload, $m) {
    return json_encode($payload);
});

The above example defines how model is stored, depending on the storage. If we call $mysql->save($user), then persistance driver will understand that either 2 inserts must be executed or UPDATE with JOIN or maybe even 2 UPDATES.

How data is stored

When storing the data, the following steps are followed:

  • model:beforeSave() callback is called

  • Values from all the dirty fields are collected into $payload

  • st:beforeSave() is called. can do some changes.

  • Data Driver calls st:beforeUpdateQuery

  • Data Driver starts building query

  • Data Driver calls st:afterUpdateQuery

  • model:afterSave() is called

To be continued...