Note:

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

Persistent: Difference between revisions

From MoodleDocs
mNo edit summary
m (Text replacement - "<code php>" to "<syntaxhighlight lang="php">")
 
(15 intermediate revisions by 7 users not shown)
Line 5: Line 5:
Each class must define the properties that it contains. This can be done by defining the method ''protected static define_properties()''. It returns an array where the keys are the names of the properties (and implies the database column names), and their attributes. The ''type'' of each property must be specified using one of the ''PARAM_*'' constants. This type will be used to automatically validate the property's value.
Each class must define the properties that it contains. This can be done by defining the method ''protected static define_properties()''. It returns an array where the keys are the names of the properties (and implies the database column names), and their attributes. The ''type'' of each property must be specified using one of the ''PARAM_*'' constants. This type will be used to automatically validate the property's value.


<code php>
<syntaxhighlight lang="php">
/**
/**
  * Return the definition of the properties of this model.
  * Return the definition of the properties of this model.
Line 21: Line 21:
     );
     );
}
}
</code>
</syntaxhighlight>


=== Properties attributes ===
=== Properties attributes ===
Line 36: Line 36:
: An array of values which the property must fall in.
: An array of values which the property must fall in.


<code php>
<syntaxhighlight lang="php">
'messageformat' => array(
'messageformat' => array(
     'type' => PARAM_INT,
     'type' => PARAM_INT,
Line 50: Line 50:
     },
     },
),
),
</code>
</syntaxhighlight>


Note that you should always use a ''Closure'' for the ''default'' value when you cannot guarantee that it will not change since the start of the request. The list of properties and their attributes is cached, and so failure to use a ''Closure'' can result in using an outdated default value.
Note that you should always use a ''Closure'' for the ''default'' value when you cannot guarantee that it will not change since the start of the request. The list of properties and their attributes is cached, and so failure to use a ''Closure'' can result in using an outdated default value.
Line 66: Line 66:
;timemodified (non-null integer)
;timemodified (non-null integer)
: The timestamp at which the record was modified. It is automatically set, and defaults to 0.
: The timestamp at which the record was modified. It is automatically set, and defaults to 0.
From Moodle 3.7 onwards, the XMLDB tool has an option to add these fields to your table.


== Linking to a database table ==
== Linking to a database table ==
Line 71: Line 73:
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 it. This can be done by defining the class constant ''TABLE''.
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 it. This can be done by defining the class constant ''TABLE''.


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


The table name must not contain the Moodle prefix. Also it is common practice to always refer to your table name using the constant.
The table name must not contain the Moodle prefix. Also it is common practice to always refer to your table name using the constant.
Line 80: Line 82:
== Reading property values ==
== Reading property values ==


This can be done using the method ''get()'', or the magic method ''get_'' followed by the property name. Alternatively you can also use ''to_record()'' which exports the whole object.
This can be done using the method ''get()''. Alternatively you can also use ''to_record()'' which exports the whole object.


<code php>
<syntaxhighlight lang="php">
// Create a new object.
// Create a new object.
$persistent = new status();
$persistent = new status();


// Get the user ID using persistent::get().
// Get the user ID using the generic getter.
$userid = $persistent->get('userid');
$userid = $persistent->get('userid');
// Get the user ID using the magic getter.
$userid = $persistent->get_userid();


// Get all the properties in an stdClass.
// Get all the properties in an stdClass.
$data = $persistent->to_record();
$data = $persistent->to_record();
</code>
</syntaxhighlight>


You may override the magic getters to implement your own custom logic. For instance you could convert the data in another format automatically as a convenience for developers. However, use this sparingly as it may lead to confusion: what you get is not what is stored in the database. Note that you cannot guarantee that your getter will be used, remember that it is possible for developers to call the alternative ''get()'' which will not use your custom logic.
You may add a new custom getter to implement your own custom logic. To do this just add a new method get_propertyname() and it will be called from the generic get() method. For instance you could convert the data in another format automatically as a convenience for developers. However, use this sparingly as it may lead to confusion: what you get is not what is stored in the database. Also - to get the actual value from a custom getter - call raw_get(). Calling get() from a custom getter will generate a segfault due to infinite recursion.


It is, however, encouraged to add convenience methods such as the following:
It is, however, encouraged to add convenience methods such as the following:


<code php>
<syntaxhighlight lang="php">
/**
/**
  * Returns the user object of the author.
  * Returns the user object of the author.
Line 109: Line 108:
     return core_user::get_user($this->get('userid'));
     return core_user::get_user($this->get('userid'));
}
}
</code>
</syntaxhighlight>


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


There are a few methods to do so.
There are two ways to do so.


You use an object (''stdClass'') to assign a bunch of properties at once. Use it with the constructor, or the method ''from_record()''.
You use an object (''stdClass'') to assign a bunch of properties at once. Use it with the constructor, or the method ''from_record()''.


<code php>
<syntaxhighlight lang="php">
$data = (object) array('userid' => 2, 'message' => 'Hello world!');
$data = (object) array('userid' => 2, 'message' => 'Hello world!');


Line 127: Line 126:
$persistent->from_record($data);
$persistent->from_record($data);


</code>
</syntaxhighlight>


Or you can use the ''set()'' method on an instance.
Or, you can use the magic setters ''set_'' followed by the property name.


<code php>
<syntaxhighlight lang="php">
// Instantiates a blank object.
// Instantiates a blank object.
$persistent = new status();
$persistent = new status();
Line 137: Line 136:
// Assign a new value to the 'message' property.
// Assign a new value to the 'message' property.
$persistent->set('message', 'Hello new world!');
$persistent->set('message', 'Hello new world!');
</code>
</syntaxhighlight>
 
Finally you can use the magic setters ''set_'' followed by the property name.
 
<code php>
// Instantiates a blank object.
$persistent = new status();
 
// Assign a new value to the 'message' property.
$persistent->set_message('Hello new world!');
</code>


=== Defining a custom setter ===
=== Defining a custom 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.
Though you don't have to for the code to work, you can define your own ''setter'' methods which will be called from the generic setter. This is 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 ''raw_set()'' method to assign the values (if you do not you will see a segfault because of infinite recursion).


<code php>
<syntaxhighlight lang="php">
/**
/**
  * Convenience method to set the user ID.
  * Convenience method to set the user ID.
Line 164: Line 153:
         $userid = $idorobject->id;
         $userid = $idorobject->id;
     }
     }
     $this->set('userid', $userid);
     $this->raw_set('userid', $userid);
}
}
</code>
</syntaxhighlight>


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.
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.


Just like custom getters, you cannot guarantee that they will be used. Developers can directly call the ''set()'' method. Therefore you must not use a custom setter to reliably transform the 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.
Just like custom getters, must be careful so that the following code should not result in a change in the DB.
<syntaxhighlight lang="php">
$value = $persistent->get('property');
$persistent->set('property', $value);
$persistent->update();
</syntaxhighlight>


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
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
Line 180: Line 174:
Here are some code examples:
Here are some code examples:


<code php>
<syntaxhighlight lang="php">
// Fetches an object from database based on its ID.
// Fetches an object from database based on its ID.
$id = 123;
$id = 123;
Line 195: Line 189:
$id = 123;
$id = 123;
$persistent = new status($id);
$persistent = new status($id);
$persistent->set_message('Hello new world!');
$persistent->set('message', 'Hello new world!');
$persistent->update();
$persistent->update();


Line 203: Line 197:
// Permanently delete the object from the database.
// Permanently delete the object from the database.
$persistent->delete();
$persistent->delete();
</code>
</syntaxhighlight>


== Fetching records ==
== Fetching records ==
Line 209: Line 203:
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.
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 php>
<syntaxhighlight lang="php">
// Use the constructor to fetch one object from its ID.
// Use the constructor to fetch one object from its ID.
$persistent = new status($id);
$persistent = new status($id);
Line 224: Line 218:
// Check whether a record exists.
// Check whether a record exists.
$exists = status::record_exists($id);
$exists = status::record_exists($id);
</code>
</syntaxhighlight>


Make sure to also check their additional parameters and their variants (''record_exists_select()'', ''count_records_select'', ''get_records_select'', ...).
Make sure to also check their additional parameters and their variants (''record_exists_select()'', ''count_records_select'', ''get_records_select'', ...).
Line 232: Line 226:
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.
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 php>
<syntaxhighlight lang="php">
/**
/**
  * Get all records from a user's username.
  * Get all records from a user's username.
Line 258: Line 252:
     return $persistents;
     return $persistents;
}
}
</code>
</syntaxhighlight>


If you need to join persistents together and be able to extract their respective properties in a single database query, you should have a look at the following methods:
If you need to join persistents together and be able to extract their respective properties in a single database query, you should have a look at the following methods:
Line 267: Line 261:
: Extracts all the properties from a row based on the given prefix.
: Extracts all the properties from a row based on the given prefix.


<code php>
<syntaxhighlight lang="php">
// Minimalist example.
// Minimalist example.
$sqlfields = status::get_sql_fields('s', 'statprops');
$sqlfields = status::get_sql_fields('s', 'statprops');
Line 277: Line 271:
$statusdata = status::extract_record($row, 'statprops');
$statusdata = status::extract_record($row, 'statprops');
$persistent = new status(0, $statusdata);
$persistent = new status(0, $statusdata);
</code>
</syntaxhighlight>


== Validating ==
== Validating ==
Line 285: Line 279:
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 php>
<syntaxhighlight lang="php">
/**
/**
  * Validate the user ID.
  * Validate the user ID.
Line 299: Line 293:
     return true;
     return true;
}
}
</code>
</syntaxhighlight>


The above example ensures that the ''userid'' property contains a valid user ID.
The above example ensures that the ''userid'' property contains a valid user ID.
Line 307: Line 301:
=== Validation results ===
=== 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.
The validation of the object automatically happens upon ''create'' and ''update''. If the validation did not pass, an ''invalid_persistent_exception'' will be raised. You can validate the object prior to saving the object and get the validation results if you need to.


<code php>
<syntaxhighlight lang="php">
// We can catch the invalid_persistent_exception.
// We can catch the invalid_persistent_exception.
try {
try {
Line 326: Line 320:
// Validate the object.
// Validate the object.
$persistent->validate();        // Returns true, or an array of errors.
$persistent->validate();        // Returns true, or an array of errors.
</code>
</syntaxhighlight>


== Hooks ==
== Hooks ==
Line 346: Line 340:
;protected after_delete(bool $result)
;protected after_delete(bool $result)
: Do something right after the object was deleted from the database.
: Do something right after the object was deleted from the database.
== Persistent and forms ==
If your persistents are exposed to your users via forms, head to the [[Persistent form|persistent form page]].


== Common pitfalls ==
== Common pitfalls ==
Line 357: Line 355:
=== Minimalist ===
=== Minimalist ===


<code php>
<syntaxhighlight lang="php">
<?php
<?php
class status extends core\persistent {
class status extends \core\persistent {


     /** Table name for the persistent. */
     /** Table name for the persistent. */
