The #2942426: [DISCUSSION] Remove query counting and collection counts? explains that without a JSON:API Extras it's *almost* impossible to get a total number of items for a collection request within meta.count. This caused by the "static" code of Drupal\jsonapi\ResourceType\ResourceType::includeCount():

public function includeCount() {
  // By default, do not return counts in collection queries.
  return FALSE;
}

Proposed resolution

  1. Add string|null $totalCountMetaName to the Drupal\jsonapi\ResourceType\ResourceType.
  2. Add string|null $total_count_meta_name = NULL as the argument to Drupal\jsonapi\ResourceType\ResourceType::__construct() and assign it to $this->totalCountMetaName.
  3. Add logic to Drupal\jsonapi\ResourceType\ResourceType::includeCount() and return $this->totalCountMetaName !== NULL, so the total count is included for collection responses if the meta name is provided.
  4. Introduce Drupal\jsonapi\ResourceType\ResourceType::getTotalCountMetaName(): ?string that returns $this->totalCountMetaName or 'count' if the Drupal\jsonapi\ResourceType\ResourceType::includeCount() method is overridden.
  5. In the Drupal\jsonapi\Controller\EntityResource::respondWithCollection() use $meta[$resource_type->getTotalCountMetaName()] instead of $meta['count'].
  6. In the Drupal\jsonapi\ResourceType\ResourceTypeBuildEvent, introduce string|null $totalCountMetaName, setTotalCountMetaName(?string $name): void, and getTotalCountMetaName(): ?string that will allow changing the resource definition at a build time.
  7. In the Drupal\jsonapi\ResourceType\ResourceTypeRepository::createResourceType(), query $event->getTotalCountMetaName() for non-internal resource types and pass the value to new ResourceType.
CommentFileSizeAuthor
#98 3104408-98--9.4.x.patch17.57 KBbr0ken
#78 interdiff_76-78.txt2.67 KBimmaculatexavier
#78 3104408-78.patch16.32 KBimmaculatexavier
#76 interdiff_74-76.txt2.69 KBimmaculatexavier
#76 3104408-76.patch19.06 KBimmaculatexavier
#74 interdiff_72-74.txt1005 bytesimmaculatexavier
#74 3104408-74.patch16.32 KBimmaculatexavier
#72 interdiff_70-72.txt1.62 KBimmaculatexavier
#72 3104408-72.patch16.24 KBimmaculatexavier
#70 reroll_diff_50-70.txt10.49 KBimmaculatexavier
#70 3104408-70.patch15.93 KBimmaculatexavier
#50 3104408-50.patch15.89 KBbr0ken
#50 interdiff-3104408-48-50.txt1.89 KBbr0ken
#48 interdiff-3104408-46-48.txt1.88 KBbr0ken
#48 3104408-48.patch14.41 KBbr0ken
#46 3104408-46.patch14.65 KBbr0ken
#46 interdiff-3104408-43-46.txt1.86 KBbr0ken
#43 interdiff-3104408-40-43.txt1.12 KBbr0ken
#43 3104408-43.patch14.68 KBbr0ken
#40 3104408-40.patch14.64 KBbr0ken
#40 interdiff-3104408-39-40.patch4.11 KBbr0ken
#39 interdiff-3104408-34-39.patch15.48 KBbr0ken
#39 3104408-39.patch13.91 KBbr0ken
#34 interdiff-3104408-33-34.txt573 bytesbr0ken
#34 3104408-34.patch10.89 KBbr0ken
#33 3104408-33.patch10.89 KBbr0ken
#33 interdiff-3104408-32-33.txt1.7 KBbr0ken
#32 interdiff-3104408-30-32.txt2.15 KBbr0ken
#32 3104408-32.patch10.85 KBbr0ken
#30 3104408-30.patch10.61 KBbr0ken
#30 interdiff-3104408-29-30.txt912 bytesbr0ken
#29 interdiff-3104408-25-29.txt3.99 KBbr0ken
#29 3104408-29.patch11.24 KBbr0ken
#25 3104408-25.patch14.28 KBbr0ken
#25 interdiff-3104408-22-25.txt807 bytesbr0ken
#24 interdiff-3104408-16-22.txt10.44 KBbr0ken
#24 3104408-22.patch14.29 KBbr0ken
#16 3104408-16--9.1.x.patch11.46 KBbr0ken
#11 Screenshot 2020-05-28 at 17.57.54.png91.12 KBbr0ken
#6 3104408-6--9.0.x.patch11.5 KBbr0ken
#5 3104408-5.patch11.44 KBbr0ken
#4 interdiff-3104408-2-3.txt5.33 KBbr0ken
#3 interdiff-3104408-2-3.txt7.09 KBbr0ken
#3 3104408-3.patch11.45 KBbr0ken
#2 3104408-2.patch4.66 KBbr0ken

