MDL-63658 core_favourites: adding paging support to the service layer
authorJake Dallimore <jake@moodle.com>
Tue, 9 Oct 2018 03:10:21 +0000 (11:10 +0800)
committerJake Dallimore <jake@moodle.com>
Thu, 18 Oct 2018 00:22:07 +0000 (08:22 +0800)
favourites/classes/local/repository/crud_repository.php
favourites/classes/local/repository/favourites_repository.php
favourites/classes/local/service/user_favourites_service.php
favourites/tests/repository_test.php
favourites/tests/service_test.php

index 503a321..3a515d9 100644 (file)
@@ -31,10 +31,10 @@ interface crud_repository {
     /**
      * Add one item to this repository.
      *
-     * @param \stdClass $item the item to add.
-     * @return \stdClass the item which was added.
+     * @param object $item the item to add.
+     * @return object the item which was added.
      */
-    public function add(\stdClass $item) : \stdClass;
+    public function add($item);
 
     /**
      * Add all the items in the list to this repository.
@@ -48,24 +48,28 @@ interface crud_repository {
      * Find an item in this repository based on its id.
      *
      * @param int $id the id of the item.
-     * @return \stdClass the item.
+     * @return object the item.
      */
-    public function find(int $id) : \stdClass;
+    public function find(int $id);
 
     /**
      * Find all items in this repository.
      *
+     * @param int $limitfrom optional pagination control for returning a subset of records, starting at this point.
+     * @param int $limitnum optional pagination control for returning a subset comprising this many records.
      * @return array list of all items in this repository.
      */
-    public function find_all() : array;
+    public function find_all(int $limitfrom = 0, int $limitnum = 0) : array;
 
     /**
      * Find all items with attributes matching certain values.
      *
      * @param array $criteria the array of attribute/value pairs.
+     * @param int $limitfrom optional pagination control for returning a subset of records, starting at this point.
+     * @param int $limitnum optional pagination control for returning a subset comprising this many records.
      * @return array the list of items matching the criteria.
      */
-    public function find_by(array $criteria) : array;
+    public function find_by(array $criteria, int $limitfrom = 0, int $limitnum = 0) : array;
 
     /**
      * Check whether an item exists in this repository, based on its id.
@@ -85,10 +89,10 @@ interface crud_repository {
     /**
      * Update an item within this repository.
      *
-     * @param \stdClass $item the item to update.
-     * @return \stdClass the updated item.
+     * @param object $item the item to update.
+     * @return object the updated item.
      */
-    public function update(\stdClass $item) : \stdClass;
+    public function update($item);
 
     /**
      * Delete an item by id.
index 799b8e9..9e0fbdb 100644 (file)
@@ -53,7 +53,7 @@ class favourites_repository implements ifavourites_repository {
      * @throws \dml_exception if any database errors are encountered.
      * @throws \moodle_exception if the favourite has missing or invalid properties.
      */
-    public function add(\stdClass $favourite) : \stdClass {
+    public function add($favourite) : \stdClass {
         global $DB;
         $this->validate($favourite);
         $favourite = (array)$favourite;
@@ -102,23 +102,27 @@ class favourites_repository implements ifavourites_repository {
      * Return all items matching the supplied criteria (a [key => value,..] list).
      *
      * @param array $criteria the list of key/value criteria pairs.
+     * @param int $limitfrom optional pagination control for returning a subset of records, starting at this point.
+     * @param int $limitnum optional pagination control for returning a subset comprising this many records.
      * @return array the list of favourites matching the criteria.
      * @throws \dml_exception if any database errors are encountered.
      */
-    public function find_by(array $criteria) : array {
+    public function find_by(array $criteria, int $limitfrom = 0, int $limitnum = 0) : array {
         global $DB;
-        return $DB->get_records($this->favouritetable, $criteria);
+        return $DB->get_records($this->favouritetable, $criteria, '', '*', $limitfrom, $limitnum);
     }
 
     /**
      * Return all items in this repository, as an array, indexed by id.
      *
+     * @param int $limitfrom optional pagination control for returning a subset of records, starting at this point.
+     * @param int $limitnum optional pagination control for returning a subset comprising this many records.
      * @return array the list of all favourites stored within this repository.
      * @throws \dml_exception if any database errors are encountered.
      */
-    public function find_all() : array {
+    public function find_all(int $limitfrom = 0, int $limitnum = 0) : array {
         global $DB;
-        return $DB->get_records($this->favouritetable);
+        return $DB->get_records($this->favouritetable, null, '', '*', $limitfrom, $limitnum);
     }
 
     /**
@@ -165,7 +169,7 @@ class favourites_repository implements ifavourites_repository {
      * @return \stdClass the updated favourite.
      * @throws \dml_exception if any database errors are encountered.
      */
-    public function update(\stdClass $favourite) : \stdClass {
+    public function update($favourite) : \stdClass {
         global $DB;
         $time = time();
         $favourite->timemodified = $time;
index 37da623..c9026aa 100644 (file)
@@ -44,17 +44,6 @@ class user_favourites_service {
     /** @var int $userid the id of the user to which this favourites service is scoped. */
     protected $userid;
 
-    /**
-     * Helper, returning a flat list of component names.
-     *
-     * @return array the array of component names.
-     */
-    protected function get_component_list() {
-        return array_keys(array_reduce(\core_component::get_component_list(), function($carry, $item) {
-            return array_merge($carry, $item);
-        }, []));
-    }
-
     /**
      * The user_favourites_service constructor.
      *
@@ -66,6 +55,17 @@ class user_favourites_service {
         $this->userid = $usercontext->instanceid;
     }
 
+    /**
+     * Helper, returning a flat list of component names.
+     *
+     * @return array the array of component names.
+     */
+    protected function get_component_list() {
+        return array_keys(array_reduce(\core_component::get_component_list(), function($carry, $item) {
+            return array_merge($carry, $item);
+        }, []));
+    }
+
     /**
      * Favourite an item defined by itemid/context, in the area defined by component/itemtype.
      *
@@ -105,14 +105,24 @@ class user_favourites_service {
      *
      * @param string $component the frankenstyle component name.
      * @param string $itemtype the type of the favourited item.
+     * @param int $limitfrom optional pagination control for returning a subset of records, starting at this point.
+     * @param int $limitnum optional pagination control for returning a subset comprising this many records.
      * @return array the list of favourites found.
      * @throws \moodle_exception if the component name is invalid, or if the repository encounters any errors.
      */
-    public function find_favourites_by_type(string $component, string $itemtype) : array {
+    public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array {
         if (!in_array($component, $this->get_component_list())) {
             throw new \moodle_exception("Invalid component name '$component'");
         }
-        return $this->repo->find_by(['userid' => $this->userid, 'component' => $component, 'itemtype' => $itemtype]);
+        return $this->repo->find_by(
+            [
+                'userid' => $this->userid,
+                'component' => $component,
+                'itemtype' => $itemtype
+            ],
+            $limitfrom,
+            $limitnum
+        );
     }
 
     /**
index e815cf9..1910931 100644 (file)
@@ -235,6 +235,48 @@ class favourites_repository_testcase extends advanced_testcase {
         }
     }
 
+    /**
+     * Testing the pagination of the find_all method.
+     */
+    public function test_find_all_pagination() {
+        list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
+
+        $favouritesrepo = new favourites_repository($user1context);
+
+        // Verify that for an empty repository, find_all with any combination of page options returns an empty array.
+        $this->assertEquals([], $favouritesrepo->find_all(0, 0));
+        $this->assertEquals([], $favouritesrepo->find_all(0, 10));
+        $this->assertEquals([], $favouritesrepo->find_all(1, 0));
+        $this->assertEquals([], $favouritesrepo->find_all(1, 10));
+
+        // Save 10 arbitrary favourites to the repo.
+        foreach (range(1, 10) as $i) {
+            $favourite = (object) [
+                'userid' => $user1context->instanceid,
+                'component' => 'core_course',
+                'itemtype' => 'course',
+                'itemid' => $i,
+                'contextid' => $course1context->id
+            ];
+            $favouritesrepo->add($favourite);
+        }
+
+        // Verify we have 10 favourites.
+        $this->assertEquals(10, $favouritesrepo->count());
+
+        // Verify we can fetch the first page of 5 records.
+        $favourites = $favouritesrepo->find_all(0, 5);
+        $this->assertCount(5, $favourites);
+
+        // Verify we can fetch the second page.
+        $favourites = $favouritesrepo->find_all(5, 5);
+        $this->assertCount(5, $favourites);
+
+        // Verify the third page request ends with an empty array.
+        $favourites = $favouritesrepo->find_all(10, 5);
+        $this->assertCount(0, $favourites);
+    }
+
     /**
      * Test retrieval of a user's favourites for a given criteria, in this case, area.
      */
@@ -263,6 +305,52 @@ class favourites_repository_testcase extends advanced_testcase {
         $this->assertCount(0, $userfavourites);
     }
 
+    /**
+     * Testing the pagination of the find_by method.
+     */
+    public function test_find_by_pagination() {
+        list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
+
+        $favouritesrepo = new favourites_repository($user1context);
+
+        // Verify that for an empty repository, find_all with any combination of page options returns an empty array.
+        $this->assertEquals([], $favouritesrepo->find_by([], 0, 0));
+        $this->assertEquals([], $favouritesrepo->find_by([], 0, 10));
+        $this->assertEquals([], $favouritesrepo->find_by([], 1, 0));
+        $this->assertEquals([], $favouritesrepo->find_by([], 1, 10));
+
+        // Save 10 arbitrary favourites to the repo.
+        foreach (range(1, 10) as $i) {
+            $favourite = (object) [
+                'userid' => $user1context->instanceid,
+                'component' => 'core_course',
+                'itemtype' => 'course',
+                'itemid' => $i,
+                'contextid' => $course1context->id
+            ];
+            $favouritesrepo->add($favourite);
+        }
+
+        // Verify we have 10 favourites.
+        $this->assertEquals(10, $favouritesrepo->count());
+
+        // Verify a request for a page, when no criteria match, results in an empty array.
+        $favourites = $favouritesrepo->find_by(['component' => 'core_message'], 0, 5);
+        $this->assertCount(0, $favourites);
+
+        // Verify we can fetch a the first page of 5 records.
+        $favourites = $favouritesrepo->find_by(['component' => 'core_course'], 0, 5);
+        $this->assertCount(5, $favourites);
+
+        // Verify we can fetch the second page.
+        $favourites = $favouritesrepo->find_by(['component' => 'core_course'], 5, 5);
+        $this->assertCount(5, $favourites);
+
+        // Verify the third page request ends with an empty array.
+        $favourites = $favouritesrepo->find_by(['component' => 'core_course'], 10, 5);
+        $this->assertCount(0, $favourites);
+    }
+
     /**
      * Test the count_by() method.
      */
index 60f8fab..d02b353 100644 (file)
@@ -84,7 +84,7 @@ class user_favourites_service_testcase extends advanced_testcase {
         );
         $mockrepo->expects($this->any())
             ->method('find_by')
-            ->will($this->returnCallback(function(array $criteria) use (&$mockstore) {
+            ->will($this->returnCallback(function(array $criteria, int $limitfrom = 0, int $limitnum = 0) use (&$mockstore) {
                 // Check the mockstore for all objects with properties matching the key => val pairs in $criteria.
                 foreach ($mockstore as $index => $mockrow) {
                     $mockrowarr = (array)$mockrow;
@@ -92,6 +92,11 @@ class user_favourites_service_testcase extends advanced_testcase {
                         $returns[$index] = $mockrow;
                     }
                 }
+                // Return a subset of the records, according to the paging options, if set.
+                if ($limitnum != 0) {
+                    return array_slice($returns, $limitfrom, $limitnum);
+                }
+                // Otherwise, just return the full set.
                 return $returns;
             })
         );
@@ -245,6 +250,37 @@ class user_favourites_service_testcase extends advanced_testcase {
         $service->find_favourites_by_type('cccore_notreal', 'something');
     }
 
+    /**
+     * Test confirming the pagination support for the find_favourites_by_type() method.
+     */
+    public function test_find_favourites_by_type_pagination() {
+        list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
+
+        // Get a user_favourites_service for the user.
+        $repo = $this->get_mock_repository([]);
+        $service = new \core_favourites\local\service\user_favourites_service($user1context, $repo);
+
+        // Favourite 10 arbitrary items.
+        foreach (range(1, 10) as $i) {
+            $service->create_favourite('core_course', 'course', $i, $course1context);
+        }
+
+        // Verify we have 10 favourites.
+        $this->assertCount(10, $service->find_favourites_by_type('core_course', 'course'));
+
+        // Verify we get back 5 favourites for page 1.
+        $favourites = $service->find_favourites_by_type('core_course', 'course', 0, 5);
+        $this->assertCount(5, $favourites);
+
+        // Verify we get back 5 favourites for page 2.
+        $favourites = $service->find_favourites_by_type('core_course', 'course', 5, 5);
+        $this->assertCount(5, $favourites);
+
+        // Verify we get back an empty array if querying page 3.
+        $favourites = $service->find_favourites_by_type('core_course', 'course', 10, 5);
+        $this->assertCount(0, $favourites);
+    }
+
     /**
      * Test confirming the basic deletion behaviour.
      */