Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

User:Frédéric Massart: Difference between revisions

From MoodleDocs
No edit summary
No edit summary
Line 3: Line 3:
Moodle persistents are equivalent to models (or active records). They represent an object stored in the database and provide the methods to create, read, update, and delete those objects. Additionally, the persistents validate its own data against automatic and custom validation rules.
Moodle persistents are equivalent to models (or active records). They represent an object stored in the database and provide the methods to create, read, update, and delete those objects. Additionally, the persistents validate its own data against automatic and custom validation rules.


Persistents extend the abstract class <code>core\persistent</code>.
Persistents extend the abstract class ''core\persistent''.


=== Properties ===
=== Properties ===


Defining properties can be done by defining the method <code>protected static define_properties()</code>. It returns an array where the keys are the names of the fields (and database columns), and their details. The ''type'' of each field must be specified using one of the <code>PARAM_*</code>. This type will be used to automatically validate the property's value.
Defining properties can be done by defining the method ''protected static define_properties()''. It returns an array where the keys are the names of the fields (and database columns), and their details. The ''type'' of each field must be specified using one of the ''PARAM_*'' constants. This type will be used to automatically validate the property's value.


<code>
<code>
    /**
/**
    * Return the definition of the properties of this model.
* Return the definition of the properties of this model.
    *
*
    * @return array
* @return array
    */
*/
    protected static function define_properties() {
protected static function define_properties() {
        return array(
    return array(
            'userid' => array(
        'userid' => array(
                'type' => PARAM_INT,
            'type' => PARAM_INT,
            ),
        ),
            'message' => array(
        'message' => array(
                'type' => PARAM_RAW,
            'type' => PARAM_RAW,
            )
        )
        );
    );
    }
}
</code>
</code>


Line 47: Line 47:


<code>
<code>
    /** Table name for the persistent. */
/** Table name for the persistent. */
    const TABLE = 'status';
const TABLE = 'status';
</code>
</code>


The table name must not contain the Moodle prefix. Also it is common practice to always refer to your table use by accessing the constant rather than repeating it.
The table name must not contain the Moodle prefix. Also it is common practice to always refer to your table use by accessing the constant rather than repeating it.


=== Assigning values properties ===
=== Assigning values to properties ===


Before even saving our object, we must find out how to assign values to our object's properties. There are 3 methods to do so.
Before even saving our object, we must find out how to assign values to our object's properties. There are 3 methods to do so.


You can pass an object (stdClass) as the second argument of the constructor. The object's properties will be assigned to the new instance.
You can pass an object (''stdClass'') as the second argument of the constructor. The object's properties will be assigned to the new instance.


<code>
<code>
Line 87: Line 87:
==== Defining your own setter ====
==== Defining your own setter ====


Though you don't have to for the code to work, you can define your own ''setter'' methods which will override the magic setters. They are useful if you want to transform the data prior to assigning it to your object. Though note that those setters will then have to use the ''set()'' method to assign the values.
Though you don't have to for the code to work, you can define your own ''setter'' methods which will override the magic setters. They are useful if you want to extract the data out of a more complex object prior to assigning it. Though note that those setters will then have to use the ''set()'' method to assign the values.


<code>
<code>
    /**
/**
    * Convenience method to set the user ID.
* Convenience method to set the user ID.
    *
*
    * @param object|int $idorobject The user ID, or a user object.
* @param object|int $idorobject The user ID, or a user object.
    */
*/
    public function set_userid($idorobject) {
public function set_userid($idorobject) {
        $userid = $idorobject;
    $userid = $idorobject;
        if (is_object($idorobject)) {
    if (is_object($idorobject)) {
            $userid = $idorobject->id;
        $userid = $idorobject->id;
        }
        $this->set('userid', $userid);
     }
     }
    $this->set('userid', $userid);
}
</code>
</code>


In the above example we will accept for an object or an ID, as a convenience for developers we will extract the ID value out of the object passed.
In the above example we will accept an object or an ID, as a convenience for developers we will extract the ID value out of the object passed if any.
 
Note that you cannot guarantee that your setter will be used. Developers can directly call the ''set()'' method. Therefore you must not use a custom setter to reliably transform any data added to a property. For instance do not add a custom setter to remove HTML tags out of a text field, it may not always happen.


Note that you cannot guarantee that your setter will be used. Developers can directly call the ''set()'' method. So do not use a custom setter to reliably transform any data added to a property.
You can obviously create your own setters which aren't based on any properties just as a convenience. For instance we could have created ''set_userid_from_user(object $user)'' which is more verbose and more predictable


=== Read, save and delete entries ===
=== Read, save and delete entries ===


