final public static function properties_definition() {
        global $CFG;

        static $def = null;
        if ($def !== null) {
            return $def;
        }

        $def = static::define_properties();
        $def['id'] = array(
            'default' => 0,
            'type' => PARAM_INT,
        );
        $def['timecreated'] = array(
            'default' => 0,
            'type' => PARAM_INT,
        );
        $def['timemodified'] = array(
            'default' => 0,
            'type' => PARAM_INT
        );
        $def['usermodified'] = array(
            'default' => 0,
            'type' => PARAM_INT
        );

        // List of reserved property names. Mostly because we have methods (getters/setters) which would confict with them.
        // Think about backwards compability before adding new ones here!
        $reserved = array('errors', 'formatted_properties', 'records', 'records_select', 'property_default_value',
            'property_error_message', 'sql_fields');

        foreach ($def as $property => $definition) {

            // Ensures that the null property is always set.
            if (!array_key_exists('null', $definition)) {
                $def[$property]['null'] = NULL_NOT_ALLOWED;
            }

            // Warn the developers when they are doing something wrong.
            if ($CFG->debugdeveloper) {
                if (!array_key_exists('type', $definition)) {
                    throw new coding_exception('Missing type for: ' . $property);

                } else if (isset($definition['message']) && !($definition['message'] instanceof lang_string)) {
                    throw new coding_exception('Invalid error message for: ' . $property);

                } else if (in_array($property, $reserved)) {
                    throw new coding_exception('This property cannot be defined: ' . $property);

                }
            }
        }

        return $def;
    }