Line 380: Line 378:
     }
     }
}
}
</code>
</syntaxhighlight>


=== More advanced ===
=== More advanced ===


<code php>
<syntaxhighlight lang="php">
namespace example;
namespace example;


Line 444: Line 442:
             $userid = $idorobject->id;
             $userid = $idorobject->id;
         }
         }
         $this->set('userid', $userid);
         $this->raw_set('userid', $userid);
     }
     }


Line 488: Line 486:


}
}
</code>
</syntaxhighlight>
 
== See also ==
 
* [[Persistent form]]
* [[Exporter]]

Latest revision as of 13:31, 14 July 2021

Moodle 3.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 their own data against automatic and custom validation rules. Persistents are made by extending the abstract class core\persistent.

Properties

Each class must define the properties that it contains. This can be done by defining the method protected static define_properties(). It returns an array where the keys are the names of the properties (and implies the database column names), and their attributes. The type of each property 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,
        )
    );
}

Properties attributes

type
The only mandatory attribute. It must be one of the many PARAM_* constants.
default
The default value to attach to the property when it hasn't been provided. Without a default, the property's value is required. Alternatively this can be a Closure returning the default value.
null
Either of the constants NULL_ALLOWED or NULL_NOT_ALLOWED determining if the null value is accepted. This defaults to NULL_NOT_ALLOWED.
message
The default error message (as a lang_string instance) to return when the validation of this field fails.
choices
An array of values which the property must fall in.
'messageformat' => array(
    'type' => PARAM_INT,
    'default' => FORMAT_PLAIN,
    'choices' => array(FORMAT_PLAIN, FORMAT_HTML, FORMAT_MOODLE, FORMAT_MARKDOWN)
),
'location' => array(
    'type' => PARAM_ALPHANUMEXT,
    'null' => NULL_ALLOWED,
    'message' => new lang_string('invaliddata', 'error'),
    'default' => function() {
        return get_config('core', 'default_location');
    },
),