Issue fork drupal-3104408

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

BR0kEN created an issue. See original summary.

br0ken’s picture

Status: Active » Needs review
StatusFileSize
new4.66 KB

I doubt this request will be accepted since maintainers considering a removal of the feature however this issue might become an argument since there are people using this functionality.

br0ken’s picture

Issue tags: -Needs tests
StatusFileSize
new11.45 KB
new7.09 KB
br0ken’s picture

StatusFileSize
new5.33 KB
br0ken’s picture

Version: 8.8.x-dev » 8.9.x-dev
StatusFileSize
new11.44 KB

Re-roll for 8.9.x.

br0ken’s picture

StatusFileSize
new11.5 KB

+ re-roll for 9.0.x

gabesullice’s picture

I doubt this request will be accepted since maintainers considering a removal of the feature

I'm not vehemently opposed to this proposal.

My opposition to the total count was entirely centered on the fact that framing the number as a "total count" is misleading and would become a source of bug reports.

However, I think I see a compromise position that I never saw before and now it seems glaringly obvious...

What if we rename /meta/count to /meta/dataMax?

I think that rename would express that the given number is an upper bound on the number of resources in a collection, not a total while providing the same information as before. I think we would need a BC-layer for the change, but the event that you've introduced could be the perfect place for an "opt-in". WDYT @BR0kEN?

gabesullice’s picture

Oh, and as for the patch, I'm very happy about moving to an explicit API that uses the ResourceTypeBuildEvent instead of using the ResourceTypeRepository hack :)

gabesullice’s picture

Status: Needs review » Active
jordandukart’s picture

I'd be a +1 towards supporting something like this as opposed to DI-ing or relying on JSON:API Extras.

br0ken’s picture

StatusFileSize
new91.12 KB

Never thought that count is an inaccurate word for expression the total number of items. I'd propose the dataTotal or itemsTotal.

We can also make this word configurable via the same event so by default it'll stay count until the next major release. In case of changing the wording should we also renamed methods/properties like includeCount()?

Another issue right now is that the meta/count right now is not a number but string.
API /meta/count is a string

gabesullice’s picture

Never thought that count is an inaccurate word for expression the total number of items.

When you have access controls and you request /jsonapi/node/article?page[limit]=10 you may only get 5 resource objects in the response. Similarly, the total number of articles on your site may be 500, but if you actually request every page of the articles collection and count the resource objects in every response, you may only get 250 or even none at all. Thus 500 is an upper bound on the number of resource objects you might receive but not a total count of every item in the collection.

br0ken’s picture

Probably that is relevant to my English knowledge but I don't see how "count" doesn't fit what you described. I understand the reasoning, and "count" seems logical because it says "here is the count of items that matches current filtering criteria".

jordandukart’s picture