The method to ''create'', ''read'', ''update'' and ''delete'' are eponymous. Your object will be validated before you ''create'' or ''update'' it. The ''update'', ''delete'' and ''read'' methods require your object to contain its ID. And you also won't be allowed to ''create'' an entry which already had an ID field.
The methods to ''create'', ''read'', ''update'' and ''delete'' are eponymous. Your object will be validated before you ''create'' or ''update'' it. The ''update'', ''delete'' and ''read'' methods require your object to contain its ID. And you also won't be allowed to ''create'' an entry which already had an ID defined.


Here are some code examples (based on the persistent ''status''):
Here are some code examples:


<code>
<code>
Line 118: Line 120:
$id = 123;
$id = 123;
$persistent = new status($id);
$persistent = new status($id);
</code>


<code>
// Create previously instantiated object in the database.
// Create previously instantiated object in the database.
$persistent->create();
$persistent->create();
</code>


<code>
// Load an object from the database, and update it.
// Load an object from the database, and update it.
$id = 123;
$id = 123;
Line 127: Line 133:
$persistent->set_message('Hello new world!');
$persistent->set_message('Hello new world!');
$persistent->update();
$persistent->update();
</code>


<code>
// Reset the instance to the values in the database.
// Reset the instance to the values in the database.
$persistent->read();
$persistent->read();
</code>


<code>
// Permanently delete the object from the database.
// Permanently delete the object from the database.
$persistent->delete();
$persistent->delete();
</code>
</code>


=== Validating fields ===
=== Validating ===


The validation of fields happens automatically based on their type (PARAM_* constant), however this not always enough. In order to implement your own custom validation, simply define a protected method starting with ''validate_'' followed with the field name. This method will be called whenever the model needs to be validated and will receive the data to validate.
Basic validation of the properties values happens automatically based on their type (''PARAM_*'' constant), however this not always enough. In order to implement your own custom validation, simply define a ''protected'' method starting with ''validate_'' followed with the property name. This method will be called whenever the model needs to be validated and will receive the data to validate.


A validation method must always return either ''true'' or an instance of ''lang_string'' which contains the error message to send to the user.
A validation method must always return either ''true'' or an instance of ''lang_string'' which contains the error message to send to the user.


<code>
<code>
    /**
/**
    * Validate the user ID.
* Validate the user ID.
    *
*
    * @param int $value The value.
* @param int $value The value.
    * @return true|lang_string
* @return true|lang_string
    */
*/
    protected function validate_userid($value) {
protected function validate_userid($value) {
         global $DB;
    global $DB;
 
    if (!core_user::is_real_user($value, true)) {
         return new lang_string('invaliduserid', 'error');
    }
 
    return true;
}
</code>
 
The above example ensures that the ''userid'' property contains a valid user ID.
 
Note that the basic validation is always performed first, and thus your custom validation method will not be called when the value did not pass the basic validation.
 
==== Validation results ====
 
The validation of the object automatically happens upon ''create'' and ''update''. If the validation did not pass, an ''invalid_persisten_exception'' will be raised. You can validate the object prior to saving the object and get the validation results if you need to.
 
<code>
// We can catch the invalid_persistent_exception.
try {
    $persistent = new status();
    $persistent->create();
} catch (invalid_persistent_exception $e) {
    // Whoops, something wrong happened.
}
</code>
 
<code>
$persistent = new status();
 
// Whether the object is valid.
$persistent->is_valid();        // True or false.
 
// Get the validation errors.
$persistent->get_errors();      // Array where keys are properties and values are errors.
 
// Validate the object.
$persistent->validate();        // Returns true, or an array of errors.
</code>
 
=== Fetching records ===
 
Once you start using persistents you should never directly interact with the database outside of your class. The persistent class comes with a few handy methods allowing you to retrieve your objects.
 
<code>
// Use the constructor to fetch one object from its ID.
$persistent = new status($id);
</code>
 
<code>
// Get one record from a set of conditions.
$persistent = status::get_record(['userid' => $userid, 'message' => 'Hello world!']);
</code>
 
<code>
// Get multiple records from a set of conditions.
$persistents = status::get_records(['userid' => $userid]);
</code>
 
<code>
// Count the records.
$count = status::count_records(['userid' => $userid]);
</code>
 
<code>
// Check whether a record exists.
$exists = status::record_exists($id);
</code>
 
Make sure to also check their additional parameters and their variants (''record_exists_select()'', ''count_records_select'', ''get_records_select'', ...).
 
==== Custom fetching ====
 
It's always a good idea to add more complex queries directly within your persistent. By convention you should always return an instance of your persistent and never an stdClass. Here we add a custom method which allows to directly fetch all records by username.
 
<code>
/**
* Get all records by a user from its username
*
* @param string $username The username.
* @return status[]
*/
public static function get_records_by_username($username) {
    global $DB;


        if (!core_user::is_real_user($value, true)) {
    $sql = 'SELECT s.*
            return new lang_string('invaliduserid', 'error');
              FROM {' . static::TABLE . '} s
        }
              JOIN {user} u
                ON u.id = s.userid
            WHERE u.username = :username';


         return true;
    $persistents = [];
 
    $recordset = $DB->get_recordset_sql($sql, ['username' => $username]);
    foreach ($recordset as $record) {
         $persistents[] = new static(0, $record);
     }
     }
    $recordset->close();
    return $persistents;
}
</code>
</code>