Note that you should always use a Closure for the default value when you cannot guarantee that it will not change since the start of the request. The list of properties and their attributes is cached, and so failure to use a Closure can result in using an outdated default value.

Mandatory properties

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

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.

From Moodle 3.7 onwards, the XMLDB tool has an option to add these fields to your table.

Linking to a database table

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 it. This can be done by defining the class 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 name using the constant.

Reading property values

This can be done using the method get(). Alternatively you can also use to_record() which exports the whole object.

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

// Get the user ID using the generic getter.
$userid = $persistent->get('userid');

// Get all the properties in an stdClass.
$data = $persistent->to_record();

You may add a new custom getter to implement your own custom logic. To do this just add a new method get_propertyname() and it will be called from the generic get() method. For instance you could convert the data in another format automatically as a convenience for developers. However, use this sparingly as it may lead to confusion: what you get is not what is stored in the database. Also - to get the actual value from a custom getter - call raw_get(). Calling get() from a custom getter will generate a segfault due to infinite recursion.

It is, however, encouraged to add convenience methods such as the following:

/**
 * Returns the user object of the author.
 *
 * @return stdClass
 */
public function get_author() {
    return core_user::get_user($this->get('userid'));
}

Assigning values to properties

There are two ways to do so.

You use an object (stdClass) to assign a bunch of properties at once. Use it with the constructor, or the method from_record().