If I'm interpreting @gabesullice's point correctly is that "count" is potentially misleading for people who may not be fully ingrained through the various discussion threads (https://www.drupal.org/project/drupal/issues/2942426, https://www.drupal.org/project/jsonapi/issues/2932625 and https://www.drupal.org/project/drupal/issues/2942426 for examples) or RTFMing the pagination documentation.

That is someone may assume that "count" is a reliable max as opposed to the theoretical amount of items available (assuming all access checks have passed). Since I don't imagine https://www.drupal.org/project/drupal/issues/2979294 will be seeing love any time soon I think the approach detailed here is sound beyond some semantic naming. I'll bring this up on the API-First call to try and get some more opinions.

br0ken’s picture

Dunno, `count` is still a valid name for me.

To be honest, I don't care be it `dataMax` or `numberOfItemsSatysfyingCurrentUserAccessLevelAndFilters`. Now we came to one of two most difficult things in coding - choosing the name for a *thing* (second is cache invalidation). I really hope this won't be blocking this feature for too long. I propose the maintainers to choose the name and tell me whether the code like `includeCount` properties/methods should be renamed too and I will do another patch.

Edit: can't wait for the day when D.O. will start recognizing markdown.

br0ken’s picture

Version: 8.9.x-dev » 9.1.x-dev
Assigned: br0ken » Unassigned
StatusFileSize
new11.46 KB
br0ken’s picture

Status: Active » Needs review
bbrala’s picture

Status: Needs review » Needs work

Hi BR0kEN,

I love this change, code seems good. Could you change count to dataMax and then we should be ok.

Also I think we should add a follow up issue to allow settings this value in the ResourceTypeBuildEvent so we expose it in an easy way.

Still getting the hang on when a changerecord is required, but since this is all internal i assume it is not needed. We'll see :)

br0ken’s picture

Wouldn't it be logical to rename the property to `dataMax` and the method to `provideDataMax()`? Tbh, I really don't understand the hassle with `count`, to which everyone got used to. IMO, this `dataMax` will only bring confusion to people. I've never seen such a name for this thing.

bbrala’s picture

Sure, yeah, being constistent in the naming is a good idea.

The thing about using count is what @gabesullice already mentioned. Even though it is extremely silly and weird, as soon as any resources are not allowed for the user doing the request there can be gaps in the amount of resources. The fact is you could request, get a count of 1000 and only get 1 result in your first page or even in everything.

Personally i think this is the weirdest thing ever in jsonapi, but unfortunately this is the reality.

If we name it count, people will assume a count of objects, this will bring us problems later. Hopefully we can fix this in the future, and then we could use count. This will take a while though.

br0ken’s picture

Version: 9.1.x-dev » 9.2.x-dev

I started changing everything to this dataMax weirdy and found that Drupal\jsonapi\JsonApiResource\Data uses count and getTotalCount(). There is also a use of $link_context['total_count']. Would it make sense to use totalCount instead of the dataMax?

soul88’s picture

totalCount sounds better to me than dataMax.

bbrala’s picture

Hi @BR0kEN, i'll have a focussed look at this friday.

br0ken’s picture

Status: Needs work » Needs review
StatusFileSize
new14.29 KB
new10.44 KB

Okey-dokey, this one brings a bit of complexity but allows everyone to change the name they want to use. Of course, we'll need a CR for this and a better description in docs but for the moment I post just a patch to get the feedback.

br0ken’s picture

StatusFileSize
new807 bytes
new14.28 KB

Correct a mistake that will cause the totalCount to be always present if not explicitly disabled.

With this patch, my frontend implementations that rely on count can stay unchanged and only backend will be affected by changing $event->setIncludeCount(TRUE) for $event->setTotalCountMetaName('count').

bbrala’s picture

I actually like the count naming, but it does mean some issues there where changes have been made already. What would happen right now to sites where TRUE is the return value there through customization, if this means requests break i think we might have a problem getting this in.

Also the patch is currently targetting 9.2.x, think this should be 9.3.x

  1. +++ b/core/modules/jsonapi/src/Controller/EntityResource.php
    @@ -1038,7 +1038,7 @@ protected function respondWithCollection(ResourceObjectData $primary_data, Data
    -      $link_context['total_count'] = $meta['count'] = $primary_data->getTotalCount();
    +      $link_context['total_count'] = $meta[$resource_type->getTotalCountMetaName()] = $primary_data->getTotalCount();
    

    This change is tricky. If in installations or contrib includeCount is enabled this would then change the name and break those sites. Not entirely sure how we should handle this, and if we can do this. This will at least need some explaination in an changerecord to communicatie this change.

    Really do think we should investigate supporting the case where this would return true through a site/contrib change and how that would "break".

  2. +++ b/core/modules/jsonapi/src/ResourceType/ResourceType.php
    @@ -249,14 +258,26 @@ public function isFieldEnabled($field_name) {
    +  public function includeCount(): bool {
    

    I wouldn't change this signature in the patch.

  3. +++ b/core/modules/jsonapi/src/ResourceType/ResourceType.php
    @@ -249,14 +258,26 @@ public function isFieldEnabled($field_name) {
    +  public function getTotalCountMetaName(): ?string {
    

    I like we can do this now.

  4. +++ b/core/modules/jsonapi/src/ResourceType/ResourceType.php
    @@ -338,8 +359,11 @@ public function isVersionable() {
    +  public function __construct($entity_type_id, $bundle, $deserialization_target_class, $internal = FALSE, $is_locatable = TRUE, $is_mutable = TRUE, $is_versionable = FALSE, array $fields = [], string $total_count_meta_name = NULL) {
    

    This will either break #3105318: Add a public API for aliasing resource type names or be broken after that is merged. Something to look out for.

  5. +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php
    @@ -167,7 +169,8 @@ protected function createResourceType(EntityTypeInterface $entity_type, $bundle)
    diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_collection_count/jsonapi_test_collection_count.info.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_collection_count/jsonapi_test_collection_count.info.yml
    

    This is the test that kinda simulated how in contrib things are handled. Mostly jsonapi_extras though, which i also maintain.

  6. +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/src/EventSubscriber/ResourceTypeBuildEventSubscriber.php
    @@ -26,6 +27,17 @@ public static function getSubscribedEvents() {
    diff --git a/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.php b/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.php
    
    +++ b/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.php
    @@ -391,12 +391,6 @@ public function testRead() {
    -    $collection_output = Json::decode($this->drupalGet('/jsonapi/node/article'));
    
    @@ -515,6 +509,48 @@ public function testRead() {
    +    $get_data = function () {
    ...
    +      $this->assertSession()->statusCodeEquals(200);
    

    A get_data function that also asserts status code? Seems inconsistent naming. I think just doing a decode as in other parts of this testfile is better.

bbrala’s picture

Status: Needs review » Needs work
br0ken’s picture

Version: 9.2.x-dev » 9.3.x-dev
br0ken’s picture

Status: Needs work » Needs review
StatusFileSize
new11.24 KB
new3.99 KB
  1. Handled by checking whether the includeCount() is overridden. Really good catch!
  2. Yeah, true. Otherwise this will break the contrib/custom code. Reverted.
  3. -
  4. Nobody likes merge conflicts 🙂I expect #3105318: Add a public API for aliasing resource type names to be merged first as it's RTBC.
  5. Ok, let's restore it.
  6. Status code assertion removed. It doesn't make much sense there.
br0ken’s picture

StatusFileSize
new912 bytes
new10.61 KB

Forgot to restore the test.

bbrala’s picture

Status: Needs review » Needs work

Thanks for the work :)

+++ b/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.php
@@ -515,6 +515,45 @@ public function testRead() {
+    $get_data = function (): array {
+      return Json::decode($this->drupalGet('/jsonapi/node/article'));
+    };

I would really prefer not using this kinda function. Perhaps we can make a separate issue where we introduce a few helper methods that can do smart things like this.

br0ken’s picture

Status: Needs work » Needs review
StatusFileSize
new10.85 KB
new2.15 KB

Oke.

br0ken’s picture

StatusFileSize
new1.7 KB
new10.89 KB
br0ken’s picture

StatusFileSize
new10.89 KB
new573 bytes

One. More. Thing.

Sorry.

bbrala’s picture

Hmm, technically this is changing to a public API if it is included in the event.

bbrala’s picture

Issue tags: -API-First Initiative +undefined

Think it will help if the summary is updated according to the current solution. I've asked around if another maintainer of jsonapi will have a look also.

br0ken’s picture

Title: Allow to include a count of items to collection responses » Allow to include a total count of items to collection responses
Issue summary: View changes
Issue tags: -undefined
gabesullice’s picture

  1. +++ b/core/modules/jsonapi/src/ResourceType/ResourceType.php
    @@ -249,14 +258,34 @@ public function isFieldEnabled($field_name) {
    +  /**
    +   * Returns the name of a `meta` member that holds a total count of items.
    +   *
    +   * @return string|null
    +   *   The name of a `meta` member that holds a total count of items or `null`
    +   *   if the member should not be present.
    +   */
    +  public function getTotalCountMetaName(): ?string {
    +    // If the `totalCountMetaName` is `null` and `includeCount()` returns
    +    // `true`, this means we deal with the overridden `includeCount()` and
    +    // should return the former name for the meta member containing the
    +    // total count.
    +    if ($this->totalCountMetaName === NULL && $this->includeCount()) {
    +      return 'count';
    +    }
    

    Let's add isCollectionSizeMemberEnabled() and deprecate the includeCount() method. Then we can add an assertion that $this->totalCountMetaName !== NULL in here. That will allow us to do return $this->totalCountMetaName ?? 'count'. Nullable types are cool, but IMO they're easy to mishandle. Better to always return a string.

  2. +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvent.php
    @@ -139,4 +150,26 @@ public function disableField(ResourceTypeField $field) {
    +  /**
    +   * Sets the name of a `meta` member that holds a total count of items.
    +   *
    +   * @param string|null $name
    +   *   The name of a `meta` member that holds a total count of items.
    +   *   Excludes the member in case of `null`.
    +   */
    +  public function setTotalCountMetaName(?string $name): void {
    +    $this->totalCountMetaName = $name;
    +  }
    

    I think this method should document how and why this member's value can be inaccurate.

    Maybe I'm being obstinate... but let me offer this compromise: can we replace all the "total count name" keywords with the phrase "collection size member name". E.g. s/setTotalCountMetaName/setCollectionSizeMemberName and s/$total_count_meta_name/$collection_size_member_name.

    "Total count" and "count" have always implied a precise, discrete, and true number of resources in the collection which we cannot guarantee for weird reasons. "Size" is still that way, but a little closer to the idea of "maximum" and "limit".

    Since this is a new API, we can do this: setCollectionSizeMemberName(string $name = 'collectionSize') at the same time as we do this: getCollectionSizeMemberName() { return $this->collectionCountMetaName ?? 'count'; }. Thus anyone still overriding ResourceType::includeCount() to be TRUE will still see count in their HTTP API. Users who upgrade (like jsonapi_extras) can call setCollectionSizeMemberName('count') to preserve BC. Finally, new users can call setCollectionSizeMemberName() without an argument and will get 'collectionSize'.

br0ken’s picture

StatusFileSize
new13.91 KB
new15.48 KB

What do you think about the solution without isCollectionSizeMemberEnabled()? The idea is to have no repetitive logic and only use getCollectionSizeMemberName() for determining whether to include a collection size member. Returns NULL - exclude, a string - include (sort of a boolean without an extra method so less to deal with). Later on we'll delete the includeCount() and its logic become return $this->collectionSizeMemberName;.

br0ken’s picture

StatusFileSize
new4.11 KB
new14.64 KB

A bit better implementation (IMO).

br0ken’s picture

+++ b/core/modules/jsonapi/src/Controller/EntityResource.php
@@ -437,21 +437,21 @@ public function getCollection(ResourceType $resource_type, Request $request) {
+      $meta[$collection_size_member_name] = (int) $this->executeQueryInRenderContext(

This may be a BC for jsonapi_extras and similar users because previously there was a numeric string.

The last submitted patch, 39: 3104408-39.patch, failed testing. View results

br0ken’s picture

StatusFileSize
new14.68 KB
new1.12 KB

Status: Needs review » Needs work

The last submitted patch, 43: 3104408-43.patch, failed testing. View results

br0ken’s picture

Status: Needs work » Needs review

How to make the deprecation not to fail the tests?

br0ken’s picture

StatusFileSize
new1.86 KB
new14.65 KB
br0ken’s picture

+++ b/core/modules/jsonapi/src/ResourceType/ResourceType.php
@@ -249,16 +259,33 @@ public function isFieldEnabled($field_name) {
+    @trigger_error(sprintf('The %s::includeCount() is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Use ResourceTypeBuildEvent::setCollectionSizeMemberName(\'count\') as a replacement.', static::class), E_USER_DEPRECATED);

This deprecation error trigger doesn't seem helpful as if the method is not overridden, we'll trigger it all the time the getCollectionSizeMemberName() is called, which doesn't make sense as we know it's deprecated since we did that. If overridden, it's only possible to trigger the deprecation error if the parent is called, which doesn't seem feasible as it always returns FALSE.

What do you think of adding this assertion to the getCollectionSizeMemberName():

    assert((function () {
      if ((new \ReflectionMethod($this, 'includeCount'))->getDeclaringClass()->getName() !== self::class) {
        @trigger_error(sprintf('The %s::includeCount() is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Use ResourceTypeBuildEvent::setCollectionSizeMemberName(\'count\') as a replacement.', static::class), E_USER_DEPRECATED);
      }

      return TRUE;
    })());

With that in place, we'll be triggering the error only if the method is overridden. However, this also adds an overhead of using the reflection.

br0ken’s picture

StatusFileSize
new14.41 KB
new1.88 KB

Wait, there is a better solution actually.

Status: Needs review » Needs work

The last submitted patch, 48: 3104408-48.patch, failed testing. View results

br0ken’s picture

Status: Needs work » Needs review
StatusFileSize
new1.89 KB
new15.89 KB
br0ken’s picture

+++ b/core/modules/jsonapi/src/JsonApiResource/Data.php
@@ -83,20 +76,6 @@ public function count() {
-  /**
-   * {@inheritdoc}
-   */
-  public function getTotalCount() {
-    return $this->count;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setTotalCount($count) {
-    $this->count = $count;
-  }
-

Is it safe to delete these methods? I find them confusing as the value is provided only when the includeCount() returns true (never in standard jsonapi configuration).

Could it be that someone uses them during the normalization?

In case we can't delete them, I think they have to be deprecated in favor of getCollectionSize(): ?int and setCollectionSize(int $size): void.

Another question: in case the methods stay, can we run the count query regardless of the meta member inclusion and always provide the collection size? Perhaps it should become the Drupal\jsonapi\JsonApiResource\Data::__construct() argument.

bbrala’s picture

Thanks for the ongoing effort you are putting into this @BR0kEN :)

  1. What do you think about the solution without isCollectionSizeMemberEnabled()? The idea is to have no repetitive logic and only use getCollectionSizeMemberName() for determining whether to include a collection size member. Returns NULL - exclude, a string - include (sort of a boolean without an extra method so less to deal with). Later on we'll delete the includeCount() and its logic become return $this->collectionSizeMemberName;.

    To be honest I rather have a separate method to provide the check if it is enabled. This will make comparison explicit and the code more readable.

  2. > if ($collection_size_member_name = $resource_type->getCollectionSizeMemberName())

    We also need to be carefull for loosely typed comparisons, and that is way easier and less error prone if we have that method.

  3. Is it safe to delete these methods? I find them confusing as the value is provided only when the includeCount() returns true (never in standard jsonapi configuration).

    We do need to do the deprecation dance there I think. I don't see much usage in contrib, but perhaps when building custom resources this might come up. For example here in jsonapi_search_api.

  4. Another question: in case the methods stay, can we run the count query regardless of the meta member inclusion and always provide the collection size? Perhaps it should become the Drupal\jsonapi\JsonApiResource\Data::__construct() argument.

    Why would we run the count query if the data is not needed, that seems wastefull to do the work for something we can know we do or do not need.

  5. +++ b/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.php
    @@ -515,6 +509,61 @@ public function testRead() {
    +  public function testCollectionSizeMember(): void {
    

    Perhaps this should enclude a test for the case of providing an empty string as name. That would be invalid and we should test that case.

  6. +++ b/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.php
    @@ -515,6 +509,61 @@ public function testRead() {
    +  public function testCollectionSizeMember(): void {
    

    Perhaps this should enclude a test for the case of providing an empty string as name. That would be rather invalid.

br0ken’s picture

  1. The logic for isCollectionSizeMemberEnabled() will be return $this->getCollectionSizeMemberName() !== NULL; which seems inefficient because we'll literally call getCollectionSizeMemberName() twice: once in an if statement as part of isCollectionSizeMemberEnabled() and then for obtaining the meta member name. This also expands the field to make a mistake for those override lovers because two methods must follow the same logic. Two instead of one. However, maybe I'm overengineering.
  2. We have assert($collection_size_member_name === NULL || trim($collection_size_member_name) !== '') that checks value correctness however I think we might also need a runtime check since it's a public API. With that guard if ($collection_size_member_name = $resource_type->getCollectionSizeMemberName()) will be safe as the method returns either null or non-empty string.
  3. Yeah, let's don't touch them in this issue at all and come back to their deprecation in a follow-up if this feature will be merged.
  4. Because the object is contradictory. It's not documented that the count can easily be null (almost always if you use core's jsonapi with no overrides of includeCount()) and that the value presence depends on the includeCount() from the resource type.

    IMO, these getTotalCount() and setTotalCount() should not exist but again, let's not touch this in scope of this issue.

  5. Makes sense.
bbrala’s picture

1+2:

I would actually think it would do the following:

return $this->getCollectionSizeMemberName() !== NULL && !empty(trim($this->getCollectionSizeMemberName());

In an if this would mean:

if ($this->isCollectionSizeMemberEnabled()){

Which i think is way better than:

if ($collection_size_member_name = $resource_type->getCollectionSizeMemberName()){

Mostly because of the loose type comparison and I rather be expicit. Technically the specification is even more specific in what would be allowed there. But lets not go there :)

bbrala’s picture

Status: Needs review » Needs work

bbrala’s picture

Opened a merge request to 9.3.x with patch from #50.

bbrala’s picture

Still needs work though. :)

bbrala’s picture

Status: Needs work » Reviewed & tested by the community
Issue tags: +Needs change record

I've revisited this issue and concluded my comment in #54 is a bit overly defensive. I've only added a small test for the empty string and rebased for you.

Changes looking good, setting RTBC. Adding tag for CR

quietone’s picture

Status: Reviewed & tested by the community » Needs work

This still needs a change record, setting to Needs Work for that,

bbrala’s picture

Status: Needs work » Reviewed & tested by the community

Not sure if it's OK to RTBC again, i've added a proper changerecord.

Also; added credits for @gabesullic and @JordanDukart for research and work on this issue.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

larowlan’s picture

hiding patches because there's an MR here

br0ken’s picture

Just migrated to 9.3.0 + the latest patch from the MR. All good. +1 to RTBC.

lauriii’s picture

Status: Reviewed & tested by the community » Needs work

#38.2 seems to be still unaddressed.

Should we at least document the potential security concerns this could introduce on sites, given that the count bypasses Entity::access? There are many cases where revealing that something exists is considered as an access bypass.

bbrala’s picture

Status: Needs work » Reviewed & tested by the community

Maybe I'm being obstinate... but let me offer this compromise: can we replace all the "total count name" keywords with the phrase "collection size member name". E.g. s/setTotalCountMetaName/setCollectionSizeMemberName and s/$total_count_meta_name/$collection_size_member_name.

"Total count" and "count" have always implied a precise, discrete, and true number of resources in the collection which we cannot guarantee for weird reasons. "Size" is still that way, but a little closer to the idea of "maximum" and "limit".

Since this is a new API, we can do this: setCollectionSizeMemberName(string $name = 'collectionSize') at the same time as we do this: getCollectionSizeMemberName() { return $this->collectionCountMetaName ?? 'count'; }. Thus anyone still overriding ResourceType::includeCount() to be TRUE will still see count in their HTTP API. Users who upgrade (like jsonapi_extras) can call setCollectionSizeMemberName('count') to preserve BC. Finally, new users can call setCollectionSizeMemberName() without an argument and will get 'collectionSize'.

@lauriii, I think that comment was addressed. The changes in the naming of the variables was applied as suggested by @gabesullice.

Should we at least document the potential security concerns this could introduce on sites, given that the count bypasses Entity::access? There are many cases where revealing that something exists is considered as an access bypass.

This is no new information that is supplied. When you currently query jsonapi the access check will be done AFTER collecting the entities for a page on the index endpoint. This means if you paginate by 10 you could have only 8 actual results (or actually 0 unfortunately). So you know that there are 2 entitites that are filtered out by the accesscheck. So this means this issue does not expose more information than it did before, since you can just count the amount of entities that are not shown in the index endpoints and get the same information.

Setting back to RTBC since I think the issues raised by @laurii have been adressed and there is no new security implications.

lauriii’s picture

@lauriii, I think that comment was addressed. The changes in the naming of the variables was applied as suggested by @gabesullice.

The first paragraph asked for additional documentation which was the part I thought wasn't addressed:

I think this method should document how and why this member's value can be inaccurate.

This is no new information that is supplied. When you currently query jsonapi the access check will be done AFTER collecting the entities for a page on the index endpoint. This means if you paginate by 10 you could have only 8 actual results (or actually 0 unfortunately). So you know that there are 2 entitites that are filtered out by the accesscheck. So this means this issue does not expose more information than it did before, since you can just count the amount of entities that are not shown in the index endpoints and get the same information.

Where is this information available? I wasn't aware that JSON:API exposed that information anywhere.

bbrala’s picture

Hmm, i just had a look at the documentation and found:

Filters and Access Control

There is mention of access checks being done after the fact and that has those implications. But i cant seem to find an explicit mention in the docs right now regarding the lists that could have gaps.

I've also gone through security considerations but haven't really found an explicit mention.

I'd be happy to work on improving the documentation, but perhaps that could be in a child/followup?

quietone’s picture

Version: 9.4.x-dev » 10.0.x-dev
Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs reroll

I've switched this to 10.0.x because it needs to be applied there first, can we get a 9.5.x version too,

@bbrala, thanks for adding the changed record. It doesn't look like that has been reviewed. Can someone do that?

immaculatexavier’s picture

Status: Needs work » Needs review
StatusFileSize
new15.93 KB
new10.49 KB

Rerolled patch against 10.0.x
Attached rerolled diff

bbrala’s picture

Status: Needs review » Needs work

Thanks for the reroll! Seems the reroll didn't go as planned though unfortunately.

immaculatexavier’s picture

Status: Needs work » Needs review
StatusFileSize
new16.24 KB
new1.62 KB

Rerolled patch against 10.0.x
Attached rerolled diff

bbrala’s picture

Status: Needs review » Needs work

Great, thanks! Unfortunately there is a styleissue that needs to be fixed in ResourseType.php

We also need a review on the CR I wrote.

immaculatexavier’s picture

Status: Needs work » Needs review
StatusFileSize
new16.32 KB
new1005 bytes

Rerolled patch against 10.0.x, Addressed #73
Attached interdiff

Status: Needs review » Needs work

The last submitted patch, 74: 3104408-74.patch, failed testing. View results

immaculatexavier’s picture

Status: Needs work » Needs review
StatusFileSize
new19.06 KB
new2.69 KB

Rerolled patch against #74
Attached interdiff

Status: Needs review » Needs work

The last submitted patch, 76: 3104408-76.patch, failed testing. View results

immaculatexavier’s picture

Status: Needs work » Needs review
StatusFileSize
new16.32 KB
new2.67 KB

Rerolled patch against #76
Attached interdiff

Status: Needs review » Needs work

The last submitted patch, 78: 3104408-78.patch, failed testing. View results

bbrala’s picture

Status: Needs work » Needs review
Issue tags: -Needs change record, -Needs reroll

I've rerolled to 10.0.x, there were no conflics which is lovely. Only made a small change to the deprecation message. I think this change was minimal so I still stand by my RTBC.

We still need a review on the changerecord though as mentioned in #69 by @quietone

daffie’s picture

Status: Needs review » Needs work

Review for the CR only:

  1. The new method Drupal\jsonapi\ResourceType\ResourceTypeBuildEvent::getCollectionSizeMemberName() is added to the public API, only not in the CR.
  2. "See Drupal\jsonapi_test_resource_type_building\EventSubscriber\ResourceTypeBuildEventSubscriber for an example of how to alias a resource type name."
    Can we add a link.
  3. The new method Drupal\jsonapi\ResourceType\ResourceType::getCollectionSizeMemberName() is added to the public API, only not in the CR.

For the MR: I do not think we can remove public methods without deprecating them first.

bradjones1’s picture

For the MR: I do not think we can remove public methods without deprecating them first.

This class, like most of all of json:api modules', is marked internal so this is not strictly a BC break.

daffie’s picture

This class, like most of all of json:api modules', is marked internal so this is not strictly a BC break.

Good point. Than they are not part of the public API and they can be removed without deprecating them first.

bbrala’s picture

Status: Needs work » Needs review

#81.1: From the changerecord: "The JSON:API ResourceTypeBuildEvent now has a new method: setCollectionSizeMemberName(string $name = NULL). Subscribers to that event can now enable the collection count and set the member name used as key in the meta data of the lists."

#81.2: We could add a link, but then it needs to be merged first. I'll promise to add that :)

#81.3: I've added the method to the CR.

#81.MR: When doing a codesearch is seems to see no usage that will break. The code will check the resources for an implementation and use that if it exist. So it does seem fine.

daffie’s picture

Status: Needs review » Reviewed & tested by the community

@bbrala: Thank you for your reply.

All my points have been addressed and the CR is RTBC for me.

alexpott made their first commit to this issue’s fork.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

I've added some questions to MR.

I think we might need another CR to detail all of the code changes. The current CR focuses on the new stuff which is great but I think we should have CR that covers the API changes and what someone who has used the old API is supposed to do.

bbrala’s picture

Version: 10.0.x-dev » 10.1.x-dev
Status: Needs work » Needs review

Ok, went through the feedback and think i addressed all of it. More BC code, a new BC test, and a new change record.

:)

wim leers’s picture

Status: Needs review » Needs work

phpstan & phpcs are not yet happy.

bbrala’s picture

Oops, thanks.

bbrala’s picture

Hmm, that is really unfortunate. So i cannot use a contructor promoted property because phpstan doesn't want that? :(

bbrala’s picture

Status: Needs work » Needs review

Removed the property promotion since that means a snakecase class property, that doesn't fly in drupal afaik :(

Also added an ignore for phpstan for a anonimous testclass.

bbrala’s picture

I've rebased the MR onto 10.1.x-dev since it was no longer mergable.

bbrala’s picture

Cleaned up the rebase, there were smoe release commits in there for some reason. Don't know why. Fixed by doing an interactive rebase on itself and dropping the extra commits. Diff from 10.1.x now looks clean <3

gaurav_manerkar’s picture

There is a merge conflict for core/phpstan-baseline.neon

gaurav_manerkar’s picture

Status: Needs review » Needs work
br0ken’s picture

Meh, now I can't apply this sweety patch https://git.drupalcode.org/project/drupal/-/merge_requests/1308.diff to my Drupal 9.4.x 😔. The thing with patches seems more reliable

br0ken’s picture

StatusFileSize
new17.57 KB

In case someone is looking for a 9.4.x version.

bbrala’s picture

Status: Needs work » Needs review

Fixed phpstan error and rebased while im at it.

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs Review Queue Initiative

MR 1308 had a CI failure.

Did not test or review.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

bbrala changed the visibility of the branch 3104408-allow-to-include to hidden.

bbrala’s picture

Status: Needs work » Needs review

Lets try again, updated to 11.x and fixed tests <3

smustgrave’s picture

Status: Needs review » Needs work

Can the issue summary be updated with a clear proposed solution?

Deprecations appear right and trying to test locally
Applied the MR
Created some articles
Went to /jsonapi/node/article
But not noticing any difference with or without the MR. Any guidance on how to test would be appreciated.

Ping me or I'll keep an eye on this to come back around and prioritize a faster review so it doesn't sit.

voleger’s picture

Are you sure the meta key name has to be `total_count`?
According to the JSONAPI example https://jsonapi.org/examples/#pagination there is a reference to `totalPages`.

bradjones1’s picture

The spec says:

Note: Putting a property like "totalPages" in "meta" can be a convenient way to indicate to clients the total number of pages in a collection (as opposed to the "last" link, which simply gives the URI of the last page). However, all "meta" values are implementation-specific, so you can call this member whatever you like ("total", "count", etc.) or not use it at all.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.