diff --git a/lib/classes/persistent.php b/lib/classes/persistent.php index 16095d6bed3..e7b09a2f299 100644 --- a/lib/classes/persistent.php +++ b/lib/classes/persistent.php @@ -143,7 +143,18 @@ abstract class persistent { if (method_exists($this, $methodname)) { return $this->$methodname(); } - return $this->raw_get($property); + + $properties = static::properties_definition(); + // If property can be NULL and value is NULL it needs to return null. + if ($properties[$property]['null'] === NULL_ALLOWED && $this->raw_get($property) === null) { + return null; + } + // Deliberately cast boolean types as such, because clean_param will cast them to integer. + if ($properties[$property]['type'] === PARAM_BOOL) { + return (bool)$this->raw_get($property); + } + + return clean_param($this->raw_get($property), $properties[$property]['type']); } /** diff --git a/lib/tests/persistent_test.php b/lib/tests/persistent_test.php index dbddd3d03c4..2f62e195879 100644 --- a/lib/tests/persistent_test.php +++ b/lib/tests/persistent_test.php @@ -36,6 +36,7 @@ class core_persistent_testcase extends advanced_testcase { public function setUp(): void { $this->make_persistent_table(); + $this->make_second_persistent_table(); $this->resetAfterTest(); } @@ -69,6 +70,35 @@ class core_persistent_testcase extends advanced_testcase { $dbman->create_table($table); } + /** + * Make the second table for the persistent. + */ + protected function make_second_persistent_table() { + global $DB; + $dbman = $DB->get_manager(); + + $table = new xmldb_table(core_testable_second_persistent::TABLE); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('int', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('intnull', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('float', XMLDB_TYPE_FLOAT, '10', null, null, null, null); + $table->add_field('text', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('raw', XMLDB_TYPE_CHAR, '100', null, null, null, null); + $table->add_field('booltrue', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('boolfalse', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + + if ($dbman->table_exists($table)) { + $dbman->drop_table($table); + } + + $dbman->create_table($table); + } + public function test_properties_definition() { $expected = array( 'shortname' => array( @@ -442,6 +472,39 @@ class core_persistent_testcase extends advanced_testcase { $this->expectExceptionMessageMatches('/The alias .+ exceeds 30 characters/'); core_testable_persistent::get_sql_fields('c'); } + + public function test_get(): void { + $data = [ + 'int' => 123, + 'intnull' => null, + 'float' => 33.44, + 'text' => 'Hello', + 'raw' => '/dev/hello', + 'booltrue' => true, + 'boolfalse' => false, + ]; + $p = new core_testable_second_persistent(0, (object)$data); + $p->create(); + + $this->assertSame($data['intnull'], $p->get('intnull')); + $this->assertSame($data['int'], $p->get('int')); + $this->assertSame($data['float'], $p->get('float')); + $this->assertSame($data['text'], $p->get('text')); + $this->assertSame($data['raw'], $p->get('raw')); + $this->assertSame($data['booltrue'], $p->get('booltrue')); + $this->assertSame($data['boolfalse'], $p->get('boolfalse')); + + // Ensure that types are correct after reloading data from database. + $p->read(); + + $this->assertSame($data['int'], $p->get('int')); + $this->assertSame($data['intnull'], $p->get('intnull')); + $this->assertSame($data['float'], $p->get('float')); + $this->assertSame($data['text'], $p->get('text')); + $this->assertSame($data['raw'], $p->get('raw')); + $this->assertSame($data['booltrue'], $p->get('booltrue')); + $this->assertSame($data['boolfalse'], $p->get('boolfalse')); + } } /** @@ -544,3 +607,51 @@ class core_testable_persistent extends \core\persistent { } } + +/** + * Example persistent class to test types. + * + * @package core + * @copyright 2021 David Matamoros + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_testable_second_persistent extends \core\persistent { + + /** Table name for the persistent. */ + const TABLE = 'phpunit_second_persistent'; + + /** + * Return the list of properties. + * + * @return array + */ + protected static function define_properties(): array { + return [ + 'int' => [ + 'type' => PARAM_INT, + ], + 'intnull' => [ + 'type' => PARAM_INT, + 'null' => NULL_ALLOWED, + 'default' => null, + ], + 'float' => [ + 'type' => PARAM_FLOAT, + ], + 'text' => [ + 'type' => PARAM_TEXT, + 'default' => '' + ], + 'raw' => [ + 'type' => PARAM_RAW, + 'default' => '' + ], + 'booltrue' => [ + 'type' => PARAM_BOOL, + ], + 'boolfalse' => [ + 'type' => PARAM_BOOL, + ] + ]; + } +} diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 32eceb15a20..6bff46bb6d7 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -91,6 +91,7 @@ information provided here is intended especially for developers. completely removed from Moodle core too. * The SWF media player has been completely removed (The Flash Player was deprecated in 2017 and officially discontinued on 31 December 2020). +* The persistent method get() now returns the correct type for each property defined in the persistent class. === 3.11.2 === * For security reasons, filelib has been updated so all requests now use emulated redirects.