The above example ensures that the ''userid'' property contains a valid user ID.
=== Hooks ===
 
You can define the following methods to be notified prior to, or after, something happened:


Note that the basic validation is always performed first, and thus your custom validation method will not be called if the value did not pass the basic validation.
;before_validate()
: Do something before the object is validated.
;before_create()
: Do something before the object is inserted in the database. Note that values assigned to properties are not longer validated at this point.
;after_create()
: Do something right after the object was added to the database.
;before_update()
: Do something before the object is updated in the database. Note that values assigned to properties are not longer validated at this point.
;after_update(bool $result)
: Do something right after the object was updated in the database.
;before_delete()
: Do something right before the object is deleted from the database.
;after_delete(bool $result)
: Do something right after the object was deleted from the database.


=== Full example ===
=== Full example ===

Revision as of 08:49, 13 December 2016

Persistents

Moodle persistents are equivalent to models (or active records). They represent an object stored in the database and provide the methods to create, read, update, and delete those objects. Additionally, the persistents validate its own data against automatic and custom validation rules.

Persistents extend the abstract class core\persistent.

Properties

Defining properties can be done by defining the method protected static define_properties(). It returns an array where the keys are the names of the fields (and database columns), and their details. The type of each field must be specified using one of the PARAM_* constants. This type will be used to automatically validate the property's value.

/**

* Return the definition of the properties of this model.
*
* @return array
*/

protected static function define_properties() {

   return array(
       'userid' => array(
           'type' => PARAM_INT,
       ),
       'message' => array(
           'type' => PARAM_RAW,
       )
   );

}

Here we define two mandatory fields, one being a non-null integer, and the other one a non-null free text field. For a complete list of properties

Mandatory properties

Four fields are always added to your persistent and should be reflected in your database table. You must not define those properties inThose are:

id (non-null integer)
The primary key of the record.
usermodified (non-null integer)
The user who created/modified the object. It is automatically set.
timecreated (non-null integer)
The timestamp at which the record was modified. It is automatically set.
timemodified (non-null integer)
The timestamp at which the record was modified. It is automatically set, and defaults to 0.

Attaching to the database

While the persistent class is helpful for database interactions, it does not automatically fetch the properties from the database, nor does it create the tables. You will need to create the table yourself, as well as pointing the persistent to the right class. This can be done by defining the constant TABLE.

/** Table name for the persistent. */ const TABLE = 'status';

The table name must not contain the Moodle prefix. Also it is common practice to always refer to your table use by accessing the constant rather than repeating it.

Assigning values to properties

Before even saving our object, we must find out how to assign values to our object's properties. There are 3 methods to do so.

You can pass an object (stdClass) as the second argument of the constructor. The object's properties will be assigned to the new instance.

// Instantiates a new object with value for some properties. $data = (object) array('userid' => 2, 'message' => 'Hello world!'); $persistent = new status(0, $data);

Or you can use the set() method on an instance.

// Instantiates a blank object. $persistent = new status();

// Assign a new value to the 'message' property. $persistent->set('message', Hello new world!');

Finally you can use the magic setters 'set_' followed by the property name.

// Instantiates a blank object. $persistent = new status();

// Assign a new value to the 'message' property. $persistent->set_message('Hello new world!');

Defining your own setter

Though you don't have to for the code to work, you can define your own setter methods which will override the magic setters. They are useful if you want to extract the data out of a more complex object prior to assigning it. Though note that those setters will then have to use the set() method to assign the values.

/**

* Convenience method to set the user ID.
*
* @param object|int $idorobject The user ID, or a user object.
*/

public function set_userid($idorobject) {

   $userid = $idorobject;
   if (is_object($idorobject)) {
       $userid = $idorobject->id;
   }
   $this->set('userid', $userid);

}

In the above example we will accept an object or an ID, as a convenience for developers we will extract the ID value out of the object passed if any.

