diff --git a/src/Plugin/formatter/Formatter.php b/src/Plugin/formatter/Formatter.php index ec1e7b3b..8d9a31fd 100644 --- a/src/Plugin/formatter/Formatter.php +++ b/src/Plugin/formatter/Formatter.php @@ -7,7 +7,6 @@ namespace Drupal\restful\Plugin\formatter; -use Doctrine\Common\Collections\ArrayCollection; use Drupal\Component\Plugin\PluginBase; use Drupal\restful\Exception\ServerConfigurationException; use Drupal\restful\Plugin\ConfigurablePluginTrait; @@ -17,8 +16,12 @@ use Drupal\restful\Plugin\resource\ResourceInterface; use Drupal\restful\RenderCache\Entity\CacheFragment; use Drupal\restful\RenderCache\RenderCache; -use Drupal\restful\RenderCache\RenderCacheInterface; +/** + * Class Formatter. + * + * @package Drupal\restful\Plugin\formatter + */ abstract class Formatter extends PluginBase implements FormatterInterface { use ConfigurablePluginTrait; @@ -207,7 +210,8 @@ protected function setCachedData($data, $output, array $parent_hashes = array()) * @param mixed $data * The data to be rendered. * - * @return RenderCacheInterface + * @return \Drupal\restful\RenderCache\RenderCacheInterface; + * The cache controller. */ protected function createCacheController($data) { @@ -234,7 +238,8 @@ protected function createCacheController($data) { * @param mixed $data * The data to be rendered. * - * @return ArrayCollection + * @return \Doctrine\Common\Collections\ArrayCollection; + * The cache controller. */ protected static function cacheFragments($data) { @@ -309,4 +314,22 @@ protected static function unprefixInputOptions($allowed_fields, $prefix) { return array_filter(array_map($closure_unprefix, $allowed_fields)); } + + /** + * Helper function that calculates the number of items per page. + * + * @param ResourceInterface $resource + * The associated resource. + * + * @return int + * The items per page. + */ + protected function calculateItemsPerPage(ResourceInterface $resource) { + $data_provider = $resource->getDataProvider(); + $max_range = $data_provider->getRange(); + $original_input = $resource->getRequest()->getParsedInput(); + $items_per_page = empty($original_input['range']) ? $max_range : $original_input['range']; + return $items_per_page > $max_range ? $max_range : $items_per_page; + } + } diff --git a/src/Plugin/formatter/FormatterHalJson.php b/src/Plugin/formatter/FormatterHalJson.php index 00d3acf8..1b21302f 100644 --- a/src/Plugin/formatter/FormatterHalJson.php +++ b/src/Plugin/formatter/FormatterHalJson.php @@ -121,7 +121,6 @@ protected function addHateoas(array &$data) { ); $input = $request->getParsedInput(); - $data_provider = $resource->getDataProvider(); $page = !empty($input['page']) ? $input['page'] : 1; if ($page > 1) { @@ -138,7 +137,7 @@ protected function addHateoas(array &$data) { // We know that there are more pages if the total count is bigger than the // number of items of the current request plus the number of items in // previous pages. - $items_per_page = $data_provider->getRange(); + $items_per_page = $this->calculateItemsPerPage($resource); $previous_items = ($page - 1) * $items_per_page; if (isset($data['count']) && $data['count'] > $listed_items + $previous_items) { $input['page'] = $page + 1; diff --git a/src/Plugin/formatter/FormatterJson.php b/src/Plugin/formatter/FormatterJson.php index fb599b5c..4af48dac 100644 --- a/src/Plugin/formatter/FormatterJson.php +++ b/src/Plugin/formatter/FormatterJson.php @@ -172,7 +172,7 @@ protected function addHateoas(array &$data) { // We know that there are more pages if the total count is bigger than the // number of items of the current request plus the number of items in // previous pages. - $items_per_page = $resource->getDataProvider()->getRange(); + $items_per_page = $this->calculateItemsPerPage($resource); $previous_items = ($page - 1) * $items_per_page; if (isset($data['count']) && $data['count'] > count($data['data']) + $previous_items) { $query = array('page' => $page + 1) + $input; diff --git a/src/Plugin/formatter/FormatterJsonApi.php b/src/Plugin/formatter/FormatterJsonApi.php index c5007c9e..e9f38eb0 100644 --- a/src/Plugin/formatter/FormatterJsonApi.php +++ b/src/Plugin/formatter/FormatterJsonApi.php @@ -198,13 +198,12 @@ protected function addHateoas(array &$data, ResourceInterface $resource = NULL, $options = $top_level ? array('query' => $input) : array(); $data['links']['self'] = $resource->versionedUrl($path, $options); - $data_provider = $resource->getDataProvider(); $page = !empty($input['page']) ? $input['page'] : 1; // We know that there are more pages if the total count is bigger than the // number of items of the current request plus the number of items in // previous pages. - $items_per_page = empty($original_input['range']) ? $data_provider->getRange() : $original_input['range']; + $items_per_page = $this->calculateItemsPerPage($resource); if (isset($data['meta']['count']) && $data['meta']['count'] > $items_per_page) { $num_pages = ceil($data['meta']['count'] / $items_per_page); unset($input['page']); diff --git a/tests/RestfulJsonApiTestCase.test b/tests/RestfulJsonApiTestCase.test index d0592810..472387b8 100644 --- a/tests/RestfulJsonApiTestCase.test +++ b/tests/RestfulJsonApiTestCase.test @@ -72,6 +72,41 @@ class RestfulJsonApiTestCase extends RestfulCurlBaseTestCase { $this->formatter->setConfiguration($configuration); } + /** + * Create test entities. + * + * @return Entity[] + * An array of entities. + */ + protected function createEntities() { + $entities = array(); + $entities[] = entity_create('entity_test', array( + 'name' => 'main', + 'uid' => $this->account->uid, + )); + $entities[0]->save(); + + $entities[] = entity_create('entity_test', array( + 'name' => 'main', + 'uid' => $this->account->uid, + )); + $entities[1]->save(); + + $entities[] = entity_create('entity_test', array( + 'name' => 'main', + 'uid' => $this->account->uid, + )); + + $entities[0]->entity_reference_single[LANGUAGE_NONE][] = array('target_id' => $entities[1]->pid); + $entities[0]->save(); + $entities[2]->entity_reference_single[LANGUAGE_NONE][] = array('target_id' => $entities[0]->pid); + $entities[2]->entity_reference_multiple[LANGUAGE_NONE][] = array('target_id' => $entities[0]->pid); + $entities[2]->entity_reference_multiple[LANGUAGE_NONE][] = array('target_id' => $entities[1]->pid); + $entities[2]->save(); + + return $entities; + } + /** * Test requesting resources. */ @@ -130,7 +165,7 @@ class RestfulJsonApiTestCase extends RestfulCurlBaseTestCase { // embedded entity if there is no subfield added. $this->handler->setRequest(Request::create('api/v1.1/main/' . $wrapper->getIdentifier(), array( 'include' => 'entity_reference_multiple_resource,entity_reference_single_resource', - 'fields' => 'entity_reference_multiple_resource,entity_reference_single_resource' + 'fields' => 'entity_reference_multiple_resource,entity_reference_single_resource', ))); $this->handler->setPath($wrapper->getIdentifier()); $this->formatter->setResource($this->handler); @@ -141,7 +176,7 @@ class RestfulJsonApiTestCase extends RestfulCurlBaseTestCase { // Now make sure that including fields only lists those fields. $this->handler->setRequest(Request::create('api/v1.1/main/' . $wrapper->getIdentifier(), array( 'include' => 'entity_reference_single_resource', - 'fields' => 'entity_reference_single_resource.label' + 'fields' => 'entity_reference_single_resource.label', ))); $this->handler->setPath($wrapper->getIdentifier()); $this->formatter->setResource($this->handler); @@ -401,7 +436,10 @@ class RestfulJsonApiTestCase extends RestfulCurlBaseTestCase { ); drupal_write_record('restful_test_db_query', $record); $handler = restful()->getResourceManager()->getPlugin('main:1.8'); - $handler->setRequest(Request::create('', array('include' => 'random_rel', 'range' => 1))); + $handler->setRequest(Request::create('', array( + 'include' => 'random_rel', + 'range' => 1, + ))); $handler->setPath(1); $formatter = restful()->getFormatterManager()->getPlugin('json_api'); $formatter->setResource($handler); @@ -437,14 +475,14 @@ class RestfulJsonApiTestCase extends RestfulCurlBaseTestCase { 'last' => $this->handler->versionedUrl('', array( 'query' => array( 'range' => $range, - 'page' => 5 - ) + 'page' => 5, + ), )), 'next' => $this->handler->versionedUrl('', array( 'query' => array( 'range' => $range, - 'page' => 2 - ) + 'page' => 2, + ), )), ); $this->assertEqual($links, $expected_links); @@ -453,7 +491,7 @@ class RestfulJsonApiTestCase extends RestfulCurlBaseTestCase { $range = 2; $this->handler->setRequest(Request::create('', array( 'range' => $range, - 'page' => 2 + 'page' => 2, ))); $this->handler->setPath(''); $this->formatter->setResource($this->handler); @@ -464,27 +502,27 @@ class RestfulJsonApiTestCase extends RestfulCurlBaseTestCase { 'self' => $this->handler->versionedUrl('', array( 'query' => array( 'range' => $range, - 'page' => 2 - ) + 'page' => 2, + ), )), 'first' => $this->handler->versionedUrl('', array('query' => array('range' => $range))), 'previous' => $this->handler->versionedUrl('', array( 'query' => array( 'range' => $range, - 'page' => 1 - ) + 'page' => 1, + ), )), 'last' => $this->handler->versionedUrl('', array( 'query' => array( 'range' => $range, - 'page' => 5 - ) + 'page' => 5, + ), )), 'next' => $this->handler->versionedUrl('', array( 'query' => array( 'range' => $range, - 'page' => 3 - ) + 'page' => 3, + ), )), ); $this->assertEqual($links, $expected_links); @@ -493,7 +531,7 @@ class RestfulJsonApiTestCase extends RestfulCurlBaseTestCase { $range = 2; $this->handler->setRequest(Request::create('', array( 'range' => $range, - 'page' => 5 + 'page' => 5, ))); $this->handler->setPath(''); $this->formatter->setResource($this->handler); @@ -504,21 +542,21 @@ class RestfulJsonApiTestCase extends RestfulCurlBaseTestCase { 'self' => $this->handler->versionedUrl('', array( 'query' => array( 'range' => $range, - 'page' => 5 - ) + 'page' => 5, + ), )), 'first' => $this->handler->versionedUrl('', array('query' => array('range' => $range))), 'previous' => $this->handler->versionedUrl('', array( 'query' => array( 'range' => $range, - 'page' => 4 - ) + 'page' => 4, + ), )), 'last' => $this->handler->versionedUrl('', array( 'query' => array( 'range' => $range, - 'page' => 5 - ) + 'page' => 5, + ), )), ); $this->assertEqual($links, $expected_links); @@ -537,8 +575,7 @@ class RestfulJsonApiTestCase extends RestfulCurlBaseTestCase { // Add files and taxonomy term references. $query = new EntityFieldQuery(); - $result = $query - ->entityCondition('entity_type', 'taxonomy_term') + $result = $query->entityCondition('entity_type', 'taxonomy_term') ->entityCondition('bundle', 'test_vocab') ->execute(); $tids = array_keys($result['taxonomy_term']); @@ -554,15 +591,15 @@ class RestfulJsonApiTestCase extends RestfulCurlBaseTestCase { $entities[2]->term_multiple[LANGUAGE_NONE][] = array('tid' => $tids[2]); $entities[2]->file_single[LANGUAGE_NONE][] = array( 'fid' => $images[0], - 'display' => TRUE + 'display' => TRUE, ); $entities[2]->file_multiple[LANGUAGE_NONE][] = array( 'fid' => $images[1], - 'display' => TRUE + 'display' => TRUE, ); $entities[2]->file_multiple[LANGUAGE_NONE][] = array( 'fid' => $images[2], - 'display' => TRUE + 'display' => TRUE, ); entity_save('entity_test', $entities[2]); @@ -600,8 +637,7 @@ class RestfulJsonApiTestCase extends RestfulCurlBaseTestCase { ))); $handler->setPath($entities[2]->uuid); $format_manager->setResource($handler); - $response = $format_manager - ->negotiateFormatter(NULL, 'json_api') + $response = $format_manager->negotiateFormatter(NULL, 'json_api') ->prepare($handler->process()); $result = $response['data']['attributes']; @@ -765,39 +801,4 @@ class RestfulJsonApiTestCase extends RestfulCurlBaseTestCase { $this->assertEqual($expected, $parsed_body); } - /** - * Create test entities. - * - * @return Entity[] - * An array of entities. - */ - protected function createEntities() { - $entities = array(); - $entities[] = entity_create('entity_test', array( - 'name' => 'main', - 'uid' => $this->account->uid, - )); - $entities[0]->save(); - - $entities[] = entity_create('entity_test', array( - 'name' => 'main', - 'uid' => $this->account->uid, - )); - $entities[1]->save(); - - $entities[] = entity_create('entity_test', array( - 'name' => 'main', - 'uid' => $this->account->uid, - )); - - $entities[0]->entity_reference_single[LANGUAGE_NONE][] = array('target_id' => $entities[1]->pid); - $entities[0]->save(); - $entities[2]->entity_reference_single[LANGUAGE_NONE][] = array('target_id' => $entities[0]->pid); - $entities[2]->entity_reference_multiple[LANGUAGE_NONE][] = array('target_id' => $entities[0]->pid); - $entities[2]->entity_reference_multiple[LANGUAGE_NONE][] = array('target_id' => $entities[1]->pid); - $entities[2]->save(); - - return $entities; - } - } diff --git a/tests/RestfulSimpleJsonTestCase.test b/tests/RestfulSimpleJsonTestCase.test index 07fd3810..42ee4c11 100644 --- a/tests/RestfulSimpleJsonTestCase.test +++ b/tests/RestfulSimpleJsonTestCase.test @@ -37,7 +37,7 @@ class RestfulSimpleJsonTestCase extends RestfulCurlBaseTestCase { $plugin_definition['formatter'] = 'json'; $handler->setPluginDefinition($plugin_definition); - $handler->setRequest(Request::create('api/v1.5/articles/' . $node->nid)); + $handler->setRequest(Request::create('/api/v1.5/articles/' . $node->nid)); $handler->setPath($node->nid); $response = drupal_json_decode(restful() ->getFormatterManager() @@ -67,20 +67,52 @@ class RestfulSimpleJsonTestCase extends RestfulCurlBaseTestCase { // Assert the HATEOAS. // Create another node for pagination. restful_test_create_node_with_tags(); + // Change the max range. $handler->getDataProvider()->setRange(1); $handler->setRequest(Request::create('')); $handler->setPath(''); - $response = drupal_json_decode(restful() - ->getFormatterManager() - ->format($handler->process(), 'json')); - $response = $response['data']; - $result = $formatter->format($response); - $decoded_json = drupal_json_decode($result); + $results = $handler->process(); + $formatter = restful()->getFormatterManager()->getPlugin('json'); + $formatter->setResource($handler); + $data = $formatter->prepare($results); + + $this->assertNotNull($data['self'], '"Self" property added.'); + $this->assertEqual($data['count'], 2, 'Count was populated correctly.'); + $this->assertEqual(count($data['data']), 1, 'The correct number of items was listed.'); + $this->assertNotNull($data['next'], '"Next" property added.'); + + // Test the pagination links for bigger ranges. + // Create an extra node for more pagination options. + restful_test_create_node_with_tags(); + $handler->getDataProvider()->setRange(50); + $handler->setRequest(Request::create('', array( + 'range' => 1, + 'page' => 2, + ))); + $handler->setPath(''); + $results = $handler->process(); + $formatter = restful()->getFormatterManager()->getPlugin('json'); + $formatter->setResource($handler); + $data = $formatter->prepare($results); + + $this->assertEqual($data['count'], 3, 'Count was populated correctly.'); + $this->assertEqual(count($data['data']), 1, 'The correct number of items was listed.'); + $this->assertNotNull($data['next'], '"Next" property added.'); + $this->assertNotNull($data['previous'], '"Previous" property added.'); + + // Test the max cap for the range. + $handler->getDataProvider()->setRange(1); + $handler->setRequest(Request::create('/api/v1.5/articles', array( + 'range' => 1000, + 'page' => 2, + ))); + $handler->setPath(''); + $results = $handler->process(); + $formatter = restful()->getFormatterManager()->getPlugin('json'); + $formatter->setResource($handler); + $data = $formatter->prepare($results); - $this->assertNotNull($decoded_json['self'], '"Self" property added.'); - $this->assertEqual($decoded_json['count'], 2, 'Count was populated correctly.'); - $this->assertEqual(count($decoded_json['data']), 1, 'The correct number of items was listed.'); - $this->assertNotNull($decoded_json['next'], '"Next" property added.'); + $this->assertNotNull($data['next'], '"Next" property added.'); } }