Object-Relational Mapping (ORM)

Object-Relational Mapping (ORM) is a technique that lets you query and manipulate data from a database using an object-oriented paradigm. When talking about ORM, most people are referring to a library that implements the Object-Relational Mapping technique, hence the phrase "an ORM".

An ORM library is a completely ordinary library written in your language of choice that encapsulates the code needed to manipulate the data, so you don't use SQL anymore; you interact directly with an object in the same language you're using.

(The above is taken from: Stackoverflow)

Our ORM framework provides a streamlined way of working with the database. It was created with the goal of speeding up development and creating as many reusable parts as possible. 

It is very closely related to Laravel's Eloquent and you'll find a lot of similarities. It builds on top of our DML and on top of the Query builder.

One important difference is that our ORM mixes the ActiveRecord Pattern with the Repository Pattern. Entities provide the ActiveRecord part and are essential the Models in Eloquent. The Entity repository is used to query and interact with collections of entities, which in Eloquent is also handled by the Models. The Entity relationships provide a powerful way to load connected data and use the power of lazy and eager loading to load related entities. If you have a model which is based on an entity you can make use of the Entity model.

Defining entities

Each database table has a corresponding entity which is used to interact with that table in an object-orientated way. For example a competency entity represents a row of the mdl_comp table.

As most of the basic functionality is built into the general entity class the simplest example of an entity just requires extending the entity class and defining the table name.

/**
 * No need to define the column as properties as they are handled as attributes internally
 * but for IDE autocompletion and other functionality (like refactoring, find usages, etc.) 
 * you may want to define your columns as PHPDoc properties.
 * 
 * @property string $idnumber
 * @property string $fullname
 */
class competency extends \core\orm\entity\entity {

    public const TABLE = 'comp';

}

Retrieving entities

Retrieving single entity

Loading a single record from the database is made very easy.

// Load and modify a single competency
$competency = new competency(123);

// prints the whole entity
print_r($competency->to_array());

To avoid unwanted creation of records there will be an exception thrown when the record with the given id does not exist in the database.

If the record might not exist and you need to take that into account use the repository method:

// returns null if not found
$competency = competency::repository()->find(123);

Access attributes

You can access attributes via the get_attribute() method or directly via property access.

echo $competency->fullname;

If an attribute you are trying to access does not exist in the entity we throw a debugging message.

Retrieving collection of Entities

The entity repository provides the functionality to retrieve multiple records as a collection.

See the Entity repository documentation for details.

Refreshing an entity

If you ever want to refresh your object with the current values in the database call refresh();

// Load and modify a single competency
$competency = new competency(123);
$competency->fullname = 'new name';

$competency->refresh();

// prints false
var_dump($competency->changed());

// prints 'old name'
echo $competency->fullname;

Inserting and updating entities

Inserting an entity

Creating a record requires just a few line of code:

// Create a new framework
$framework = new competency_framework();
$framework->fullname = 'My framework';
$framework->idnumber = 'fw1';
$framework->save();

// Create a new competency
$competency = new competency();
$competency->fullname = 'OOP design'
$competency->framework = $framework->id;
$competency->save();

// prints the id
echo $competency->id;

// prints the whole entity
print_r($competency->to_array());

Updating an entity

There's a save() method to store your changes. save() is intelligent enough to detect if it's a new entity or a changed one. There are direct functions create() and update() which can be used as well.

// Load and modify a single competency
$competency = new competency(123);
$competency->fullname = 'new name';

// prints true
var_dump($competency->changed());

// Only changed attributes are persisted in the db
$competency->save();

If an attribute you are trying to set does not exist as a column in the table we throw an exception.


The entity keeps track of changed attributes and only writes those to the database if you call save(). Unchanged attributes are ignored on save(). Internally it's called dirty attributes.

Deleting entities

Deleting a single record is easy as well.

// Load and modify a single competency
$competency = new competency(123);
$competency->delete();