Note that you cannot guarantee that your setter will be used. Developers can directly call the set() method. Therefore you must not use a custom setter to reliably transform any data added to a property. For instance do not add a custom setter to remove HTML tags out of a text field, it may not always happen.

You can obviously create your own setters which aren't based on any properties just as a convenience. For instance we could have created set_userid_from_user(object $user) which is more verbose and more predictable

Read, save and delete entries

The methods to create, read, update and delete are eponymous. Your object will be validated before you create or update it. The update, delete and read methods require your object to contain its ID. And you also won't be allowed to create an entry which already had an ID defined.

Here are some code examples:

// Fetches an object from database based on its ID. $id = 123; $persistent = new status($id);

// Create previously instantiated object in the database. $persistent->create();

// Load an object from the database, and update it. $id = 123; $persistent = new status($id); $persistent->set_message('Hello new world!'); $persistent->update();

// Reset the instance to the values in the database. $persistent->read();

// Permanently delete the object from the database. $persistent->delete();

Validating

Basic validation of the properties values happens automatically based on their type (PARAM_* constant), however this not always enough. In order to implement your own custom validation, simply define a protected method starting with validate_ followed with the property name. This method will be called whenever the model needs to be validated and will receive the data to validate.

A validation method must always return either true or an instance of lang_string which contains the error message to send to the user.

/**

* Validate the user ID.
*
* @param int $value The value.
* @return true|lang_string
*/

protected function validate_userid($value) {

   global $DB;
   if (!core_user::is_real_user($value, true)) {
       return new lang_string('invaliduserid', 'error');
   }
   return true;

}

The above example ensures that the userid property contains a valid user ID.

Note that the basic validation is always performed first, and thus your custom validation method will not be called when the value did not pass the basic validation.

Validation results

The validation of the object automatically happens upon create and update. If the validation did not pass, an invalid_persisten_exception will be raised. You can validate the object prior to saving the object and get the validation results if you need to.

// We can catch the invalid_persistent_exception. try {

   $persistent = new status();
   $persistent->create();

} catch (invalid_persistent_exception $e) {

   // Whoops, something wrong happened.

}

$persistent = new status();

// Whether the object is valid. $persistent->is_valid(); // True or false.

// Get the validation errors. $persistent->get_errors(); // Array where keys are properties and values are errors.

// Validate the object. $persistent->validate(); // Returns true, or an array of errors.

Fetching records

Once you start using persistents you should never directly interact with the database outside of your class. The persistent class comes with a few handy methods allowing you to retrieve your objects.

// Use the constructor to fetch one object from its ID. $persistent = new status($id);

// Get one record from a set of conditions. $persistent = status::get_record(['userid' => $userid, 'message' => 'Hello world!']);

// Get multiple records from a set of conditions. $persistents = status::get_records(['userid' => $userid]);

// Count the records. $count = status::count_records(['userid' => $userid]);

// Check whether a record exists. $exists = status::record_exists($id);

Make sure to also check their additional parameters and their variants (record_exists_select(), count_records_select, get_records_select, ...).

Custom fetching

It's always a good idea to add more complex queries directly within your persistent. By convention you should always return an instance of your persistent and never an stdClass. Here we add a custom method which allows to directly fetch all records by username.

/**

* Get all records by a user from its username
*
* @param string $username The username.
* @return status[]
*/

public static function get_records_by_username($username) {

   global $DB;
   $sql = 'SELECT s.*
             FROM {' . static::TABLE . '} s
             JOIN {user} u
               ON u.id = s.userid
            WHERE u.username = :username';
   $persistents = [];
   $recordset = $DB->get_recordset_sql($sql, ['username' => $username]);
   foreach ($recordset as $record) {
       $persistents[] = new static(0, $record);
   }
   $recordset->close();
   return $persistents;

}

Hooks

You can define the following methods to be notified prior to, or after, something happened:

before_validate()
Do something before the object is validated.
before_create()
Do something before the object is inserted in the database. Note that values assigned to properties are not longer validated at this point.
after_create()
Do something right after the object was added to the database.
before_update()
Do something before the object is updated in the database. Note that values assigned to properties are not longer validated at this point.
after_update(bool $result)
Do something right after the object was updated in the database.
before_delete()
Do something right before the object is deleted from the database.
after_delete(bool $result)
Do something right after the object was deleted from the database.

Full example

class status extends persistent {

   /** Table name for the persistent. */
   const TABLE = 'status';
   /**
    * Return the definition of the properties of this model.
    *
    * @return array
    */
   protected static function define_properties() {
       return array(
           'userid' => array(
               'type' => PARAM_INT,
           ),
           'message' => array(
               'type' => PARAM_RAW,
           )
       );
   }

}