Skip to content

Commit

Permalink
MDL-63658 core_favourites: adding paging support to the service layer
Browse files Browse the repository at this point in the history
  • Loading branch information
snake committed Oct 18, 2018
1 parent ac9138d commit 8ffbe9c
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 30 deletions.
24 changes: 14 additions & 10 deletions favourites/classes/local/repository/crud_repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -48,24 +48,28 @@ public function add_all(array $items) : array;
* 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.
Expand All @@ -85,10 +89,10 @@ public function count() : int;
/**
* 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.
Expand Down
16 changes: 10 additions & 6 deletions favourites/classes/local/repository/favourites_repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public function __construct() {
* @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;
Expand Down Expand Up @@ -102,23 +102,27 @@ public function find(int $id) : \stdClass {
* 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);
}

/**
Expand Down Expand Up @@ -165,7 +169,7 @@ public function exists(int $id) : bool {
* @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;
Expand Down
36 changes: 23 additions & 13 deletions favourites/classes/local/service/user_favourites_service.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -66,6 +55,17 @@ public function __construct(\context_user $usercontext, \core_favourites\local\r
$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.
*
Expand Down Expand Up @@ -105,14 +105,24 @@ public function create_favourite(string $component, string $itemtype, int $itemi
*
* @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
);
}

/**
Expand Down
88 changes: 88 additions & 0 deletions favourites/tests/repository_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,48 @@ public function test_find_all() {
}
}

/**
* 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.
*/
Expand Down Expand Up @@ -263,6 +305,52 @@ public function test_find_by() {
$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.
*/
Expand Down
38 changes: 37 additions & 1 deletion favourites/tests/service_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,19 @@ protected function get_mock_repository(array $mockstore) {
);
$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;
if (array_diff($criteria, $mockrowarr) == []) {
$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;
})
);
Expand Down Expand Up @@ -245,6 +250,37 @@ public function test_find_favourites_by_type_nonexistent_component() {
$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.
*/
Expand Down

0 comments on commit 8ffbe9c

Please sign in to comment.