// prints true
var_dump($competency->deleted());

// prints 'null'
echo $competency->id;

Advanced usage

Relationships

It is possible to define and make use of relationships between entities. Please see the Entity relationship documentation for more details.

Accessors

You can use accessors to modify attributes while accessing them or create completely new attributes combined from others.

You can define your accessor method with the get_[attribute_name]_attribute() pattern.

If the attribute exists in the entity it is automatically passed as an argument to your function.

/**
 * @property int $status
 *
 * @property-read string $status_name
 */
class competency extends \core\orm\entity\entity {

    public const TABLE = 'comp';

    protected function get_status_name_attribute() { 
        switch ($this->status) {
            case 0:
                $name = 'disabled';
                break;
            case 1:
                $name = 'active';
                break;
        }
        return $name; 
    }

}

// Now you can access status name
$competency = new competency(123);

// prints "0"
echo $competency->status;
// prints "disabled"
echo $competency->status_name;

Mutators

Similar to accessors you can create mutator-methods to manipulate attributes when they are set. The value to be set is passed as the argument to the mutator method.

To set a value in a mutator you have to set the value of the internal attributes array.

    protected function set_idnumber_attribute($value) {
        $this->attributes['idnumber'] = strtolower($value);
    }

Extra attributes

By default an Entity only contains and reveals the attributes present as columns in the table row. If you call a to_array() you will get only those. If you want any extra_attributes to be present in a to_array() result you need to define it in the extra_attributes property.

/**
 * @property string $status
 *
 * @property-read string $status_name
 */
class competency extends \core\orm\entity\entity {

    public const TABLE = 'comp';

    protected $extra_attributes = [
        'status_name'
    ];

    protected function get_status_name_attribute() {
        // ...
        return $name;
    }

}

$competency = new competency(123);
print_r($competency->to_array());

// prints
// Array (
//     'status' => 0,
//     'status_name' => 'disabled'
// )

Sometimes you want to dynamically define if a property is an extra_property. In this case you can use the add_extra_attribute() method.

class competency extends \core\orm\entity\entity {

    public const TABLE = 'comp';
 
    public function with_status_name() {
        $this->add_extra_attribute('status_name');
        return $this;
    }

    protected function get_status_name_attribute() { switch ($this->status) {
        ...
    }

}

$competency = new competency(123);
$competency->with_status_name()->to_array();

Timestamp handling

The Entity can handle automatic updates of created and updated timestamps of rows.

You only need to define the column names for created and updated timestamps as constants. See the following example:

class user extends \core\orm\entity\entity {

    public const TABLE = 'user';

    public const CREATED_TIMESTAMP = 'timecreated';

    public const UPDATED_TIMESTAMP = 'timemodified';

}

$user = new user();
$user->firstname = 'Bob';
$user->lastname = 'Jones';
$user->save();

// prints the automatically set timestamp
echo $user->timecreated;
// prints null
echo $user->timemodified;

$user->firstname = 'Robert';
$user->save();

// prints automatically set timestamp
echo $user->timemodified;

// If you don't want timestamps for a single case to be set
$user->do_not_update_timestamps()->save()

Entities and DML

You can create entities using results of classic DML actions like get_record(), get_records() etc.

// Just pass a record to an entity and continue working with it.
$record = $DB->get_record('user', ['id' => 123]);

$user = new user($record);
// prints the id
echo $user->id;


// Sometimes it's easier to fall back to plain SQL
$sql = "
    SELECT u.* 
    FROM {user} u
    JOIN other_tables t1 ON u.id = t1.userid
    JOIN just_another_table t2 t1.id = t2.foreignid
    ...
    GROUP BY u.id
";
$records = $DB->get_records_sql($sql, $params);

// The result can still be converted to a collection of entities to keep continuing 
// in an object orientated way and profit from it's features
$users = user::repository()->from_records($records);

from_records() by default validates the attributes of the incoming records to make sure only the appropriate entity attributes are used.