$data = (object) array('userid' => 2, 'message' => 'Hello world!');

// Instantiates a new object with value for some properties.
$persistent = new status(0, $data);

// Is similar to.
$persistent = new status();
$persistent->from_record($data);

Or, 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 a custom setter

Though you don't have to for the code to work, you can define your own setter methods which will be called from the generic setter. This is 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 raw_set() method to assign the values (if you do not you will see a segfault because of infinite recursion).

/**
 * 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->raw_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.

Just like custom getters, must be careful so that the following code should not result in a change in the DB.

$value = $persistent->get('property');
$persistent->set('property', $value);
$persistent->update();

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 object in the database.
$data = new stdClass();
$data->message = 'Hello new world';
$persistent = new persistent(0, $data);
$persistent->create();
// $persistent->get('id') will now return an id.

// 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();

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 from a user's 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;
}

If you need to join persistents together and be able to extract their respective properties in a single database query, you should have a look at the following methods:

get_sql_fields(string $alias, string $prefix = null)
Returns the SQL statement to include in the SELECT clause to prefix columns.
extract_record(stdClass $row, string $prefix = null)
Extracts all the properties from a row based on the given prefix.
// Minimalist example.
$sqlfields = status::get_sql_fields('s', 'statprops');
$sql = "SELECT $sqlfields, u.username
          FROM {" . status::TABLE . "} s
          JOIN {user} ON s.userid = u.id
         WHERE s.id = 1";
$row = $DB->get_record($sql, []);
$statusdata = status::extract_record($row, 'statprops');
$persistent = new status(0, $statusdata);

Validating

Basic validation of the properties values happens automatically based on their type (PARAM_* constant), however this is 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) {
    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_persistent_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.
}

// Check 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.

Hooks

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

protected before_validate()
Do something before the object is validated.
protected 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.
protected after_create()
Do something right after the object was added to the database.
protected 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.
protected after_update(bool $result)
Do something right after the object was updated in the database.
protected before_delete()
Do something right before the object is deleted from the database.
protected after_delete(bool $result)
Do something right after the object was deleted from the database.

Persistent and forms

If your persistents are exposed to your users via forms, head to the persistent form page.

Common pitfalls

  1. You must create the database table yourself, using the XMLDB editor and an upgrade script.
  2. You must include the mandatory fields in your table schema.
  3. You must never extend another persistent as this leads to unpredictable errors whenever the parent changes (new properties missing in your table, changes in validation, custom getters and setters, etc...).

Examples

Minimalist

<?php
class status extends \core\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,
            ),
        );
    }
}

More advanced

namespace example;

use core\persistent;
use core_user;
use lang_string;

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,
            ),
            'messageformat' => array(
                'type' => PARAM_INT,
                'default' => FORMAT_PLAIN,
                'choices' => array(FORMAT_PLAIN, FORMAT_HTML, FORMAT_MOODLE, FORMAT_MARKDOWN)
            ),
            'location' => array(
                'type' => PARAM_ALPHANUMEXT,
                'null' => NULL_ALLOWED,
                'message' => new lang_string('invaliddata', 'error'),
                'default' => function() {
                    return get_config('core', 'default_location');
                },
            ),
        );
    }

    /**
     * Returns the user object of the author.
     *
     * @return stdClass
     */
    public function get_author() {
        return core_user::get_user($this->get('userid'));
    }

    /**
     * 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->raw_set('userid', $userid);
    }

    /**
     * Validate the user ID.
     *
     * @param int $value The value.
     * @return true|lang_string
     */
    protected function validate_userid($value) {
        if (!core_user::is_real_user($value, true)) {
            return new lang_string('invaliduserid', 'error');
        }

        return true;
    }

    /**
     * Get all records from a user's 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;
    }

}

See also