final public function validate() {
        global $CFG;

        // Before validate hook.
        $this->before_validate();

        // If this object has not been validated yet.
        if ($this->validated !== true) {

            $errors = array();
            $properties = static::properties_definition();
            foreach ($properties as $property => $definition) {

                // Get the data, bypassing the potential custom getter which could alter the data.
                $value = $this->raw_get($property);

                // Check if the property is required.
                if ($value === null && static::is_property_required($property)) {
                    $errors[$property] = new lang_string('requiredelement', 'form');
                    continue;
                }

                // Check that type of value is respected.
                try {
                    if ($definition['type'] === PARAM_BOOL && $value === false) {
                        // Validate_param() does not like false with PARAM_BOOL, better to convert it to int.
                        $value = 0;
                    }
                    if ($definition['type'] === PARAM_CLEANHTML) {
                        // We silently clean for this type. It may introduce changes even to valid data.
                        $value = clean_param($value, PARAM_CLEANHTML);
                    }
                    validate_param($value, $definition['type'], $definition['null']);
                } catch (invalid_parameter_exception $e) {
                    $errors[$property] = static::get_property_error_message($property);
                    continue;
                }

                // Check that the value is part of a list of allowed values.
                if (isset($definition['choices']) && !in_array($value, $definition['choices'])) {
                    $errors[$property] = static::get_property_error_message($property);
                    continue;
                }

                // Call custom validation method.
                $method = 'validate_' . $property;
                if (method_exists($this, $method)) {

                    // Warn the developers when they are doing something wrong.
                    if ($CFG->debugdeveloper) {
                        $reflection = new ReflectionMethod($this, $method);
                        if (!$reflection->isProtected()) {
                            throw new coding_exception('The method ' . get_class($this) . '::'. $method . ' should be protected.');
                        }
                    }

                    $valid = $this->{$method}($value);
                    if ($valid !== true) {
                        if (!($valid instanceof lang_string)) {
                            throw new coding_exception('Unexpected error message.');
                        }
                        $errors[$property] = $valid;
                        continue;
                    }
                }
            }

            $this->validated = true;
            $this->errors = $errors;
        }

        return empty($this->errors) ? true : $this->errors;
    }