From #2547833: Pager.inc -- add tests, clean it up, convert to a service, use it!:

Problem/Motivation

pager.inc uses global variables, which we are trying to get rid of, as they make it impossible to manage pagers in a OO manner. All the pager related API are procedural functions.

Proposed resolution

Make Drupal's default pager an OO service, using the capabilities introduced in Drupal 8. This will make it OO and will open it up to the possibility to replace it by alternative implementations.

Unfortunately proper cleanup would include removing the four globals in pager.inc. Since that's an API change, the best we can do in 8.x is deprecate them and include (deprecated) wrappers for the new service to maintain backwards comparability until 9.x.

In detail:

1) A 'pager' is a classed object that contains data and methods relative to a pager element. A 'pager manager' service creates 'pager' objects and keeps track of the objects created so that they can be traversed to produce the 'page' URL querystring for the pager links.

2) The global variables are retained at this stage, wrapped into the Pager methods so that methods can alter them, but also, for BC, other code can alter them independently, and in this case the values stored in the global override any value stored in the Pager object.

3) pager.inc procedural functions remain for BC as wrappers to PagerManagerInterface service or Pager methods. There are no longer global variables declared in this file!

4) template_preprocess_pager() use the new service methods instead of directly accessing the globals.

5) A PagerParameters helper service is used to extract the pager parameters from a request.

Remaining tasks

  • review

User interface changes

None.

API changes

All pager_* global variables and procedural function will be deprecated. New Drupal\Core\Pager\PagerInterface and Drupal\Core\Pager\PagerManagerInterface and their default implementations will be added. BC layer introduced to allow deprecated global variables and functions to stay with same functionality until Drupal 9.

Data model changes

None.

CommentFileSizeAuthor
#290 2044435-290-interdiff.txt20.49 KBkim.pepper
#290 2044435-290.patch62.32 KBkim.pepper
#286 278-286-interdiff.txt1.8 KBalexpott
#286 278-286-interdiff.txt1.8 KBalexpott
#278 2044435-278-interdiff.txt2.03 KBkim.pepper
#278 2044435-278.patch62.4 KBkim.pepper
#275 2044435-273-interdiff.txt11.12 KBkim.pepper
#275 2044435-273.patch62.37 KBkim.pepper
#270 2044435-270-interdiff.txt5.41 KBkim.pepper
#270 2044435-270.patch59.34 KBkim.pepper
#269 2044435-269-interdiff.txt821 byteskim.pepper
#269 2044435-269.patch63.3 KBkim.pepper
#263 2044435-263-interdiff.txt821 byteskim.pepper
#263 2044435-263.patch68.53 KBkim.pepper
#261 2044435-261-interdiff.txt21.53 KBkim.pepper
#261 2044435-261.patch67.72 KBkim.pepper
#257 2044435-257-interdiff.txt15.03 KBkim.pepper
#257 2044435-257.patch62.49 KBkim.pepper
#254 2044435-254-interdiff.txt14.9 KBkim.pepper
#254 2044435-254.patch58.88 KBkim.pepper
#252 2044435-252.patch58.79 KBkim.pepper
#249 2044435-249.patch58.79 KBjibran
#247 2044435-pager-247.patch58.79 KBandypost
#247 interdiff.txt3.32 KBandypost
#245 2044435-245-interdiff.txt3 KBkim.pepper
#245 2044435-245.patch57.59 KBkim.pepper
#242 2044435-242-interdiff.txt3.54 KBkim.pepper
#242 2044435-242.patch55.07 KBkim.pepper
#241 2044435-241-interdiff.txt1.82 KBkim.pepper
#241 2044435-241.patch53.32 KBkim.pepper
#239 2044435-pager-239.patch53.28 KBandypost
#239 interdiff.txt1.05 KBandypost
#237 2044435-237.patch53.29 KBandypost
#237 interdiff.txt8.35 KBandypost
#235 2044435-235.patch53.24 KBandypost
#235 interdiff.txt1.93 KBandypost
#234 2044435-234.patch52.06 KBandypost
#234 interdiff.txt6.29 KBandypost
#229 2044435-229.patch51.5 KBandypost
#229 interdiff.txt611 bytesandypost
#227 2044435-227.patch51.34 KBAnonymous (not verified)
#224 2044435-pager-224.patch52.52 KBkim.pepper
#224 2044435-pager-224-interdiff.txt8.63 KBkim.pepper
#222 2044435-pager-222.patch45.74 KBkim.pepper
#222 2044435-pager-222-interdiff.txt527 byteskim.pepper
#219 2044435-pager-service-219.patch45.74 KBkim.pepper
#219 2044435-pager-service-219-interdiff.txt583 byteskim.pepper
#217 2044435-pager-service-217.patch45.65 KBkim.pepper
#217 2044435-pager-service-217-interdiff.txt637 byteskim.pepper
#214 2044435-pager-service-214.patch45.39 KBkim.pepper
#212 2044435-pager-service-212.patch45.61 KBkim.pepper
#212 2044435-pager-service-212-interdiff.txt22.94 KBkim.pepper
#206 2044435-pager-service-206.patch44.72 KBkim.pepper
#206 2044435-pager-service-206-interdiff.txt8.46 KBkim.pepper
#202 interdiff_197.txt2.15 KBmile23
#202 2044435_202.patch44.91 KBmile23
#200 interdiff-2044435-197-200.txt3.27 KBmartin107
#200 2044435-200.patch45.14 KBmartin107
#197 interdiff.txt16.22 KBmile23
#197 2044435_197.patch44.86 KBmile23
#195 interdiff.txt22.88 KBmile23
#195 2044435_195.patch43.45 KBmile23
#192 interdiff.txt8.33 KBmile23
#192 2044435_192.patch38.15 KBmile23
#189 interdiff.txt13.12 KBmile23
#189 2044435_187.patch34.19 KBmile23
#186 interdiff.txt7.91 KBmile23
#186 2044435_186.patch27.7 KBmile23
#183 interdiff.txt22.58 KBmile23
#183 2044435_183.patch21.72 KBmile23
#181 2044435_181.patch16.94 KBmile23
#171 2044435-171.patch37.87 KBmondrake
#171 interdiff_168-171.txt3.38 KBmondrake
#168 2044435-168.patch38.38 KBmondrake
#168 interdiff_162-168.txt1.83 KBmondrake
#162 2044435-162.patch38.26 KBmondrake
#162 interdiff_160-162.txt10.95 KBmondrake
#160 2044435-160.patch36.79 KBmondrake
#160 interdiff_140-160.txt20.72 KBmondrake
#140 interdiff_136-140.txt10.84 KBmondrake
#140 2044435-140.patch36.76 KBmondrake
#136 interdiff_133-136.txt2.05 KBmondrake
#136 2044435-136.patch38.99 KBmondrake
#134 interdiff_131-133.txt3.95 KBmondrake
#134 2044435-133.patch41.04 KBmondrake
#131 2044435-131.patch42.09 KBmondrake
#131 interdiff_126-131.txt25.24 KBmondrake
#126 2044435-125.patch39.54 KBmondrake
#126 interdiff_121-125.txt7.4 KBmondrake
#121 interdiff_110-121.txt4.2 KBmondrake
#121 2044435-121.patch41.07 KBmondrake
#118 2044435-118.patch40.89 KBdaffie
#118 interdiff-2044435-110-118.txt1.89 KBdaffie
#110 2044435-110.patch42.47 KBmondrake
#110 interdiff_103-110.txt736 bytesmondrake
#103 2044435-103.patch42.46 KBmondrake
#103 interdiff_101-103.txt2.44 KBmondrake
#101 2044435-101.patch42.67 KBmondrake
#101 interdiff_99-101.txt1.94 KBmondrake
#99 2044435-99.patch42.26 KBmondrake
#99 interdiff_97-99.txt1005 bytesmondrake
#97 2044435-97.patch42.21 KBmondrake
#97 interdiff_91-97.txt8.44 KBmondrake
#91 2044435-91.patch43.94 KBmondrake
#91 interdiff_83-91.patch19.27 KBmondrake
#83 2044435-83.patch46.71 KBmondrake
#83 interdiff_81-83.txt718 bytesmondrake
#81 2044435-81.patch46.54 KBmondrake
#81 interdiff_76-81.txt7.69 KBmondrake
#76 2044435-76.patch44.93 KBmondrake
#76 interdiff_73-76.txt8.97 KBmondrake
#73 2044435-73.patch42.97 KBmondrake
#73 interdiff_69-73.txt7.33 KBmondrake
#69 2044435-68.patch42.18 KBmondrake
#69 interdiff_64-68.txt5.49 KBmondrake
#68 2044435-68.patch42.99 KBmondrake
#68 interdiff_64-68.txt5.49 KBmondrake
#64 2044435-64.patch42.58 KBmondrake
#64 interdiff_63-64.txt5.58 KBmondrake
#63 2044435-63.patch37.59 KBmondrake
#63 interdiff_62-63.txt6.01 KBmondrake
#62 2044435-62.patch33.45 KBmondrake
#62 interdiff_61-62.txt1.34 KBmondrake
#61 2044435-61.patch32.97 KBmondrake
#61 interdiff_59-61.txt1.11 KBmondrake
#59 2044435-59.patch32.98 KBmondrake
#59 interdiff_58-59.txt4.5 KBmondrake
#58 interdiff-55-58.txt1.28 KBmartin107
#58 2044435-pager_service-58.patch33.56 KBmartin107
#55 interdiff-2044435-54-55.txt2.1 KBmartin107
#55 2044435-pager_service-55.patch33.55 KBmartin107
#54 2044435-pager_service-54.patch33.81 KBmondrake
#54 interdiff_49-54.txt1.86 KBmondrake
#53 interdiff_49-53.txt4.91 KBmondrake
#53 2044435-pager_service-53.patch33.71 KBmondrake
#49 2044435-pager_service-49.patch34.2 KBmondrake
#49 interdiff_48-49.txt6.51 KBmondrake
#48 2044435-pager_service-48.patch33.2 KBmondrake
#48 2044435-pager_service-48.patch33.2 KBmondrake
#40 2044435-pager_service-40.patch33 KBmondrake
#40 interdiff_39-40.txt647 bytesmondrake
#39 2044435-pager_service-39.patch32.81 KBmondrake
#38 2044435-pager_service-38.patch35.8 KBmondrake
#38 interdiff_36-38.txt10.69 KBmondrake
#36 interdiff_34-36.txt11.02 KBmondrake
#36 2044435-pager_service-36.patch34.76 KBmondrake
#34 2044435-pager_service-34.patch27.78 KBmondrake
#26 2044435-pager_component-26-do-not-test.patch49.69 KBmondrake
#26 interdiff_16-26.txt1.13 KBmondrake
#16 2044435-pager_component-16.patch49.55 KBmondrake
#16 interdiff_13-16.txt1.67 KBmondrake
#13 2044435-pager_component-13.patch49.54 KBmondrake
#13 interdiff_9-13.txt24.13 KBmondrake
#9 2044435-pager_component-9.patch48.83 KBmondrake
#9 interdiff_5-9.txt18.85 KBmondrake
#5 2044435-pager_component-5.patch46.2 KBmondrake
#5 interdiff_1-5.txt1.14 KBmondrake
#1 2044435-pager_component-1.patch46.19 KBmondrake

Comments

mondrake’s picture

Status: Active » Needs review
StatusFileSize
new46.19 KB

Patch

Status: Needs review » Needs work

The last submitted patch, 2044435-pager_component-1.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review

#1: 2044435-pager_component-1.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, 2044435-pager_component-1.patch, failed testing.

mondrake’s picture

StatusFileSize
new1.14 KB
new46.2 KB

Just a char encoding problem?

mondrake’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 2044435-pager_component-5.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review

#5: 2044435-pager_component-5.patch queued for re-testing.

mondrake’s picture

StatusFileSize
new18.85 KB
new48.83 KB

With some cleanup.

larowlan’s picture

Issue tags: +PHPUnit, +WSCCI-conversion

Tagging

larowlan’s picture

Firstly *awesome* more globals down.
A few questions/observations.

+++ b/core/lib/Drupal/Component/Pager/Pager.phpundefined
@@ -0,0 +1,310 @@
+   * Each key in the array is itself an associative array with the following
+   * properties:
+   *   'totalItems' - total number of items in the pager
+   *   'totalPages' - total number of pages in the pager
+   *   'limit' - items per page
+   *   'current' - current page
...
+    static::$elements[$element][$key] = $value;

Does this warrant a helper class? Having a first class object would make a stronger API thank an associative array.

+++ b/core/lib/Drupal/Component/Pager/Pager.phpundefined
@@ -0,0 +1,310 @@
+    $page = isset($_GET['page']) ? $_GET['page'] : '';

This should use Request but would that mean it's no longer drupal agnostic?

+++ b/core/lib/Drupal/Component/Pager/Pager.phpundefined
@@ -0,0 +1,310 @@
+    self::set('totalItems', $total, $element);

I think we prefer static over self, it allows for the class to be extended

+++ b/core/lib/Drupal/Component/Pager/Pager.phpundefined
@@ -0,0 +1,310 @@
+    $query = &drupal_static(__CLASS__ . '::' . __FUNCTION__);
...
+      $query = drupal_get_query_parameters($_GET, array('page'));

Drupal specific shouldn't be used in Drupal\Component

For the static can we just make a protected property?

+++ b/core/lib/Drupal/Component/Pager/Pager.phpundefined
@@ -0,0 +1,310 @@
+    $max_element = max(array_keys(static::$elements));
...
+      $element_pages[] = ($i == $element) ? $page : self::getCurrentPage($i);

Any reason for mixing self and static?

mondrake’s picture

Title: Convert pager.inc to a Pager component (??) » Convert pager.inc to a Pager component
Issue tags: +Need tests

#12 - not fully experienced/aware of the design decisions, so please bear with me.

Does this warrant a helper class? Having a first class object would make a stronger API thank an associative array.

Agree, can you point to an example to check?

This should use Request but would that mean it's no longer drupal agnostic?

Yes I am watching #1998696: Use Symfony Request for core includes which is addressing same thing.

I think we prefer static over self, it allows for the class to be extended

OK fixed in attached patch

Drupal specific shouldn't be used in Drupal\Component

Aha ... if not component what would be the alternative?

For the static can we just make a protected property?

OK in attached

Any reason for mixing self and static?

Yes, ignorance :)

mondrake’s picture

StatusFileSize
new24.13 KB
new49.54 KB

More cleanup, maybe a bit too radical... let's see bot's mood.

When this gets green I will start incorporating tests from #1588138: pager_query_add_page() [in D7, theme_pager_link()] overrides parameters passed programmatically . More test will be needed to test non-0 pager elements... I have not seen any.

Crell’s picture

I am doubtful that this issue would be accepted at this point in the cycle. It's an API change, doesn't buy us much (just lazy loading; it doesn't fix that our pager system is AFU to begin with), and there's nothing critical or major blocked on it.

Status: Needs review » Needs work

The last submitted patch, 2044435-pager_component-13.patch, failed testing.

mondrake’s picture

StatusFileSize
new1.67 KB
new49.55 KB

This patch should fix the test failure in #13.

@14 - I know it's late but on the other hand I could find only 2 contrib modules in DrupalContrib using any of the Drupal 7 pager API functions. Is there a process to check if this could be finally accepted? Before I put any more effort here.

Thanks

mondrake’s picture

Status: Needs work » Needs review
Crell’s picture

The verification process is here: https://drupal.org/node/2045345

mondrake’s picture

Status: Needs review » Postponed

Thanks @Crell, clear enough :)

So this won't have a chance at this stage, marking postponed.

BTW - I learn this cannot be a 'component' as it requires to access the Request object to determine the current page to position in the needed recordset page, not being Drupal agnostic then. Would it then be a 'service'?

Crell’s picture

Things in Component can be registered as a service. It would just have to live in Drupal\Core\Pager instead. A service just means "a read only object available via the container" (more or less).

Now, if we could simplify and less-fugly the pager without a hard API break, that's something to consider. Do we have options there to at least clean up the code?

mondrake’s picture

Well, we may still do this AND keep the current API as procedural wrappers, marked as deprecated. We'll have to retain the 4 globals though, which would buy us even less IMHO.

larowlan’s picture

Issue tags: +API change

Tagging to get some exposure, it might be allowable anyway given the main use for the pager (views) is in core anyway.

xjm’s picture

Version: 8.x-dev » 9.x-dev

So, re-filing based on the current consensus on this issue. This is used two places in core: Views UI, and the demented hydra that is the taxonomy overview form (see #1974492: Convert taxonomy_overview_terms to a Form Controller). I'm actually sort of with Crell in that doing this this doesn't gain us this much immediately--at least, from the Taxonomy side, the form that uses this is not going to be cleanly decoupled any time soon anyway. I'll ping Views UI co-maintainers to see what they thing about the globals.

mondrake’s picture

For a discussion on the Mini pager, and the need to have an 'open-ended' pager i.e. where total number of pages in the recordset is unknown, please see #2049723: Mini pager shows "Page 1" if there is non item at all available.

oadaeh’s picture

Issue tags: -Need tests +Needs tests

Changing the tests tag to the more universally used one.

mondrake’s picture

Just a reroll of #16 to keep up with changes in 8.x HEAD. No tests, no additional logic.

gagarine’s picture

Issue summary: View changes

Why not using https://github.com/whiteoctober/Pagerfanta ? I'm using it for symfony project and it's pretty simple and solid.

larowlan’s picture

Issue tags: +Kill includes
Crell’s picture

At least in the abstract I'd be fine with dropping our pager and using Pagerfanta.

dawehner’s picture

@Crell
Pagerfanta does not even support our default pager which is the mini pager. That one cannot support the AdapterInterface they provide (see https://github.com/whiteoctober/Pagerfanta/issues/145)

mondrake’s picture

Title: Convert pager.inc to a Pager component » Convert pager.inc to a service
Assigned: Unassigned » mondrake
Related issues: +#2547833: Pager.inc -- add tests, clean it up, convert to a service, use it!

Working on a new patch.

xano’s picture

We are on the brink of releasing RC1. You may want to check on IRC (#drupal-contribute) to see if this issue will be able to make it into Drupal 8 at all, before you spend time on this.

mondrake’s picture

I know this is not for D 8.0.0 :)

Still I have some ideas I'd like to share for future thoughts ;)

mondrake’s picture

Version: 9.x-dev » 8.0.x-dev
Assigned: mondrake » Unassigned
Status: Postponed » Needs review
Issue tags: +Needs issue summary update
StatusFileSize
new27.78 KB

So, here's the concept.

1) as per previous comments, let me remark I am aware this is not for Drupal 8.0, so I have no expectations in this sense. I am setting this issue to NR for 8.0.x to let bot gnaw the patch, and I will re-postpone later.

2) this patch is based on a similar approach implemented in the D8 version of the Pagerer contrib module. The idea is that a 'pager' is a classed object that contains data and methods relative to a pager element. A 'pager factory' creates 'pager' objects and keeps a directory of the objects created so that they can be traversed to produce the 'page' URL querystring for the pager links.

3) the global variables are retained at this stage, wrapped into the pager methods so that methods can alter them, but also, for BC, other code can alter them independently, and in this case the values stored in the global override any value stored in the pager object.

4) pager.inc functions remain for BC as wrappers to pager.factory service methods. There are no longer global variables declared in this file!

5) template_preprocess_pager() and template_preprocess_views_mini_pager() use the new service methods instead of directly accessing the globals.

mondrake’s picture

Version: 8.0.x-dev » 8.1.x-dev
Status: Needs review » Postponed

Maybe we can discuss this for 8.1.x, given there is BC

mondrake’s picture

Version: 8.1.x-dev » 8.0.x-dev
Status: Postponed » Needs review
StatusFileSize
new34.76 KB
new11.02 KB

Adding some docs.

mondrake’s picture

Version: 8.0.x-dev » 8.1.x-dev
Status: Needs review » Postponed
mondrake’s picture

Version: 8.1.x-dev » 8.0.x-dev
Status: Postponed » Needs review
StatusFileSize
new10.69 KB
new35.8 KB

Some changes, mostly around the factory so that it can use a parameter to define the class to be used to instantiate Pager objects. This way in the future an alternative pager engine (here I am thinking about Pagerer module) can use the same factory class to get pager objects that are an alternative implementation class of PagerInterface, without having to swap the factory class too. Not sure this is the right way though...

mondrake’s picture

mondrake’s picture

Title: Convert pager.inc to a service » [PP-1] Convert pager.inc to a service
Related issues: +#2572533: Add tests for multiple pagers on a given page
StatusFileSize
new647 bytes
new33 KB

This would benefit from additional tests for multiple pagers proposed in #2572533: Add tests for multiple pagers on a given page.

Here I am adding a parameter in the service container, being the class of the 'Pager' objects to be instantiated by the 'PagerFactory'. This way, contrib modules (see #38) can override that class in a MyModuleServiceProvider modifier, without touching the service definition -- just pointing to a different implementation of the 'PagerInterface' interface, like e.g.

class MyModuleServiceProvider extends ServiceProviderBase {

  /**
   * {@inheritdoc}
   */
  public function alter(ContainerBuilder $container) {
    // Override the 'pager_class' parameter class with MyModule pager class.
    $container->setParameter('pager_class', 'Drupal\MyModule\Pager');
  }

mondrake’s picture

Status: Needs review » Postponed
mondrake’s picture

Updated IS and tags

mondrake’s picture

Issue summary: View changes
mondrake’s picture

Version: 8.0.x-dev » 8.1.x-dev
Status: Postponed » Needs review
mondrake’s picture

Title: [PP-1] Convert pager.inc to a service » Convert pager.inc to a service
Issue summary: View changes

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Status: Needs review » Needs work

The last submitted patch, 40: 2044435-pager_service-40.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new33.2 KB
new33.2 KB

Plain reroll, will fail as #2641682: permalink breaks in comment introduced in 8.1 a #route_parameters variable to the pager preprocess, need to reflect that in the patch.

mondrake’s picture

StatusFileSize
new6.51 KB
new34.2 KB

Added management of the route parameters and updated the deprecation messages.

The last submitted patch, 48: 2044435-pager_service-48.patch, failed testing.

The last submitted patch, 48: 2044435-pager_service-48.patch, failed testing.

mondrake’s picture

Patch in #49 is green for review.

mondrake’s picture

StatusFileSize
new33.71 KB
new4.91 KB

Simplified the Pager object instantiation, and using directly Url object instead of injected url_generator service for building links.

mondrake’s picture

StatusFileSize
new1.86 KB
new33.81 KB

Using directly Url object instead of injected url_generator service for building links. Disregard #53.

martin107’s picture

StatusFileSize
new33.55 KB
new2.1 KB

This looks like a good conversion to me so far... it does need tests :(

Just picked out a few ultra trivial nits while following along.
[mostly @file is deprecated ]

mondrake’s picture

Thanks for review and patch, @martin107!

+++ b/core/lib/Drupal/Core/Pager/Pager.php
@@ -0,0 +1,323 @@
+<?php
+namespace Drupal\Core\Pager;
+

I think there should be an empty line between <?php and namespace Drupal\Core\Pager;. Same for other files.

mondrake’s picture

Status: Needs review » Needs work

@martin107 can you by any chance review #2572533: Add tests for multiple pagers on a given page? This would add a significant test for this issue too. Then yes, PHPUnit tests would be needed. CNW for #56.

martin107’s picture

Status: Needs work » Needs review
StatusFileSize
new33.56 KB
new1.28 KB

I have added the newline.

Thanks for the nudge I will have a look at

#2572533: Add tests for multiple pagers on a given page

sometime this weekend.

@mondrake I have only looked at the surface of that issue.. but between here and there there is a lot of work of yours that looks good and is unreviewed ... that is a shame. Is there anything else you want me to look at?

mondrake’s picture

StatusFileSize
new4.5 KB
new32.98 KB

Cleaned up a bit the Pager class.

andypost’s picture

+interface PagerInterface extends ContainerInjectionInterface

Suppose this interface should define only pager related methods, so container injection is not needed

mondrake’s picture

StatusFileSize
new1.11 KB
new32.97 KB

#60 OK thanks

mondrake’s picture

StatusFileSize
new1.34 KB
new33.45 KB

Adding a PagerFactoryInterface::getFirstAvailableElementId() method to return the first pager element ID free for use.

Would be useful in #1202484: Improve usage of 'pager.manager' service in PagerSelectExtender, allow code to know the pager element ID used, but I think it's better to have it in the interface earlier than later - in case this lands in a minor but the other not, then it will be difficult to change the interface.

mondrake’s picture

StatusFileSize
new6.01 KB
new37.59 KB

Adding PHPUnit tests for the PagerFactory class. Next, tests for the Pager class.

mondrake’s picture

Adding PHPUnit tests for the Pager class.

mondrake’s picture

Issue summary: View changes

Status: Needs review » Needs work

The last submitted patch, 64: 2044435-64.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review
mondrake’s picture

StatusFileSize
new5.49 KB
new42.99 KB

Here I am dropping the ::findCurrentPage method from PagerInterface. We can work exposing only ::getCurrentPage.

mondrake’s picture

StatusFileSize
new5.49 KB
new42.18 KB

Sorry this should be right.

Here I am dropping the ::findCurrentPage method from PagerInterface. We can work exposing only ::getCurrentPage.

daffie’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll

My first review for the pager service:

  1. +++ b/core/core.services.yml
    @@ -33,6 +33,7 @@ parameters:
    +  pager_class: Drupal\Core\Pager\Pager
    

    I am not sure if this right or not, but I have noticed that there are no other classes defined like this. Can you explain.

  2. +++ b/core/lib/Drupal/Core/Pager/PagerFactoryInterface.php
    @@ -0,0 +1,65 @@
    +  public function get($element);
    

    The name of this method is a bit vague. Not sure what would be better. Maybe something like getPager()?!

  3. +++ b/core/lib/Drupal/Core/Pager/PagerFactoryInterface.php
    @@ -0,0 +1,65 @@
    +  public function all();
    

    The name of this method is a bit vague. Not sure what would be better. Maybe something like getAllPagers()?!

  4. +++ b/core/lib/Drupal/Core/Pager/PagerInterface.php
    @@ -0,0 +1,196 @@
    +   * Gets the pager element.
    

    Can you add some more documentation what exactly is a pager element. Also in relation to the other parts of a pager.

  5. +++ b/core/lib/Drupal/Core/Pager/PagerInterface.php
    @@ -0,0 +1,196 @@
    +  /**
    +   * Initializes the pager.
    +   *
    +   * @param int $total
    +   *   The total number of items to be paged.
    +   * @param int $limit
    +   *   The number of items the calling code will display per page.
    +   *
    +   * @return \Drupal\Core\Pager\PagerInterface
    +   *   The Pager object.
    +   */
    +  public function init($total, $limit);
    

    Maybe a stupid question, but why not use the create() method instead of init()?

  6. +++ b/core/lib/Drupal/Core/Pager/PagerInterface.php
    @@ -0,0 +1,196 @@
    +   * @var $total_pages int
    ...
    +   * @var $total_items int
    ...
    +   * @var $limit int
    

    You do not define those variable in the interface , so what the interface is concerned they do not exist. They are just method parameters (@param).

  7. +++ b/core/lib/Drupal/Core/Pager/PagerInterface.php
    @@ -0,0 +1,196 @@
    +  /**
    +   * Gets a pager link.
    +   *
    +   * @param int $page
    +   *   The target page.
    +   *
    +   * @return string
    +   *   The URL of the pager link.
    +   */
    +  public function getLink($page);
    

    Can you add some more documentation what exactly is a pager link. Also in relation to the other parts of a pager.

  8. +++ b/core/lib/Drupal/Core/Pager/Pager.php
    @@ -0,0 +1,317 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public static function create(ContainerInterface $container, $element = NULL) {
    

    The Pager::create() method get it inheritance from ContainerInjectionInterface only you add an extra parameter to the method. Not sure if that is allowed. What is this method return value?

  9. +++ b/core/lib/Drupal/Core/Pager/Pager.php
    @@ -0,0 +1,317 @@
    +    $this->currentPage = $page;
    ...
    +    $this->totalPages = $total_pages;
    ...
    +    $this->totalItems = $total_items;
    ...
    +    $this->limit = $limit;
    

    Can we add typecasting: $this->variable = (int) $variable.

  10. Not instances of the deprecated functions in core/pager.inc are replaced by the new Pager service in the rest of core. Can you do that. Maybe we shall find some bugs we did not think of.

The file core/core.service.php had some additions, so small reroll is needed.

Good work mondrake. Lets work on this together and get this fixed.

The last submitted patch, 69: 2044435-68.patch, failed testing.

mondrake’s picture

Version: 8.2.x-dev » 8.3.x-dev
Assigned: Unassigned » mondrake

Thank you for review @daffie, will reply soon. Bumping to 8.3.x.

mondrake’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
StatusFileSize
new7.33 KB
new42.97 KB

#69:

1. Please see #38 and #40. The idea is to make the actual class used by the pager factory configurable, so that it could be swapped by alternative implementation without having to swap the factory too. This would be useful for e.g. #1818040: Pager should start counting from 1, not 0. Using config or plugin system seems to me too heavy. I'm open to any alternative, but I would like to avoid a situation like image.factory where the use of Drupal\Core\Image\Image is hardcoded.
2. and 3. 'image.factory' has a 'get' method to fetch an image. Symfony Request uses 'get' and 'all' to retrieve individual or all sub-components of a request. I was following that pattern. I am open to any change, but let's get more input here before doing any change in code.
4. OK, done
5. 'init' is the equivalent of 'pager_default_initialize'. Sometimes (see for example #2770649: [PP-1] Refactor OverviewTerms form to use the pager.factory service) you may need to retrieve the current page from the URL before you can initialize the pager. Also, if we were to put $total, $limit in the pager 'create' method, we would have to expose it also in the factory 'get' method. But that's not desirable: the 'get' should just return the object in its current status, because when it's used in theme preprocessing we are not going to set any variables but just getting them.
6. Copy/past error, sorry. Should be OK now.
7. OK, done
8. Shall we have a separate 'create' method in 'PagerInterface' then? Return value is an instance of the Pager object itself.
9. OK, done
10. AFAIK 'deprecation' means signalling that methods/functions should no longer be used, not that we should remove all their usages (yet). If I do that we will just scare people away from review :) as the patch will grow very large. I have already a couple of followup issues here, see #2770649: [PP-1] Refactor OverviewTerms form to use the pager.factory service and #1202484: Improve usage of 'pager.manager' service in PagerSelectExtender, allow code to know the pager element ID used. My suggestion would be to file all other followups based on scanning usages and refer to them in the parent issue #2547833: Pager.inc -- add tests, clean it up, convert to a service, use it!.

daffie’s picture

Status: Needs review » Needs work
Issue tags: +Needs change record

My second review for the pager service:

  1. +++ b/core/includes/pager.inc
    @@ -191,13 +191,13 @@ function template_preprocess_pager(&$variables) {
       // Calculate various markers within this pager piece:
       // Middle is used to "center" pages around the current page.
       $pager_middle = ceil($quantity / 2);
       // current is the page we are currently paged to.
    -  $pager_current = $pager_page_array[$element] + 1;
    +  $pager_current = $pager->getCurrentPage() + 1;
       // first is the first page listed by this pager piece (re quantity).
       $pager_first = $pager_current - $pager_middle + 1;
       // last is the last page listed by this pager piece (re quantity).
       $pager_last = $pager_current + $quantity - $pager_middle;
       // max is the maximum page number.
    -  $pager_max = $pager_total[$element];
    +  $pager_max = $pager->getTotalPages();
       // End of marker calculations.
    

    Minor problem with these function variables. I am fine with using these variables or with using the one you get from the pager object. But now we are mixing them in the rest of the function and that is confusing. Like $pager_current and $pager->getCurrentPage(). If we choose for using the pager object maybe we should add some extra method to the pager object.

  2. I do not like the naming of the "element" variable. For me does not feel right and I find it confusing. I think that "id" would be a better name. I do not know if we can change it and maybe there is a very good reason why it is named as it is. On the other side, when do we get an other chance to change the name of this variable.
  3. +++ b/core/lib/Drupal/Core/Pager/PagerInterface.php
    @@ -0,0 +1,208 @@
    +   * Gets the current page (0-index).
    ...
    +   * Sets the current page for this pager (0-index).
    ...
    +   * Gets last page in the pager (0-index).
    

    I have seen that there is an issue to change the pager from 0-indexed to 1-indexed. If we want to use the same PagerInterface for that, then we should remove the "0-index" parts from the PagerInterface.

  4. +++ b/core/lib/Drupal/Core/Pager/PagerFactory.php
    @@ -0,0 +1,126 @@
    +  /**
    +   * The array of pager objects.
    +   *
    +   * @var \Drupal\Core\Pager\PagerInterface[]
    +   */
    +  protected $pagers = [];
    

    Not 100% sure, but I think that the pager objects are indexed by their element. Can you add that to the documentation.

  5. +++ b/core/lib/Drupal/Core/Pager/PagerFactory.php
    @@ -0,0 +1,126 @@
    +  public function getFirstAvailableElementId() {
    

    Are the pagers indexed by their Element or by their ElementId? Or rename the method to getFirstAvailablePagerId?

  6. +++ b/core/lib/Drupal/Core/Pager/PagerFactoryInterface.php
    @@ -0,0 +1,65 @@
    +  /**
    +   * Returns the current array of pager objects.
    +   *
    +   * @return \Drupal\Core\Pager\Pager[]
    +   *   The array of pager objects.
    +   */
    +  public function all();
    

    Array of pager object indexed by their element.

  7. For comment #70.2 and #70.3: Good explanation lets leave it as it is.
  8. For comment #70.5: The Pager::init() method is fine.
  9. Can we do the change from comment #70.8 for the Pager::create() method.
  10. For comment #70.10: Lets do it in the follow-up issues.
  11. We need a change record for this issue. Can we add the part from comment #40 with how to change the pager class.

Todo: review the tests.

daffie’s picture

My review of the tests for the pager service:

  1. +++ b/core/tests/Drupal/Tests/Core/Pager/PagerFactoryTest.php
    @@ -0,0 +1,128 @@
    +    // Pager element should be 0.
    

    Pager element should be 2.

  2. +++ b/core/tests/Drupal/Tests/Core/Pager/PagerFactoryTest.php
    @@ -0,0 +1,128 @@
    +  public function testAll() {
    

    Can we also check that the elements are 4 and 6.

  3. +++ b/core/tests/Drupal/Tests/Core/Pager/PagerFactoryTest.php
    @@ -0,0 +1,128 @@
    +    $this->assertSame(['foo' => 'bar', 'qux' => 'dee'], $this->pagerFactory->getCurrentRequestQueryParameters());
    ...
    +    $this->assertSame('bar', $this->pagerFactory->getCurrentRequestQueryParameter('foo'));
    +    $this->assertSame('dee', $this->pagerFactory->getCurrentRequestQueryParameter('qux'));
    ...
    +    $this->assertSame('', $this->pagerFactory->getCurrentRequestQueryParameter('zot'));
    

    These tests are not testing much. With the method getCurrentRequestQuery() being mocked.

mondrake’s picture

StatusFileSize
new8.97 KB
new44.93 KB

Thanks @daffie!

#74

1. Let's not confuse the properties of the Pager object with the local variables used by the preprocess to determine the actual pager links to be shown. I've renamed the latter.
2. 'element' comes from the Pager render element (Drupal\Core\Render\Element\Pager) and previously from the 'pager' theme. I agree it's not ideal and 'id' would be better. OTOH, it will be confusing for themers as we need to keep the 'element' naming on renders/themes.

/**
 * Provides a render element for a pager.
 *
 * The pager must be initialized with a call to pager_default_initialize() in
 * order to render properly. When used with database queries, this is performed
 * for you when you extend a select query with
 * \Drupal\Core\Database\Query\PagerSelectExtender.
 *
 * Properties:
 * - #element: (optional, int) The pager ID, to distinguish between multiple
 *   pagers on the same page (defaults to 0).
 * - #parameters: (optional) An associative array of query string parameters to
 *   append to the pager.
 * - #quantity: The maximum number of numbered page links to create (defaults
 *   to 9).
 * - #tags: (optional) An array of labels for the controls in the pages.
 * - #route_name: (optional) The name of the route to be used to build pager
 *   links. Defaults to '<none>', which will make links relative to the current
 *   URL. This makes the page more effectively cacheable.
 *
 * @code
 * $build['pager'] = [
 *   '#type' => 'pager',
 * ];
 * @endcode
 *
 * @RenderElement("pager")
 */

3. Actually I think that alternative implementations SHOULD keep the 0-index internally, and only use a 1-index version of the pege number in href links.

4. OK, done
5. Renamed the method.
6. OK, done
7, 8, 10. OK
9. OK, done
11. let's do when we have maintainer's feedback on the patch here

#75

1, 2 . OK done
3. Understand but OTOH we're not in the business of testing the Request object here

mondrake’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 76: 2044435-76.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review

#76 is green.

daffie’s picture

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

I think the patch looks great now and almost ready for RTBC. I have one point left:

+++ b/core/tests/Drupal/Tests/Core/Pager/PagerFactoryTest.php
@@ -0,0 +1,130 @@
+    $this->assertSame(4, $pager->getElement());
...
+    $this->assertSame(6, $pager->getElement());

I think we had a miscommunication. The change I was looking for was $this->assertSame([4, 6], array_keys($this->pagerFactory->all()));. We are testing the method PagerFactory::all().

Thank you for explaining why we cannot rename the element part.

I have added a change record.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new7.69 KB
new46.54 KB

@daffie thanks a lot for review and for adding the CR.

Here I am doing #80, plus added an additional parameter in the container to be able to specify the querystring parameter to be used by the pager to prepare the URL, i.e. the 'page' piece. Alternative implementation may want to use a different string for that (see the discussion for 0-index vs 1-index). Looking ahead at followups, caching will retrieve via 'page' hardcoded ATM, so I added a method from the factory that tells what the string in use is.

Followups suggested to remove all usages of deprecated variables/functions:

  1. Cleanup and move docs from pager.inc to the new classes
  2. Taxonomy OverviewForm using pager_* globals #2770649: [PP-1] Refactor OverviewTerms form to use the pager.factory service
  3. views/pager/SqlBase using pager_* globals
  4. Refactor Cache/Context/PagersCacheContext to use the new service
  5. Refactor Entity/Query/QueryBase to use the new service
  6. Refactor Database/Query/PagerSelectExtender to use the new service
  7. Refactor core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController to use the new service

Status: Needs review » Needs work

The last submitted patch, 81: 2044435-81.patch, failed testing.

mondrake’s picture

StatusFileSize
new718 bytes
new46.71 KB

Trying to fix the test failure.

mondrake’s picture

Status: Needs work » Needs review

The last submitted patch, 81: 2044435-81.patch, failed testing.

mondrake’s picture

#83 is green.

daffie’s picture

Status: Needs review » Reviewed & tested by the community

It all looks good to me. All my remarks are explained or addressed, so RTBC from me.

Good work mondrake!

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 83: 2044435-83.patch, failed testing.

daffie’s picture

Status: Needs work » Reviewed & tested by the community

Back to RTBC.

dawehner’s picture

Status: Reviewed & tested by the community » Needs work

Sorry to move it back to needs work, but given we can improve method names and what not, I think its not the worst idea to move it to to needs work :(
It doesn't mean at all that the patch is wrong, but rather it needs some additional work.

  1. +++ b/core/core.services.yml
    @@ -41,6 +41,9 @@ parameters:
    +    class: Drupal\Core\Pager\Pager
    

    It is a bit weird to make the class configurable, as we don't do it anywhere else.

  2. +++ b/core/lib/Drupal/Core/Pager/Pager.php
    @@ -0,0 +1,317 @@
    +    // Gets the element's current page from the current request.
    +    $page = $this->getCurrentPage();
    

    This comment is also a bit not helpful. Maybe just skip it is not too bad.

  3. +++ b/core/lib/Drupal/Core/Pager/Pager.php
    @@ -0,0 +1,317 @@
    +    // We calculate the total of pages as ceil(items / limit).
    ...
    +      ->setTotalPages((int) ceil($this->getTotalItems() / $limit))
    

    At least for me this comment doesn't really add value. It would be otherwise better to explain WHY the calculation is done in that way.

  4. +++ b/core/lib/Drupal/Core/Pager/Pager.php
    @@ -0,0 +1,317 @@
    +  public function getLink($page) {
    +    $options = ['query' => $this->getLinkQueryParameters($page)];
    +    return Url::fromRoute($this->getRouteName(), $this->getRouteParameters(), $options);
    +  }
    

    This method is returning a URL, so it should be named getUrl() or maybe even toUrl() which seems to be a pattern we use more and more.

  5. +++ b/core/lib/Drupal/Core/Pager/PagerFactoryInterface.php
    @@ -0,0 +1,76 @@
    +  /**
    +   * Returns the current array of pager objects.
    +   *
    +   * @return \Drupal\Core\Pager\Pager[]
    +   *   The array of pager objects, indexed by their element.
    +   */
    +  public function all();
    

    Can we clarify what current means on the interface? Is it every initialized one or is it every available in the current query parameter of the request?

  6. +++ b/core/lib/Drupal/Core/Pager/PagerFactoryInterface.php
    @@ -0,0 +1,76 @@
    +  public function getQueryStringParameter();
    ...
    +  public function getCurrentRequestQueryParameter($parameter);
    

    It is a bit odd that we need both.

  7. +++ b/core/lib/Drupal/Core/Pager/PagerInterface.php
    @@ -0,0 +1,225 @@
    +   * Gets the route name for this pager.
    +   *
    +   * @return string
    +   *   The route name.
    +   */
    +  public function getRouteName();
    +
    +  /**
    +   * Sets the route name for this pager.
    +   *
    +   * @param string $route_name
    +   *   The route name.
    +   *
    +   * @return \Drupal\Core\Pager\PagerInterface
    +   *   The Pager object.
    +   */
    +  public function setRouteName($route_name);
    +
    +  /**
    +   * Gets the route parameters for this pager.
    +   *
    +   * @return string[]
    +   *   The route parameters.
    +   */
    +  public function getRouteParameters();
    +
    +  /**
    +   * Sets the route parameters for this pager.
    +   *
    +   * @param string[] $route_parameters
    +   *   The route parameters.
    +   *
    +   * @return \Drupal\Core\Pager\PagerInterface
    +   *   The Pager object.
    +   */
    +  public function setRouteParameters(array $route_parameters);
    

    We could maybe replace all those methods with a method toUrl($route_name, $route_parameters, $options), which then also sets the query parameters of the page properly

  8. +++ b/core/lib/Drupal/Core/Pager/PagerInterface.php
    @@ -0,0 +1,225 @@
    +   * @return \Drupal\Core\Url
    ...
    +  public function getLink($page);
    

    As written before, I think getLink is not the best name for it :)

  9. +++ b/core/tests/Drupal/Tests/Core/Pager/PagerFactoryTest.php
    @@ -0,0 +1,138 @@
    +    // @todo drop globals in D9.
    +    global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
    +    $this->backupGlobals = [$pager_page_array, $pager_total, $pager_total_items, $pager_limits];
    +
    ...
    +  /**
    +   * {@inheritdoc}
    +   */
    +  protected function tearDown() {
    +    // @todo drop globals in D9.
    +    global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
    +    list($pager_page_array, $pager_total, $pager_total_items, $pager_limits) = $this->backupGlobals;
    +  }
    

    Can't use use https://phpunit.de/manual/5.5/en/appendixes.annotations.html#appendixes.annotations.backupGlobals directly?

  10. +++ b/core/tests/Drupal/Tests/Core/Pager/PagerFactoryTest.php
    @@ -0,0 +1,138 @@
    +    $this->assertSame(0, $this->pagerFactory->GetFirstAvailableElement());
    ...
    +    $this->assertSame(1, $this->pagerFactory->GetFirstAvailableElement());
    ...
    +    $this->assertSame(5, $this->pagerFactory->GetFirstAvailableElement());
    

    PHP is case insensitive, but still let's use getFirstAvailableElement()

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new19.27 KB
new43.94 KB

Thanks for your review @dawehner.

1. See #73.1, #38, #40. My intention is to be able to swap the implementation of the Pager class without having to swap PagerFactory too. If there are better ways, let me know. I will surely use this in contrib.
2. OK
3. Was there in pager_default_initialize, changed.
4, 7, 8. OK, done
5. Changed the doc
6. One method returns the value of the parameter in a specific request (e.g. 0,1,6), the other the name of the parameter (i.e., 'page'). Changed the method name of the latter to avoid confusion.
9. I thought about that. but that would apply for any global. Since we know the exact names of the globals involved, I thought it is more accurate to make the test excluding those only. No problem to change that if you insist.
10. OK

The last submitted patch, 91: interdiff_83-91.patch, failed testing.

mondrake’s picture

Sorry, I misnamed the interdiff in #91.

dawehner’s picture

1. See #73.1, #38, #40. My intention is to be able to swap the implementation of the Pager class without having to swap PagerFactory too. If there are better ways, let me know. I will surely use this in contrib.

Well, its a pattern we don't use anywhere else in core. A reason why I personally believe its not that useful is that if you replace a class, you also often need new dependencies. When this is the case, the container parameter doesn't help you.

9. I thought about that. but that would apply for any global. Since we know the exact names of the globals involved, I thought it is more accurate to make the test excluding those only. No problem to change that if you insist.

It just feels safer to let PHPUnit handle it directly, as they maybe more things, for example when a test fails ... one never knows.

mondrake’s picture

Status: Needs review » Needs work

...if you replace a class, you also often need new dependencies. When this is the case, the container parameter doesn't help you.

That's why in the patch I am passing the entire container to Pager::create so that the class itself can load any service it needs.

Anyway. This is minor, workaround is possible in contrib. I will post a new patch without this and with the backupGlobals change.

dawehner’s picture

Thank you @mondrake!

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new8.44 KB
new42.21 KB

Here we go, let's see testbot.

Status: Needs review » Needs work

The last submitted patch, 97: 2044435-97.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new1005 bytes
new42.26 KB

Strange, let's see this

Status: Needs review » Needs work

The last submitted patch, 99: 2044435-99.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new1.94 KB
new42.67 KB

Looks like backupGlobals disable on class level is not enough

Status: Needs review » Needs work

The last submitted patch, 101: 2044435-101.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new2.44 KB
new42.46 KB

... and that values of global variables are not cleaned between different tests within a same test class ...

dawehner’s picture

@mondrake
Sorry for causing that trouble :(

mondrake’s picture

@dawehner no problem, it's all good learning. Anything left?

daffie’s picture

@mondrake: Can you tell me how you are going to override the Pager class in contrib?

mondrake’s picture

@daffie we will need to override the PagerFactory class with another one with a call to $container->set in the contrib module service provider class. That class will extend from core PagerFactory. In that class we will change the ::create method from using the Pager class to the one we need. (theoretically, have not done that yet, but that should do:))

mondrake’s picture

daffie’s picture

Status: Needs review » Needs work

@mondrake: Thank you for the explanation.

The patch is almost RTBC for me. I do have some minor documentation remarks:

  1. +++ b/core/includes/pager.inc
    @@ -123,18 +123,15 @@ function pager_find_page($element = 0) {
    -  // We calculate the total of pages as ceil(items / limit).
    

    Can we change the test to be the same as with Pagar::init().

  2. +++ b/core/lib/Drupal/Core/Pager/Pager.php
    @@ -0,0 +1,249 @@
    +    // page ($limit). We take the nearest integer to accomodate any remainder.
    

    Ceil() is not the nearest integer, the next integer by rounding up the calculated value.

  3. +++ b/core/tests/Drupal/Tests/Core/Pager/PagerTest.php
    @@ -0,0 +1,129 @@
    + * @todo remove the backupGlobals directive once $pager_page_array,
    + *   $pager_total, $pager_total_items, $pager_limits globals have been removed.
    + *
    + * @backupGlobals disabled
    

    We are not using the pager globals in the PagetTest, so these comments are not nessecary.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new736 bytes
new42.47 KB

#109

1. ? we are removing that comment from pager.inc with this patch. Or I do not understand?
2. OK, thanks, done
3. Actually we are using the globals in the test, indirectly, when doing

+++ b/core/tests/Drupal/Tests/Core/Pager/PagerTest.php
@@ -0,0 +1,129 @@
+
+    // Pager for element 6.
+    $this->pager = Pager::create($container, 6);
+

we are calling Pager::create which uses the globals

+++ b/core/lib/Drupal/Core/Pager/Pager.php
@@ -0,0 +1,249 @@
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, $element) {
+    $instance = new static(
+      $container->get('pager.factory')
+    );
+
+    // Set the pager element.
+    $instance->element = $element;
+
+    // @todo: Start of BC layer. This maps the pager properties to the global
+    // pager variables of D8. When removing the global variables in D9, remove
+    // the following lines.
+    global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
+    if (!isset($pager_page_array[$element])) {
+      $pager_page_array[$element] = NULL;
+    }
+    $instance->currentPage = &$pager_page_array[$element];
+    if (!isset($pager_total[$element])) {
+      $pager_total[$element] = NULL;
+    }
+    $instance->totalPages = &$pager_total[$element];
+    if (!isset($pager_total_items[$element])) {
+      $pager_total_items[$element] = NULL;
+    }
+    $instance->totalItems = &$pager_total_items[$element];
+    if (!isset($pager_limits[$element])) {
+      $pager_limits[$element] = NULL;
+    }
+    $instance->limit = &$pager_limits[$element];
+    // @todo: End of BC layer.
+
+    return $instance;
daffie’s picture

Status: Needs review » Reviewed & tested by the community

@mondrake: For 110.1 you are right and my mistake. For 110.3, thank you for explaining.

The patch look great again.
All remarks of @dawehner are addressed. Thank you for your help @dawehner!

For me it is back to RTBC.

Great work @mondrake!

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 110: 2044435-110.patch, failed testing.

mondrake’s picture

Assigned: mondrake » Unassigned
Status: Needs work » Reviewed & tested by the community

Looks like #112 was a random failure.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 110: 2044435-110.patch, failed testing.

daffie’s picture

Status: Needs work » Reviewed & tested by the community

Testbot problems

alexpott’s picture

Status: Reviewed & tested by the community » Needs review
+++ b/core/includes/pager.inc
@@ -173,105 +173,93 @@ function pager_get_query_parameters() {
-  // Calculate various markers within this pager piece:
+  // Calculate various markers within this pager list:
   // Middle is used to "center" pages around the current page.
-  $pager_middle = ceil($quantity / 2);
-  // current is the page we are currently paged to.
-  $pager_current = $pager_page_array[$element] + 1;
-  // first is the first page listed by this pager piece (re quantity).
-  $pager_first = $pager_current - $pager_middle + 1;
-  // last is the last page listed by this pager piece (re quantity).
-  $pager_last = $pager_current + $quantity - $pager_middle;
-  // max is the maximum page number.
-  $pager_max = $pager_total[$element];
+  $list_middle = ceil($quantity / 2);
+  // Current is the page we are currently paged to.
+  $list_current = $pager->getCurrentPage() + 1;
+  // First is the first page listed by this pager list (re quantity).
+  $list_first = $list_current - $list_middle + 1;
+  // Last is the last page listed by this pager list (re quantity).
+  $list_last = $list_current + $quantity - $list_middle;
   // End of marker calculations.
 
   // Prepare for generation loop.
-  $i = $pager_first;
-  if ($pager_last > $pager_max) {
+  $i = $list_first;
+  if ($list_last > $pager->getTotalPages()) {
     // Adjust "center" if at end of query.
-    $i = $i + ($pager_max - $pager_last);
-    $pager_last = $pager_max;
+    $i = $i + ($pager->getTotalPages() - $list_last);
+    $list_last = $pager->getTotalPages();
   }
   if ($i <= 0) {
     // Adjust "center" if at start of query.
-    $pager_last = $pager_last + (1 - $i);
+    $list_last = $list_last + (1 - $i);
     $i = 1;
   }
   // End of generation loop preparation.

This is really icky change. The change from pager_ to list_ doesn't help understanding at all. Is there no way that more of the logic here can be encapsulated in the class? In an ideal would the template_preprocess_pager would not exist or have the minimum logic possible.

mondrake’s picture

Status: Needs review » Reviewed & tested by the community

@alexpott thanks for feedback

Is there no way that more of the logic here can be encapsulated in the class?

IMHO I will not move code from preprocess to the Pager class. The classes introduced here are about managing paging in 'abstract' i.e. independently from what of a pager we want to render on a page. With the classes here we answer 'how many pagers on a request', 'how many items/pages is pager x managing', 'how many items per page has pager y', 'give me a link to go to page z for pager w', etc.

Whether we want to render
<< First < Previous ... 8 9 10 11 12 ... Next > Last >> (like full pager) or
< Page 10 > (like views mini pager)

is a matter of styling/theming, and may differ significantly from theme to theme, so I would not move the theme specific code to the 'general' classes.

In an ideal would the template_preprocess_pager would not exist or have the minimum logic possible.

In the Pagerer contrib module that's more or less what I am doing - introducing a plugin system so that different 'pager styles' can all be accommodated through a single theme - the theme preprocess in that case handles over preprocess to each 'style' plugin.

We can explore this for core if desired, but it'd be a separate issue IMO - not about converting pager.inc to a service but to provide a flexible mechanism for styling pagers.

That said, I can easily revert the variables' name change back from list_* to pager_*. Please set to CNW if we need that.

daffie’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new1.89 KB
new40.89 KB

Talked to @alexpott on IRC and wants the changes to core/includes/pager.inc changed back as he stated in comment #116. This patch does that.

mondrake’s picture

+++ b/core/includes/pager.inc
@@ -188,27 +188,29 @@ function template_preprocess_pager(&$variables) {
-  $list_middle = ceil($quantity / 2);
-  // Current is the page we are currently paged to.
-  $list_current = $pager->getCurrentPage() + 1;
-  // First is the first page listed by this pager list (re quantity).
-  $list_first = $list_current - $list_middle + 1;
-  // Last is the last page listed by this pager list (re quantity).
-  $list_last = $list_current + $quantity - $list_middle;
+  $pager_middle = ceil($quantity / 2);
+  // current is the page we are currently paged to.
+  $pager_current = $pager_page_array[$element] + 1;
+  // first is the first page listed by this pager piece (re quantity).
+  $pager_first = $pager_current - $pager_middle + 1;
+  // last is the last page listed by this pager piece (re quantity).
+  $pager_last = $pager_current + $quantity - $pager_middle;
+  // max is the maximum page number.
+  $pager_max = $pager_total[$element];

@daffie this won't work because you are re-introducing usage of global variables.

I suggest to just change back $list_* to *$pager_*

mondrake’s picture

Assigned: Unassigned » mondrake

Working on it

mondrake’s picture

StatusFileSize
new41.07 KB
new4.2 KB

Here it comes. Sorry @daffie to step on your toes, and thanks for checking with @alexpott.

Interdiff still against #110.

The last submitted patch, 118: 2044435-118.patch, failed testing.

daffie’s picture

Status: Needs review » Reviewed & tested by the community

@mondrake: You did not step on my toes. I should have looked better add the patch. Thanks for fixing it. Just happy that we got this issue moving again.

The changes that @alexpott wanted are done.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/lib/Drupal/Core/Pager/Pager.php
@@ -0,0 +1,250 @@
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, $element) {
+    $instance = new static(
+      $container->get('pager.factory')
+    );
+
+    // Set the pager element.
+    $instance->element = $element;
+
+    // @todo: Start of BC layer. This maps the pager properties to the global
+    // pager variables of D8. When removing the global variables in D9, remove
+    // the following lines.
+    global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
+    if (!isset($pager_page_array[$element])) {
+      $pager_page_array[$element] = NULL;
+    }
+    $instance->currentPage = &$pager_page_array[$element];
+    if (!isset($pager_total[$element])) {
+      $pager_total[$element] = NULL;
+    }
+    $instance->totalPages = &$pager_total[$element];
+    if (!isset($pager_total_items[$element])) {
+      $pager_total_items[$element] = NULL;
+    }
+    $instance->totalItems = &$pager_total_items[$element];
+    if (!isset($pager_limits[$element])) {
+      $pager_limits[$element] = NULL;
+    }
+    $instance->limit = &$pager_limits[$element];
+    // @todo: End of BC layer.
+
+    return $instance;

+++ b/core/lib/Drupal/Core/Pager/PagerFactory.php
@@ -0,0 +1,138 @@
+    return Pager::create($this->container, $element);

This is odd... we're borrowing from the \Drupal\Core\DependencyInjection\ContainerInjectionInterface but we're not implementing it. Why is this not just all in the constructor? Seems unnecessarily complex and if someone did just used the constructor atm it wouldn't work. Especially since this method will be largely redundant once the bc layer is removed.

mondrake’s picture

Status: Needs work » Needs review

Thanks @alexpott. Changed to not use ContainerInterface and moved BC layer into constructor.

mondrake’s picture

StatusFileSize
new7.4 KB
new39.54 KB
daffie’s picture

Status: Needs review » Needs work

Looks good to me.
All wishes of @alexpott are implemented.
For me it is RTBC.

@alexpott: Thank you for reviewing.

daffie’s picture

Status: Needs work » Reviewed & tested by the community

Wrong status.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. Sorry the reviews keep on coming in drips and drabs - I just keep on looking at this patch and feel uneasy about it and then find more things that concern me. This time it is the mutability of the pager objects once they've been constructed. It just feels wrong that they should be so changeable.
  2. +++ b/core/lib/Drupal/Core/Pager/Pager.php
    @@ -0,0 +1,238 @@
    +      $pager_page_array[$element] = NULL;
    ...
    +      $pager_total[$element] = NULL;
    ...
    +      $pager_total_items[$element] = NULL;
    ...
    +      $pager_limits[$element] = NULL;
    

    These all need to be set up correctly for BC to be maintained but it does not look as thought they are.

  3. +++ b/core/lib/Drupal/Core/Pager/Pager.php
    @@ -0,0 +1,238 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function init($total, $limit) {
    +    // The total number of pages to be managed by the pager is given by the
    +    // total number of items ($total) divided by the number of items in each
    +    // page ($limit). We round up to the next integer to accommodate any
    +    // remainder.
    +    $this
    +      ->setTotalItems($total)
    +      ->setTotalPages((int) ceil($this->getTotalItems() / $limit))
    +      ->setCurrentPage(max(0, min($this->getCurrentPage(), ((int) $this->getTotalPages() - 1))))
    +      ->setLimit($limit);
    +    return $this;
    +  }
    

    Why is this not part of construction? What's the point of an uninitialised pager?

  4. +++ b/core/lib/Drupal/Core/Pager/Pager.php
    @@ -0,0 +1,238 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function setCurrentPage($page) {
    +    $this->currentPage = (int) $page;
    +    return $this;
    +  }
    

    This should validate that it is a possible page.

  5. +++ b/core/lib/Drupal/Core/Pager/Pager.php
    @@ -0,0 +1,238 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function setTotalPages($total_pages) {
    +    $this->totalPages = (int) $total_pages;
    +    return $this;
    +  }
    ...
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function setTotalItems($total_items) {
    +    $this->totalItems = (int) $total_items;
    +    return $this;
    +  }
    ...
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function setLimit($limit) {
    +    $this->limit = (int) $limit;
    +    return $this;
    +  }
    

    I don't think this should be changeable after construction.

mondrake’s picture

Assigned: mondrake » Unassigned

I need to think in depth about #129. In the meantime, if anyone want to take this on, feel free.

Some remarks:

I don't think this should be changeable after construction.

In theory, I agree. But here we have the $pager_* global variables in the equation, at least until D9. Contrib/custom code can alter them directly without using the API, either creating new pager elements outside of the Pager objects, or chaning settings of instantiated ones. These methods are there to allow changing them through the API and avoiding direct calls.

What's the point of an uninitialised pager?

See #73.5 - you may have the case where you need to know the current page before to calculate the total items to be paged. See #2770649: [PP-1] Refactor OverviewTerms form to use the pager.factory service for example. Need to think if there is an alternative.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new25.24 KB
new42.09 KB

#129:

1) made the Pager immutable after instantiation. We now have a PagerFactory::set method that creates new pagers, and a PagerFactory::get method to retrieve the Pager object during theme preprocessing.
2) removed Pager::init, ::setCurrentPage, ::setTotalPages, ::setLimit and ::setTotalItems from the interface.
3) Fixed a bug that prevents taxonomy/src/Form/OverviewTerms to produce a pager, at least with this patch applied: the global $pager_limits was not set by code. This class uses the globals directly instead of the current API; refactoring in follow-up #2770649: [PP-1] Refactor OverviewTerms form to use the pager.factory service
4) adjusted PHPUnit tests.

It will be interesting to refactor views/pager/SqlBase that also manages globals directly. But that will be a follow-up.

daffie’s picture

Status: Needs review » Needs work

Looks good @mondrake. I think that that all points of @alexpott are addressed. I do have some other points:

  1. +++ b/core/includes/pager.inc
    @@ -123,18 +120,15 @@ function pager_find_page($element = 0) {
    -  $pager_total_items[$element] = $total;
    -  $pager_total[$element] = ceil($pager_total_items[$element] / $limit);
    -  $pager_page_array[$element] = max(0, min($page, ((int) $pager_total[$element]) - 1));
    -  $pager_limits[$element] = $limit;
    

    Here are the pager global variables set and we are removing that. The problem for me is that AFAIK in the Pager service they are not set anywhere.

  2. +++ b/core/lib/Drupal/Core/Pager/Pager.php
    @@ -0,0 +1,233 @@
    +      ->setTotalPages((int) ceil($this->getTotalItems() / $limit))
    ...
    +      ->setLimit($limit);
    

    Can we move the line ->setLimit($limit); 2 lines up. So that we can replace $limit with $this->getLimit() in the other line.

  3. +++ b/core/lib/Drupal/Core/Pager/PagerFactory.php
    @@ -0,0 +1,158 @@
    +      $this->pagers[$element] = $this->create($element, $total, $limit);
    

    Why are we using the protected method create? Can we not just call new Pager() directly. Can we then remove the method create?

  4. +++ b/core/lib/Drupal/Core/Pager/PagerFactory.php
    @@ -0,0 +1,158 @@
    +    // max(array_keys($this->pagers)) directly.
    

    Nitpick: should be "// max(array_keys($this->pagers)) + 1 directly."

  5. +++ b/core/modules/taxonomy/src/Form/OverviewTerms.php
    @@ -77,7 +77,7 @@ public function getFormId() {
    -    global $pager_page_array, $pager_total, $pager_total_items;
    +    global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
    
    @@ -173,6 +173,7 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular
    +    $pager_limits[0] = $page_increment;
    

    I am missing why this is in this patch. If you found a bug in OverviewTerms, then create a new issue for it. It will then also need testing for the bug.

mondrake’s picture

Status: Needs work » Needs review

Thank you @daffie for your reviews and continued support, really appreciated.

1. They are. The magic lies in this BC layer code:

+++ b/core/lib/Drupal/Core/Pager/Pager.php
@@ -0,0 +1,233 @@
+    // @todo: Start of BC layer. This maps the pager properties to the global
+    // pager variables of D8. When removing the global variables in D9, remove
+    // the following lines.
+    global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
+    $this->currentPage = &$pager_page_array[$element];
+    $this->totalPages = &$pager_total[$element];
+    $this->totalItems = &$pager_total_items[$element];
+    $this->limit = &$pager_limits[$element];
+    // @todo: End of BC layer.

By using the & operator, we are instructing PHP to actually read/write the values of the Pager properties from/to the corresponding global variable array element. In other words: by doing $this->limit = 50 we are actually doing $pager_limits[$this->element] = 50.

2. Done, but see point 5 below.

3. This is just there for DX purposes: it's much more convenient to have this one line method in case you override the class (and I am planning to do so in contrib), as you would just have to replace this method with instantiating the PagerInterface class you need instead. Otherwise you'd have to replace the entire set method, but this may become difficult to keep in sync if it will change in the future.

4. Done, thank you.

5. This is actually a problem only since we have changed Pager to be immutable. It's not a bug of OverviewTerms in itself, it's related to the fact that that form handles directly global $pager_* variables, and how we treat such case. This gives me the opportunity to explain how the BC layer works:

  • OverviewTerms deals with the $pager_* variables directly, without using the existing API pager_default_initialize, for element 0
  • this means that no Pager object gets instantiated
  • but when template_preprocess_pager starts processing the pager, it get the Pager object for element 0 from the pager.factory service
  • since a Pager object for this element is not existing, but the $pager_* variables have values for this element, the BC layer kicks-in:
    +++ b/core/lib/Drupal/Core/Pager/PagerFactory.php
    @@ -0,0 +1,158 @@
    +  public function get($element) {
    +    // @todo: BC layer. When removing the globals, remove the following and
    +    // return $this->pagers directly.
    +    global $pager_total, $pager_total_items, $pager_limits;
    +    if (!isset($this->pagers[$element]) && isset($pager_total[$element])) {
    +      $this->set($element, $pager_total_items[$element], $pager_limits[$element]);
    +    }
    +    // @todo: end of BC layer.
    +    return isset($this->pagers[$element]) ? $this->pagers[$element] : NULL;
    +  }
    
  • this will create the missing Pager object, with the values stored in the global $pager_* variables, and then allow template_preprocess_pager to use it for its own purposes.

Now, in the specific of OverviewTerms, the global $pager_* variables are set with the exception of $pager_limits[0]. This causes a division by zero in the code that is calculating the total pages: ->setTotalPages((int) ceil($this->getTotalItems() / $limit))

But I agree, we should work around this rather than fixing it, since other code in contrib/custom may do the same and we cannot expect that code to change if we are to provide BC.

So here I reverted that hunk, and made sure that if we have a 0 limit or no limit at all, we do not calculate $this->totalPages. Since $pager_total[$element] is already set by calling code, we are fine.

In any case, we should follow-up to remove usage of the $pager_ globals from the code base. For OverviewTerms, this is done in #2770649: [PP-1] Refactor OverviewTerms form to use the pager.factory service. See the do-not-test patch in #9, that's very simple once we will have this in.

mondrake’s picture

StatusFileSize
new41.04 KB
new3.95 KB

and the new patch...

daffie’s picture

Status: Needs review » Needs work

@mondrake: Thank for your new patch and your responce in comment #133/#134. For points 2-5 thank for fixing/explaining.

  1. For point 1: I do not agree with you or I am missing something completely.
    If I take the line $this->limit = &$pager_limits[$element]; then AFAIK the value $this->limit is set to &$pager_limits[$element]; and that is it. The value of &$pager_limits[$element]; does not change in this line. For me the point from comment #132.1 still stands.
  2. In this patch we creating the Pager service and I think we do not need the changes in core/modules/views/views.theme.inc for that.
mondrake’s picture

Issue tags: +Needs issue summary update
StatusFileSize
new38.99 KB
new2.05 KB

@daffie re. #135:

1. Using the & ampersand we set up a reference, see http://php.net/manual/en/language.references.whatdo.php. This means that $this->limit and $pager_limits[$element] share the same storage.

  • $this->limit = &$pager_limits[$element]; sets the reference in __construct
  • then we call $this->setLimit($limit), still from __construct
  • +++ b/core/lib/Drupal/Core/Pager/Pager.php
    @@ -0,0 +1,233 @@
    +  protected function setLimit($limit) {
    +    $this->limit = (int) $limit;
    +    return $this;
    +  }
    

    sets the $limit property of $this, which is mapped to $pager_limits[$element].

Believe me, if this was not the case we would have tons of failures :)

2. Fine, done here, we can do in follow-up but then please update the issue summary which is still declaring

4) [...] template_preprocess_views_mini_pager() use the new service methods instead of directly accessing the globals.

mondrake’s picture

Status: Needs work » Needs review
daffie’s picture

Issue summary: View changes
Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs issue summary update

Now do I get it. In this part are the variables linked to each other:

+++ b/core/lib/Drupal/Core/Pager/Pager.php
@@ -0,0 +1,233 @@
+    $this->currentPage = &$pager_page_array[$element];
+    $this->totalPages = &$pager_total[$element];
+    $this->totalItems = &$pager_total_items[$element];
+    $this->limit = &$pager_limits[$element];

And in the next part are the values set:

+++ b/core/lib/Drupal/Core/Pager/Pager.php
@@ -0,0 +1,233 @@
+    $this->setTotalItems($total)->setLimit($limit);
+    if ($this->getLimit() !== 0) {
+      $this->setTotalPages((int) ceil($this->getTotalItems() / $this->getLimit()));
+    }
+    $this->setCurrentPage(max(0, min($this->getCurrentPage(), ((int) $this->getTotalPages() - 1))));

Sorry for being a bit slow.

I am happy with the patch now and all of @alexpott points are addressed. So back to RTBC.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/includes/pager.inc
@@ -143,13 +137,16 @@ function pager_default_initialize($total, $limit, $element = 0) {
+ *
+ * @deprecated as of Drupal 8.3.x, will be removed before Drupal 9.0.0.
+ *   Use
+ *   \Drupal::service('pager.factory')->getCurrentRequestQueryParameters(['page'])
+ *   instead.
  */
 function pager_get_query_parameters() {
-  $query = &drupal_static(__FUNCTION__);
-  if (!isset($query)) {
-    $query = UrlHelper::filterQueryParameters(\Drupal::request()->query->all(), array('page'));
-  }
-  return $query;
+  $pager_factory = \Drupal::service('pager.factory');
+  $pager_parameter = $pager_factory->getPagerParameterName();
+  return $pager_factory->getCurrentRequestQueryParameters([$pager_parameter]);

+++ b/core/lib/Drupal/Core/Pager/PagerFactory.php
@@ -0,0 +1,159 @@
+  public function getCurrentRequestQueryParameter($parameter) {
...
+  public function getCurrentRequestQueryParameters(array $filter = []) {

Are these really necessary as public API it feel totally wrong to be exposing this here. The only external use is in page_get_query_paramaters() and afaics we should have a method that does this on the factory and not these other methods. Also you are removing the static cache - why? Furthermore if we moved all of this internal to the factory it wouldn't be necessary to expose the getPagerParameterName() either. Hence the entire implementation would become internal to the factory and the implementation details would not be being exposed by the factory interface.

mondrake’s picture

StatusFileSize
new36.76 KB
new10.84 KB

Thanks @alexpott.

#139: all done, with exception of getPagerParameterName() that I suggest to keep on the interface. Other code needs to know what is the parameter used for 'page', that alternative implementations may change. See for example Cache/Context/PagersCacheContext. In follow-ups of this issue we should remove hardcoded 'page' strings with calls to getPagerParameterName().

mondrake’s picture

Status: Needs work » Needs review
daffie’s picture

Status: Needs review » Reviewed & tested by the community

All points of comment #139 from @alexpott are addressed. And it all looks good to me. So back to RTBC.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 140: 2044435-140.patch, failed testing.

daffie’s picture

Status: Needs work » Reviewed & tested by the community

Back to RTBC.

pwolanin’s picture

bump - has been ready for 3 months?

pwolanin’s picture

+++ b/core/includes/pager.inc
@@ -23,15 +21,14 @@
+  return \Drupal::service('pager.factory')->findCurrentPage($element);

The service name doesn't seem right - it's not a factory if you are working directly with it? Looks like it more like a pager manager?

Also - should $element default to 0 in some or all of the methods? Seems you are relying on the the functional wrappers for that. The name "element" also seems a little odd and confusing in terms of form or html elements. why node something clearer like $pager_id?

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 140: 2044435-140.patch, failed testing.

daffie’s picture

Status: Needs work » Reviewed & tested by the community

The testbot failures are not related to this patch. So, back to RTBC.

mondrake’s picture

My comments on #146:

The service name doesn't seem right - it's not a factory if you are working directly with it? Looks like it more like a pager manager?

This patch has been sitting in the RTBC queue for the most part of the last three months, and this was not raised. Maybe let's see further opinion before making a change. BTW image.factory also has methods called directly.

Also - should $element default to 0 in some or all of the methods? Seems you are relying on the the functional wrappers for that.

IMHO we should move away from defaulting $element to 0 - this causes confusion I think when people want to place multiple pagers on a page. Generally I think code should not have a problem to know which is the element it needs to use.

The name "element" also seems a little odd and confusing in terms of form or html elements. why node something clearer like $pager_id?

This question was asked already in #74.2 and answered in #76.2.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 140: 2044435-140.patch, failed testing.

mondrake’s picture

Status: Needs work » Reviewed & tested by the community

#140 is still green after some unrelated test failures. Back to RTBC

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 140: 2044435-140.patch, failed testing.

daffie’s picture

Status: Needs work » Reviewed & tested by the community

The testbot failures are not related to this patch. So back to RTBC.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 140: 2044435-140.patch, failed testing.

mondrake’s picture

Status: Needs work » Reviewed & tested by the community

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 140: 2044435-140.patch, failed testing.

daffie’s picture

Status: Needs work » Reviewed & tested by the community
catch’s picture

Status: Reviewed & tested by the community » Needs work

I agree with @pwolanin's review on factory vs. not.

+++ b/core/lib/Drupal/Core/Pager/PagerFactory.php
@@ -0,0 +1,146 @@
+  public function getPagerParameterName() {

The pager parameter name should be a constant on the interface rather than a method. Then the various places hard-coding 'page' now would just have to use the constant rather than get the service. I thought I'd posted this weeks ago, but apparently not :( I think that would resolve all of @alexpott's concerns in #139 then, although I haven't discussed this recently with him.

mondrake’s picture

#158:

1. That is 'pager.manager' and PagerManager / PagerManagerInterface in place of 'pager.factory' and PagerFactory / PagerFactory Interface, right?

2. I disagree on setting a const on the PagerManagerInterface for the 'page' string. This would anyway force usage of 'page' all across. My intent was to be able to have an alternative string to be used in a contrib module, but with all the rest of core code still being able to use it.
For example: instead of a URL ?page=1,,3, have an URL ?pagx=2,,4 (in this case, to have 1-based page indexes on the URL, see #1818040: Pager should start counting from 1, not 0).

mondrake’s picture

Assigned: Unassigned » mondrake
Status: Needs work » Needs review
StatusFileSize
new20.72 KB
new36.79 KB

This is doing just the rename as per #159.1. Let's have this then I will suggest a compromise for #159.2.

mondrake’s picture

Status: Needs review » Needs work
mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new10.95 KB
new38.26 KB

Let's see if this passes, comments later.

mondrake’s picture

Assigned: mondrake » Unassigned

#162:

  • introduces a PagerManagerInterface::PAGER_QUERY_PARAMETER constant value equal to 'page';
  • changes scope of PagerManagerInterface::getPagerParameterName from public to protected, removing from PagerManagerInterface. I keep the method so that alternative implementation of the service inheriting from PagerManager can simply change that method to change the parameter name without having to adjust all other methods too;
  • moves ::getLinkQueryParameters from PagerInterface to PagerManagerInterface, so that Pager does not need to know the query parameter name to use;
  • introduces a PagerManagerInterface::getPagerQueryParameter method to be able to retrieve the entire value of the pager query parameter from the URL (not the page specific for the element), and uses it in Cache\Context\PagersCacheContext.

Status: Needs review » Needs work

The last submitted patch, 162: 2044435-162.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review

Patch in #162 is green, for review.

The Php 7 test failure is actually failing the same in HEAD atm.

andypost’s picture

+++ b/core/core.services.yml
@@ -1654,3 +1654,6 @@ services:
+  pager.manager:
+    class: Drupal\Core\Pager\PagerManager

+++ b/core/lib/Drupal/Core/Pager/Pager.php
@@ -0,0 +1,204 @@
+ * Pager class.

+++ b/core/lib/Drupal/Core/Pager/PagerInterface.php
@@ -0,0 +1,89 @@
+ * Provides an interface for pager objects.

+++ b/core/lib/Drupal/Core/Pager/PagerManager.php
@@ -0,0 +1,188 @@
+ * Provides a factory for pagers.
+ */
+class PagerManager implements PagerManagerInterface {

+++ b/core/lib/Drupal/Core/Pager/PagerManagerInterface.php
@@ -0,0 +1,108 @@
+ * Provides an interface for pager manager objects.
+ */
+interface PagerManagerInterface {

I'd better get rid of "manager" - it makes no sense and tells nothing about what this service doing also visualy it's reads like page manager

More over it's a factory so 'pager.factory' and PagerFactoryInterface imo is what it should be

mondrake’s picture

Re. #167:

'pager.factory' is ruled out by core committer's comment (@catch) in #158 after input from @pwolanin in #146. 'pager.manager' is following on @pwolanin suggestion in #146.

mondrake’s picture

StatusFileSize
new1.83 KB
new38.38 KB

Small tuning of the PagerManagerTest.

mondrake’s picture

Issue summary: View changes

Updated IS.

daffie’s picture

Status: Needs review » Needs work

@Mondrake: Would it be an idea to remove the method getPagerParameterName() from the PagerManager. You say that the method will be needed with #1818040: Pager should start counting from 1, not 0. If that is so then we can add the method in that issue. I understand why you want the method, but the problem is that the core committers have a different opinion.
I must say that all the changes that you have made in the last few patches look good to me. Thank you for all your work on this issue.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new3.38 KB
new37.87 KB

Thanks for support @daffie. What to say. A' la guerre comme à la guerre. This patch is removing getPagerParameterName.

daffie’s picture

Status: Needs review » Reviewed & tested by the community

All changes look good to me.
All problems that @catch has pointed to in comment #158 are addressed.
So back to RTBC.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/lib/Drupal/Core/Pager/PagerManager.php
@@ -0,0 +1,175 @@
+  /**
+   * {@inheritdoc}
+   */
+  public function getQueryParameters() {
+    static $query;
+    if (!isset($query)) {
+      $pager_parameter = PagerManagerInterface::PAGER_QUERY_PARAMETER;
+      $query = UrlHelper::filterQueryParameters($this->getCurrentRequestQuery()->all(), [$pager_parameter]);
+    }
+    return $query;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPagerQueryParameter() {
+    return $this->getCurrentRequestQuery()->get(PagerManagerInterface::PAGER_QUERY_PARAMETER, '');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLinkQueryParameters($element, $page, array $query_parameters) {
+    // Get all defined pagers.
+    $pagers = $this->all();
+
+    // Build the pager query parameter. This is built based on the current
+    // page of each pager element (or NULL if the pager is not set), with the
+    // exception of the requested page index for the current element.
+    $query = $query_parameters;
+    $max_element = max(array_keys($pagers));
+    $element_pages = [];
+    for ($i = 0; $i <= $max_element; $i++) {
+      $element_pages[] = ($i == $element) ? $page : (isset($pagers[$i]) ? $pagers[$i]->getCurrentPage() : NULL);
+    }
+    $parameter = PagerManagerInterface::PAGER_QUERY_PARAMETER;
+    $query[$parameter] = implode(',', $element_pages);
+
+    // Merge the query parameters passed to this function with the parameters
+    // from the current request. In case of collision, the parameters passed
+    // into this function take precedence.
+    if ($current_request_query = $this->getQueryParameters()) {
+      $query = array_merge($current_request_query, $query);
+    }
+
+    return $query;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function findCurrentPage($element) {
+    $page_query = $this->getCurrentRequestQuery()->get(PagerManagerInterface::PAGER_QUERY_PARAMETER);
+    $page_array = explode(',', $page_query);
+    if (!isset($page_array[$element])) {
+      $page_array[$element] = 0;
+    }
+    return (int) $page_array[$element];
+  }
+

So the problem with this patch and the reason why it is taking so long to get done is that the new architecture is not well thought out yet. If these methods where not here then this object would indeed just be a factory for pagers. Part of the new architecture needs to be thought out so that it makes sense and does not overload the responsibilities of each thing. When responsibilities are overloaded you end up with @andypost wanting something to be called a factory and @catch wanting it to be called a manager.

I really recommend stepping back and looking at the responsibilities of each new thing introduced by the patch and asking ourselves what's the best way to organise it.

alexpott’s picture

+++ b/core/lib/Drupal/Core/Pager/PagerFactoryInterface.php
@@ -0,0 +1,87 @@
+  /**
+   * Returns the first available pager element to use.
+   *
+   * @return int
+   *   A pager element.
+   */
+  public function getFirstAvailableElement();

Why does this exist? There are no usages in any runtime code.

daffie’s picture

I have asked @alexpott on IRC for some guidance for getting this issue solved. His response was:

i know I've tried to get others involved but I've only got limited headspace :(

and

well as I just commented someone and it doesn't have to be a framework manager could catalogue what the current patch creates and describe their responsibilities - that'd be a good place to start.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

mondrake’s picture

Issue tags: +Kill includes
mile23’s picture

Status: Needs work » Needs review
Issue tags: +Needs issue summary update, +Needs change record updates
StatusFileSize
new16.94 KB

This needed a re-roll, so I started over and simplified based on #175.

The scope here is to make all the functions in pager.inc into something else, so they can be deprecated.

With that in mind, we make a relatively simple shim service which has a bunch of protected accessors for the globals, for BC. The accessors could be easily replaced with class properties at some point in the future, when we don't have to provide BC.

Turning the associated pager arrays into a Pager class ends up being overkill because we're required to synchronize both the objects and the globals for BC. This way we provide a thin layer which can be modified in the future without breaking BC.

Future use-cases can become their own public methods as needed, using the 'backend' protected accessors. See the many closed issues in #2547833: Pager.inc -- add tests, clean it up, convert to a service, use it! which should really be postponed on this one.

Since there's not much test coverage for this stuff, a follow-up would convert existing code to use the new API, because this issue should only prove that the new API is compatible with code that manages the globals. There are also some issues to convert system tests dealing with the pager to BTB, so let's not @trigger_error() yet. #2984185: Convert system functional tests to phpunit for page and pager

And after all that, a subsequent follow-up for D9 will remove the use of globals, assuming we still need it.

mile23’s picture

Issue tags: +Needs followup
mile23’s picture

StatusFileSize
new21.72 KB
new22.58 KB

Updated some docs, CS, added obvious test, added interface so it's clear we've got a public face vs private sausage-making.

mile23’s picture

kim.pepper’s picture

Status: Needs review » Needs work

There are also some issues to convert system tests dealing with the pager to BTB, so let's not @trigger_error() yet

#2984185: Convert system functional tests to phpunit for page and pager is fixed so we can add the trigger_error() now :-)

mile23’s picture

StatusFileSize
new27.7 KB
new7.91 KB

Added @trigger_error(), added a deprecation-only test.

I expect there are functional tests that use paging that are going to fail based on the deprecation errors.

mile23’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 186: 2044435_186.patch, failed testing. View results

mile23’s picture

Status: Needs work » Needs review
StatusFileSize
new34.19 KB
new13.12 KB

Removes usages.

kim.pepper’s picture

  1. +++ b/core/includes/pager.inc
    @@ -23,15 +21,17 @@
    + *   \Drupal::service('pager.manager')->findPage() instead.
    

    I'm not sure what the policy is. Should this be \Drupal\Core\Pager\PagerManager::findPage() instead?

  2. +++ b/core/includes/pager.inc
    @@ -123,18 +123,17 @@ function pager_find_page($element = 0) {
    + *   \Drupal::service('pager.manager')->defaultInitialize() instead.
    

    ditto

  3. +++ b/core/includes/pager.inc
    @@ -143,13 +142,17 @@ function pager_default_initialize($total, $limit, $element = 0) {
    + *   \Drupal::service('pager.manager')->getQueryParameters() instead.
    

    ditto

  4. +++ b/core/includes/pager.inc
    @@ -307,25 +312,15 @@ function template_preprocess_pager(&$variables) {
    + *   \Drupal::service('pager.manager')->queryAddPage() instead.
    

    ditto

martin107’s picture

I'm not sure what the policy is. Should this be \Drupal\Core\Pager\PagerManager::findPage() instead?

I am afraid the suggestion will not work.

PageManager::findPage() is a call to a static method.

Internally findPage makes use of $this->requestStack - having a $this->xyz make this un-static.

same for defaultInitialize(), getQueryParameters(), queryAddPage()

I hope this helps.

mile23’s picture

Issue tags: -Needs change record updates
StatusFileSize
new38.15 KB
new8.33 KB

Fixed inline docs usages.

Updated CR.

If someone wants to update the IS that'd be great.

I'll file the follow-up for D9 after RTBC.

Re: #190 The thing you're supposed to do instead of calling those functions is to get the service and use it, rather than make a new PagerManager object.

kim.pepper’s picture

Yes, I know. I was talking about specifying the class the method is on rather than the service.

darvanen’s picture

I think @kim.pepper is right.

Services can be accessed in multiple ways, not just side-loading as shown in the example. We should be referencing the class and leaving the implementation of that up to the developer.

For example:
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Session%2...

mile23’s picture

StatusFileSize
new43.45 KB
new22.88 KB

This is a WIP patch. Feel free to review despite test fails. :-)

Fixed references to service names to be the interfaces for #191-#194.

Added another service: pager.request. This separates concerns for the pager information you get from the request, as distinct from the pager info in the globals.

Needs to decide on names for things, because pager.request for RequestPagerInterface isn't that great a set of names.

Since part of the focus here is factoring pager.inc away, converted some methods to public to support template_preprocess_pager() and others.

Still need to figure out where to put template_preprocess_pager().

I worked on removing some usages of the globals, in order to find use-cases to round out the API, but that work is not included here because some of it is quite complex. Let’s do it in follow-ups.

Drupal\taxonomy\Form\OverviewTerms::buildForm() references this issue in a @todo, so update that for the follow-up.

Follow-ups:

Remove usages of the globals.

Bring views' pager handling into harmony with the service or vice-versa.

D9 remove the globals and use something else as a 'backend.'

Status: Needs review » Needs work

The last submitted patch, 195: 2044435_195.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

mile23’s picture

StatusFileSize
new44.86 KB
new16.22 KB

This should be the patch.

Still needs followups from #195 and elsewhere.

mile23’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 197: 2044435_197.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

martin107’s picture

Status: Needs work » Needs review
StatusFileSize
new45.14 KB
new3.27 KB

2 minor refinements....test failures remain.

a) Plumbed requetsStack down into PagerManager.

b) Removed unused use statements.

Status: Needs review » Needs work

The last submitted patch, 200: 2044435-200.patch, failed testing. View results

mile23’s picture

Status: Needs work » Needs review
StatusFileSize
new44.91 KB
new2.15 KB

Actually the problem is that the one test wasn't updated. So now is has been.

This patch comes off of #197.

Status: Needs review » Needs work

The last submitted patch, 202: 2044435_202.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

andypost’s picture

+++ b/core/lib/Drupal/Core/Pager/PagerManager.php
@@ -19,14 +18,31 @@ class PagerManager implements PagerManagerInterface {
+   * Used to cache the query parameters computation.
+   *
+   * @var string
+   */
+  protected $queryParameters;

That could lead to serialization issues

kim.pepper’s picture

  1. +++ b/core/globals.api.php
    @@ -78,7 +78,11 @@
    + * @see \Drupal\Core\Pager\PagerManagerInterface
    
    @@ -87,7 +91,11 @@
    + * @see \Drupal\Core\Pager\PagerManagerInterface
    
    @@ -96,7 +104,11 @@
    + * @see \Drupal\Core\Pager\PagerManagerInterface
    
    @@ -105,6 +117,10 @@
    + * @see \Drupal\Core\Pager\PagerManagerInterface
    

    Should these link to the change record?

  2. +++ b/core/includes/pager.inc
    @@ -23,15 +21,17 @@
    + * @see \Drupal\Core\Pager\RequestPagerInterface
    
    @@ -123,18 +123,18 @@ function pager_find_page($element = 0) {
    + * @see \Drupal\Core\Pager\PagerManagerInterface
    
    @@ -143,13 +143,18 @@ function pager_default_initialize($total, $limit, $element = 0) {
    + * @see \Drupal\Core\Pager\RequestPagerInterface
    

    Need to add the method on the interface.

  3. +++ b/core/includes/pager.inc
    @@ -23,15 +21,17 @@
    +  $request_pager = \Drupal::service('pager.request');
    +  @trigger_error(__FUNCTION__ . ' is deprecated in 8.7.x for removal before Drupal 9.0.0. Use \Drupal\Core\Pager\RequestPagerInterface->findPage() instead. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED);
    

    The trigger_error should be first?

  4. +++ b/core/includes/pager.inc
    @@ -123,18 +123,18 @@ function pager_find_page($element = 0) {
    +  $pager_manager = \Drupal::service('pager.manager');
    +  @trigger_error(__FUNCTION__ . ' is deprecated in 8.7.x for removal before Drupal 9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface->defaultInitialize() instead. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED);
    
    @@ -143,13 +143,18 @@ function pager_default_initialize($total, $limit, $element = 0) {
    +  $request_pager = \Drupal::service('pager.request');
    +  @trigger_error(__FUNCTION__ . ' is deprecated in 8.7.x for removal before Drupal 9.0.0. Use \Drupal\Core\Pager\RequestPagerInterface->getQueryParameters() instead. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED);
    

    Same here

  5. +++ b/core/lib/Drupal/Core/Database/Query/PagerSelectExtender.php
    @@ -73,7 +73,7 @@ public function execute() {
    +    $current_page = \Drupal::service('pager.manager')->defaultInitialize($total_items, $this->limit, $this->element);
    

    This can be injected.

  6. +++ b/core/lib/Drupal/Core/Entity/Query/QueryBase.php
    @@ -309,11 +309,11 @@ public function pager($limit = 10, $element = NULL) {
    +      $page = \Drupal::service('pager.request')->findPage($this->pager['element']);
    

    Same here

  7. +++ b/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php
    @@ -5,12 +5,36 @@
    +  public static function create(ContainerInterface $container) {
    

    Missing docs

  8. +++ b/core/tests/Drupal/KernelTests/Core/Pager/RequestPagerTests.php
    @@ -0,0 +1,49 @@
    +use Drupal\Core\Pager\RequestPager;
    

    Unused import

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new8.46 KB
new44.72 KB

Re #204 I assume adding use DependencySerializationTrait; is enough here?

Did a few fixes for my own feedback in #205 as I'm keen to see this issue move along. :-)

  1. Fixed
  2. Fixed
  3. Fixed
  4. Fixed
  5. Can't be injected easily because we're doing return new $class($this, $this->connection); in \Drupal\Core\Database\Query\SelectExtender::extend()
  6. Can't be injected easily because we're doing return new $class($sql_query); in \Drupal\Core\Entity\Query\Sql\Query::getTables()
  7. Fixed
  8. Fixed

Status: Needs review » Needs work

The last submitted patch, 206: 2044435-pager-service-206.patch, failed testing. View results

kim.pepper’s picture

Issue tags: +DrupalSouth 2018
kim.pepper’s picture

Assigned: Unassigned » kim.pepper

The current patch is pretty much a like-for-like of the existing array madness. I'm going to try to make a nicer API which uses value objects for the pager.

mile23’s picture

See #181 on why I gave up on the value object idea.

We have to provide BC for all contrib that might use this API, and that means using the globals. Value objects then have to be stateless and always get their value from the globals and set their value to the globals.

kim.pepper’s picture

Thanks @Mile23. Yep understood. I'll keep giving it a plug to see if I can make it work.

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new22.94 KB
new45.61 KB

Here's where I am going with the value object approach.

  • New \Drupal\Core\Pager\Pager immutable object that contains the properties for a single pager.
  • Rename defaultInitialize() to \Drupal\Core\Pager\PagerManagerInterface::createPager() and return a Pager object
  • PagerManager uses a static cache to store Pager objects when creating them
  • For BC we update globals whenever a pager is created. This makes it easy to remove them in D9
  • The theming functions can call getPager() which returns it from the static cache

Thoughts?

Status: Needs review » Needs work

The last submitted patch, 212: 2044435-pager-service-212.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new45.39 KB

Re-roll

andypost’s picture

Maybe use arrayAccess and put this new shiny objects inyo globals?
It could help to trigger deprecation error for custom/contrib code to catch bc usage

Status: Needs review » Needs work

The last submitted patch, 214: 2044435-pager-service-214.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new637 bytes
new45.65 KB

Fixes for #214

Re: #215 @andypost I'm not sure how that would work. In this patch, we're moving from separate global arrays containing pager information, to a single static array containing Pagers. How would array access help?

Status: Needs review » Needs work

The last submitted patch, 217: 2044435-pager-service-217.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new583 bytes
new45.74 KB

Fixes minipager.

kim.pepper’s picture

Status: Needs review » Needs work

The last submitted patch, 219: 2044435-pager-service-219.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new527 bytes
new45.74 KB

We need to setCurrentPage() after setting the TotalPages in the Pager constructor.

Status: Needs review » Needs work

The last submitted patch, 222: 2044435-pager-222.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new8.63 KB
new52.52 KB

Remove use of globals in views and taxonomy.

Status: Needs review » Needs work

The last submitted patch, 224: 2044435-pager-224.patch, failed testing. View results

Anonymous’s picture

Assigned: kim.pepper »

i'll have a go at moving this one forward again. thanks kim.pepper for the pointer.

Anonymous’s picture

Status: Needs work » Needs review
StatusFileSize
new51.34 KB

just a reroll, #225 no longer applied. i've only tested that install works. let's see what is broken.

Status: Needs review » Needs work

The last submitted patch, 227: 2044435-227.patch, failed testing. View results

andypost’s picture

Status: Needs work » Needs review
StatusFileSize
new611 bytes
new51.5 KB

quick fix for most of failures

Status: Needs review » Needs work

The last submitted patch, 229: 2044435-229.patch, failed testing. View results

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

claudiu.cristea’s picture

This removes the drupal_static() from pager_get_query_parameters(), making this issue also part of #1577902: [META] Remove all usages of drupal_static() & drupal_static_reset() .

kim.pepper’s picture

andypost’s picture

Status: Needs work » Needs review
StatusFileSize
new6.29 KB
new52.06 KB

Rerolled and fixed message

andypost’s picture

Issue tags: -Needs reroll
StatusFileSize
new1.93 KB
new53.24 KB

Add hunk lost in 227

Status: Needs review » Needs work

The last submitted patch, 235: 2044435-235.patch, failed testing. View results

andypost’s picture

Status: Needs work » Needs review
StatusFileSize
new8.35 KB
new53.29 KB

Some clean-up and fix Drupal\Tests\views\Kernel\Plugin\CacheTest test, views pager still needs some love

Status: Needs review » Needs work

The last submitted patch, 237: 2044435-237.patch, failed testing. View results

andypost’s picture

Status: Needs work » Needs review
StatusFileSize
new1.05 KB
new53.28 KB

Fix one more test (RequestPagerTests renamed to RequestPagerTest)

Also checking how static cache removal affects...
In #204 I already pointed that current request may change in stack
So better to check splObject of current request somehow

Status: Needs review » Needs work

The last submitted patch, 239: 2044435-pager-239.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new53.32 KB
new1.82 KB

I think I found the issue with the views tests fails. We weren't setting the current page from the request parameter.

kim.pepper’s picture

StatusFileSize
new55.07 KB
new3.54 KB

Injecting dependencies into \Drupal\views\Plugin\views\pager\SqlBase

Also, I'm not super keen on RequestPager as a name. Perhaps PagerParamsHelper instead?

The last submitted patch, 241: 2044435-241.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 242: 2044435-242.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new57.59 KB
new3 KB

Fixes tests fails in #242

Status: Needs review » Needs work

The last submitted patch, 245: 2044435-245.patch, failed testing. View results

andypost’s picture

Status: Needs work » Needs review
StatusFileSize
new3.32 KB
new58.79 KB

Yep, #245 is like #3067584-17: Pager template renders attributes that do not exist

Patch removes useless var & fixes remaining test but this fix means that views integration still not ready
Next 2 methods calling each other to many times to affect globals and calculate view page

Drupal\views\Plugin\views\pager\SqlBase::setCurrentPage()
Drupal\views\Plugin\views\pager\SqlBase::updatePageInfo()
kim.pepper’s picture

Woohoo! First green patch in 9 months!

jibran’s picture

StatusFileSize
new58.79 KB

I had a look at views pager. TBH, this code is a bit of a mystery to me but the only questionable thing I could find is:

+++ b/core/modules/views/src/Plugin/views/pager/SqlBase.php
@@ -250,25 +302,8 @@ public function setCurrentPage($number = NULL) {
-    $page = $this->view->getRequest()->query->get('page');
...
+    $pager_request = new RequestPager($this->requestStack);

The request object \Drupal\views\ViewExecutable::$request and the current request in request stack might not be the same.

Anyway here is reroll.

kim.pepper’s picture

Instead of RequestPager how about these alternative names?

  1. PagerParams
  2. PagerParamsHelper
  3. PagerRequestParams
  4. PagerRequestParamsHelper
  5. Other?
andypost’s picture

Maybe just PagerParameters and #249 is right - probably it needs static method PagerRequest::fromRequest()

kim.pepper’s picture

StatusFileSize
new58.79 KB

Re-roll of #249

probably it needs static method PagerRequest::fromRequest()

Then we need to handle both RequestStack and Request in RequestPager which I think is ugly.

In \Drupal\views\ViewExecutableFactory::get() we are doing:

$view->setRequest($this->requestStack->getCurrentRequest());

Wouldn't that make it the same request?

jibran’s picture

@kim.pepper can we remove 'Needs issue summary update' and 'Needs followup' now? If not then let's update the IS and add some followups.

kim.pepper’s picture

Assigned: » Unassigned
Issue summary: View changes
Issue tags: -Needs issue summary update, -Needs followup
StatusFileSize
new58.88 KB
new14.9 KB

We can remove needs followup as that was added in #182 to convert existing code to use the new API. However, we've already done that here.

Updated the IS.

I renamed RequestPager to PagerParameters as per #251.

jibran’s picture

Status: Needs review » Reviewed & tested by the community

Thanks, it's looking good now.

larowlan’s picture

Status: Reviewed & tested by the community » Needs review
  1. +++ b/core/lib/Drupal/Core/Database/Query/PagerSelectExtender.php
    @@ -73,8 +73,10 @@ public function execute() {
    +    /** @var \Drupal\Core\Pager\PagerManagerInterface $pager_manager */
    +    $pager_manager = \Drupal::service('pager.manager');
    +    $pager = $pager_manager->createPager($total_items, $this->limit, $this->element);
    +    $this->range($pager->getCurrentPage() * $this->limit, $this->limit);
    

    can we get a follow-up (if there's not one already) to make \Drupal\Core\Database\Query\Select::extend use the class resolver so we can use container injection here?

  2. +++ b/core/lib/Drupal/Core/Entity/Query/QueryBase.php
    @@ -310,11 +310,11 @@ public function pager($limit = 10, $element = NULL) {
    +      $page = \Drupal::service('pager.parameters')->findPage($this->pager['element']);
    ...
    +      \Drupal::service('pager.manager')->createPager($this->pager['total'], $this->pager['limit'], $this->pager['element']);
    

    Same here, can we get a follow up to inject the class resolver into \Drupal\Core\Entity\Query\Sql\QueryFactory and use it with ::get

  3. +++ b/core/lib/Drupal/Core/Pager/PagerManager.php
    @@ -0,0 +1,145 @@
    +    $this->pagers[$element] = $pager;
    +    $this->updateGlobals($pager, $element);
    ...
    +    global $pager_total_items, $pager_total, $pager_page_array, $pager_limits;
    +    $pager_total_items[$element] = $pager->getTotalItems();
    +    $pager_total[$element] = $pager->getTotalPages();
    +    $pager_page_array[$element] = $pager->getCurrentPage();
    +    $pager_limits[$element] = $pager->getLimit();
    

    I think we should do leak detection and trigger_error here. i.e we should keep a 'last values' and compare that to current values, and if the globals have changed outside of this class, we should trigger an error.

    That will help people find usages

    i.e before we update the global values, we should check that they're still the same as our previous value and detect that something else has modified them

    We should be able to extend the existing test to verify this too.

  4. +++ b/core/lib/Drupal/Core/Pager/PagerManagerInterface.php
    @@ -0,0 +1,153 @@
    +  public function queryAddPage(array $query, $element, $index);
    

    No we've got greenfield - is this the best name for this? (Sorry for the bikeshed) but I don't think queryAddPage conveys what is happening?

  5. +++ b/core/lib/Drupal/Core/Pager/PagerParameters.php
    @@ -0,0 +1,64 @@
    +        $this->requestStack->getCurrentRequest()->query->all(), ['page']
    ...
    +    return $this->requestStack->getCurrentRequest()->query->get('page', '');
    

    getCurrentRequest can sometimes return NULL (e.g. in cli context). I think we should add some defensive code here around that scenario.

  6. +++ b/core/lib/Drupal/Core/Pager/PagerParametersInterface.php
    @@ -0,0 +1,59 @@
    +  public function findPage($element = 0);
    

    Should we name this $pager_id now instead of $element to convey what it actually is, $element is vague (yes I realise that's what we have now). Also, my bikeshed should be yellow.

  7. +++ b/core/lib/Drupal/Core/Render/Element/Pager.php
    @@ -68,11 +68,12 @@ public function getInfo() {
    +    // \Drupal::service('pager.manager)->queryAddPage()(), which maintains the
    

    two () here

  8. +++ b/core/modules/views/src/Plugin/views/pager/SqlBase.php
    @@ -5,11 +5,63 @@
    +  public function __construct(array $configuration, $plugin_id, $plugin_definition, PagerManagerInterface $pager_manager, RequestStack $request_stack) {
    +    parent::__construct($configuration, $plugin_id, $plugin_definition);
    +    $this->pagerManager = $pager_manager;
    +    $this->requestStack = $request_stack;
    

    It would be nice if we could do the BC dance here, i.e. removing the typehints, defaulting to NULL and checking the type - just in case someone in contrib has extended this class and implemented ::create and __construct with different arguments

  9. +++ b/core/modules/views/tests/src/Kernel/Handler/AreaDisplayLinkTest.php
    @@ -172,8 +172,8 @@ public function testAreaDisplayLink() {
    -    $this->assertSame('<a href="/page_1/1?name=John&amp;sort_by=created&amp;sort_order=ASC&amp;keep=keep&amp;keep_another=1&amp;page=1" class="views-display-link views-display-link-page_1 is-active">Page 1</a>', $this->renderDisplayLink($view, 'display_link_1'));
    -    $this->assertSame('<a href="/page_2/1?name=John&amp;sort_by=created&amp;sort_order=ASC&amp;keep=keep&amp;keep_another=1&amp;page=1" class="views-display-link views-display-link-page_2">Page 2</a>', $this->renderDisplayLink($view, 'display_link_2'));
    +    $this->assertSame('<a href="/page_1/1?name=John&amp;sort_by=created&amp;sort_order=ASC&amp;keep=keep&amp;keep_another=1&amp;page=2" class="views-display-link views-display-link-page_1 is-active">Page 1</a>', $this->renderDisplayLink($view, 'display_link_1'));
    +    $this->assertSame('<a href="/page_2/1?name=John&amp;sort_by=created&amp;sort_order=ASC&amp;keep=keep&amp;keep_another=1&amp;page=2" class="views-display-link views-display-link-page_2">Page 2</a>', $this->renderDisplayLink($view, 'display_link_2'));
     
    

    does this change indicate something broken?

  10. +++ b/core/modules/views/tests/src/Kernel/Handler/AreaDisplayLinkTest.php
    @@ -327,8 +327,8 @@ protected function assertRenderedDisplayLinks(ViewExecutable $view, $display_id)
    -    $this->assertSame('<a href="/page_1" class="views-display-link views-display-link-page_1' . $page_1_active . '">Page 1</a>', $this->renderDisplayLink($view, 'display_link_1'));
    -    $this->assertSame('<a href="/page_2" class="views-display-link views-display-link-page_2' . $page_2_active . '">Page 2</a>', $this->renderDisplayLink($view, 'display_link_2'));
    +    $this->assertEquals('<a href="/page_1" class="views-display-link views-display-link-page_1' . $page_1_active . '">Page 1</a>', $this->renderDisplayLink($view, 'display_link_1'));
    +    $this->assertEquals('<a href="/page_2" class="views-display-link views-display-link-page_2' . $page_2_active . '">Page 2</a>', $this->renderDisplayLink($view, 'display_link_2'));
    
    @@ -341,8 +341,8 @@ protected function assertRenderedDisplayLinks(ViewExecutable $view, $display_id)
    -    $this->assertSame('<a href="/page_1/1?name=John&amp;sort_by=created&amp;sort_order=ASC&amp;page=1" class="views-display-link views-display-link-page_1' . $page_1_active . '">Page 1</a>', $this->renderDisplayLink($view, 'display_link_1'));
    -    $this->assertSame('<a href="/page_2/1?name=John&amp;sort_by=created&amp;sort_order=ASC&amp;page=1" class="views-display-link views-display-link-page_2' . $page_2_active . '">Page 2</a>', $this->renderDisplayLink($view, 'display_link_2'));
    +    $this->assertEquals('<a href="/page_1/1?name=John&amp;sort_by=created&amp;sort_order=ASC&amp;page=2" class="views-display-link views-display-link-page_1' . $page_1_active . '">Page 1</a>', $this->renderDisplayLink($view, 'display_link_1'));
    +    $this->assertEquals('<a href="/page_2/1?name=John&amp;sort_by=created&amp;sort_order=ASC&amp;page=2" class="views-display-link views-display-link-page_2' . $page_2_active . '">Page 2</a>', $this->renderDisplayLink($view, 'display_link_2'));
    

    why are these changes needed?

kim.pepper’s picture

  1. Created #3086647: Allow Select query builder to use class resolver so we can use dependency injection
  2. Created #3086649: Allow QueryFactory to use class resolver so we can use dependency injection
  3. Added leak detection 💧
  4. Renamed to updateParameters()
  5. Added check for NULL request
  6. Renamed to $pager_id
  7. Fixed
  8. Added BC dance 💃
  9. Ergh
  10. Hmm

I think we need to take another look at the AreaDisplayLinkTest changes. 😕

andypost’s picture

Related issues: +#33809: Make pagers not suck

Linking one more ancient issue #33809: Make pagers not suck

larowlan’s picture

#33809: Make pagers not suck

😂

larowlan’s picture

discussed with @kim.pepper - because the globals are all arrays, we can use ArrayAccess here to provide a for-reals deprecation layer - knocked up some sample code in https://3v4l.org/AlHZK

kim.pepper’s picture

StatusFileSize
new67.72 KB
new21.53 KB

I've put together an implementation of #260 to see what breaks.

Status: Needs review » Needs work

The last submitted patch, 261: 2044435-261.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new68.53 KB
new821 bytes

Found a usage of global $pager_total in core/modules/system/tests/modules/pager_test/pager_test.module

jibran’s picture

Means new BC layer worked.

kim.pepper’s picture

I think this will only work if $pager_manager->createPager() has already been called.

If you were to just try and set a value for example:

global $pager_total;
$pager_total_items[2] = 30;

it would break, because the current code needs to call $pager_manager->getPager() before setting a value. This can return NULL if the pager doesn't exist yet. We can only shortcut it and not set a value if it doesn't exist, but we'll lose that data.

It also doesn't call $pager_manager->setPager() either, so it's never stored. Because we don't have the $pager_id. We could add it as a property of Pager.

So I don't think we have BC with this solution.

larowlan’s picture

+++ b/core/lib/Drupal/Core/Pager/Legacy/PagerGlobalVariableWrapper.php
@@ -0,0 +1,102 @@
+    $pager = $this->pagerManager->getPager($offset);
...
+    $pager = $this->pagerManager->getPager($offset);

so I think the solution is that these need to be wrapped in a protected createOrGetPager method that does the creation if it doesn't exist

kim.pepper’s picture

Nah. In order to create a pager, we need __construct($totalItems, $limit, $currentPage = 0) . However, when we are in a global wrapper, we are only dealing with one at a time (e.g. PagerTotalItemsGlobalVariableWrapper). We don't have access to those. We don't have access to $pager_id either to getPager($pager_id).

larowlan’s picture

damn, back to the drawing board I guess

kim.pepper’s picture

StatusFileSize
new63.3 KB
new821 bytes

OK here's #257 with #263 applied.

Still need to work out the views links test issues.

kim.pepper’s picture

StatusFileSize
new59.34 KB
new5.41 KB

Woo! Fixed the view links test. :-)

andypost’s picture

Status: Needs review » Reviewed & tested by the community

As #256 9-10 addressed

larowlan’s picture

Status: Reviewed & tested by the community » Needs review
+++ b/core/globals.api.php
@@ -81,7 +81,12 @@
 global $pager_limits;

@@ -90,7 +95,12 @@
 global $pager_page_array;

@@ -99,7 +109,12 @@
 global $pager_total;

@@ -108,6 +123,11 @@
 global $pager_total_items;

I still think we should wrap these in an object that implements array access and trigger the error if someone tries to access it, but just drop the injection of pager manager that tries to keep them in sync

ie what we discussed at one point in slack DeprecatedArray or similar that just wrapped the values and took a message as a constructor

thoughts?

kim.pepper’s picture

If we did that, we would be triggering it from PageManager, as it needs to update globals for BC. We could add them to the list of skipped deprecations? But I think we're trying to avoid that.

larowlan’s picture

If we did that, we would be triggering it from PageManager, as it needs to update globals for BC. We could add them to the list of skipped deprecations? But I think we're trying to avoid that.

Could PagerManager just create new instances of the array access object instead?

kim.pepper’s picture

StatusFileSize
new62.37 KB
new11.12 KB

Discussed this with @larowlan in slack and agreed that we can use DeprecatedArray and just reset all the globals whenever an pager is updated in PagerManager.

jibran’s picture

+++ b/core/lib/Drupal/Core/Pager/PagerManager.php
@@ -0,0 +1,153 @@
+   * @todo Pager global variables are deprecated and will be removed before
+   * Drupal 9.0.0.
...
+   * @todo Pager global variables are deprecated and will be removed before
+   * Drupal 9.0.0.

Do we need to create a followup and add the link here?

larowlan’s picture

  1. +++ b/core/lib/Drupal/Core/Cache/Context/PagersCacheContext.php
    @@ -11,7 +12,24 @@
    +  public function __construct(PagerParametersInterface $pager_params) {
    +    $this->pagerParams = $pager_params;
    +  }
    

    I think we need the BC dance here, but the two-pronged version as seen in #2669074: Convert file_create_url() & file_url_transform_relative() to service, deprecate it where we also have to allow for sub-classes that don't call the parent constructor because it was previously empty - sorry 😿

  2. +++ b/core/lib/Drupal/Core/Pager/PagerManager.php
    @@ -0,0 +1,153 @@
    +      $GLOBALS['pager_page_array'] = new DeprecatedArray($this->pagerParams->getPagerQuery(), 'Global variable $pager_page_array is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457');
    

    is pager_page_array using getPagerQuery correct? in updateGlobals we use getCurrentPage for that variable?

kim.pepper’s picture

StatusFileSize
new62.4 KB
new2.03 KB

Re: #276 discussed in slack and decided to remove @todos.

Re: #277

  1. Added the bc dance 💃
  2. Yes this is correct. $this->pagerParams->getPagerQuery() is an array of pager_id => current page. In updateGlobals() we are iterating through the pagers and building this up.
jibran’s picture

Status: Needs review » Reviewed & tested by the community

Thanks, for addressing the feedback it looks ready now.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

larowlan’s picture

I'm RTBC+1 on this, but will discuss with other framework managers

larowlan’s picture

Version: 8.9.x-dev » 8.8.x-dev
Status: Reviewed & tested by the community » Needs review

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

larowlan’s picture

Version: 8.9.x-dev » 8.8.x-dev
Status: Needs review » Reviewed & tested by the community

cross-post

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

So re #277 and the BC dance. It's more complex than what we do in #278 and the "two-pronged version as seen in #2669074: Convert file_create_url() & file_url_transform_relative() to service, deprecate it" is also not quite right. That's because in HEAD this class extends \Drupal\Core\Cache\Context\RequestStackCacheContextBase() which does implement a constructor.

So the correct BC is not typehinting the first argument and checking if it is an instanceof PagerParametersInterface and updating the @trigger_error message appropriately. Add adding

  use DeprecatedServicePropertyTrait;

  /**
   * {@inheritdoc}
   */
  protected $deprecatedProperties = ['requestStack' => 'request_stack'];
alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new1.8 KB
new1.8 KB

Something like this.

catch’s picture

Apparently three years (!) since I last reviewed this. Only really found some nits although much shorter review than I'd like.

  1. +++ b/core/lib/Drupal/Component/Utility/DeprecatedArray.php
    @@ -0,0 +1,69 @@
    +/**
    + * An array that triggers a deprecation warning when accessed.
    + */
    +class DeprecatedArray implements \ArrayAccess {
    +
    

    This is a nice solution.

  2. +++ b/core/lib/Drupal/Core/Pager/PagerManager.php
    @@ -0,0 +1,143 @@
    +
    +  /**
    +   * A static cache of pagers.
    +   *
    

    Nit: not really a static cache.

  3. +++ b/core/lib/Drupal/Core/Pager/PagerManagerInterface.php
    @@ -0,0 +1,153 @@
    + *
    + * Since there can be multiple pagers per requested page, each one is
    + * represented by an 'element' ID. This is an integer. It represents the index
    + * of the pager element within the 'page' query. The pager element is an
    + * integer telling us the current page number for that pager.
    + *
    

    This is a bit confusing, it both says the element is an integer identifying the pager, then also that it's an integer telling you the page number. Should it say the value of the element tells you the page number? But I think it's copied from the existing docs..

  4. +++ b/core/lib/Drupal/Core/Pager/PagerManagerInterface.php
    @@ -0,0 +1,153 @@
    +   *   $where = "status = 1";
    +   *   $total = mymodule_select("SELECT COUNT(*) FROM data " . $where)->result();
    +   *   $num_per_page = \Drupal::config('mymodule.settings')->get('num_per_page');
    

    I'm sure this is copy/pasted but there's no need for $where to be a variable here. Would be good to update the docs to use selectquery or similar (in a follow-up, appreciate this is probably copy and paste from the old example).

On #285 IMO we don't need to provide bc for cache context implementations at all.

alexpott’s picture

For what its worth I agree with @catch that BC constructors on cache contexts is unnecessary. The thing is though our current policy is best effort BC so @larowlan requested the deprecation in #277. #285 was pointing out that the version of the BC dance used in #278 wasn't right.

@Berdir and I have talked about opening a policy issue to clarify when we need to do constructor deprecation and when we don't but as far as I know that's never happened. Our BC policy says that constructors are not API but in real world we have broken plenty of things with changes to constructors of things like plugins (that are not API)...

plach’s picture

Very cool indeed!

There are no hard-blockers in my review below, however it would be nice to address it if we happen to reroll this patch, as we probably should, per #285.

  1. +++ b/core/lib/Drupal/Core/Pager/Pager.php
    @@ -0,0 +1,121 @@
    +    $this->currentPage = max(0, min($currentPage, ((int) $this->getTotalPages()) - 1));
    

    What about casting the return value of ::getTotalPages() to int instead? Or alternatively initialize all members with 0.

  2. +++ b/core/lib/Drupal/Core/Pager/PagerManager.php
    @@ -0,0 +1,143 @@
    +    $this->setPager($pager, $element);
    +    $this->updateGlobals();
    

    The $this->updateGlobals(); invocation is not needed: it is already performed by ::setPager().

  3. +++ b/core/lib/Drupal/Core/Pager/PagerManager.php
    @@ -0,0 +1,143 @@
    +      $GLOBALS['pager_page_array'] = new DeprecatedArray($this->pagerParams->getPagerQuery(), 'Global variable $pager_page_array is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457');
    

    Very nice! However I think we should probably do all the $GLOBALS massaging (init/update) only in the master request.

  4. +++ b/core/lib/Drupal/Core/Pager/PagerManagerInterface.php
    @@ -0,0 +1,153 @@
    +   * paged through, then you should call this function in preparation.
    +   *
    +   * The following example shows how this function can be used in a controller
    

    s/this function/this service?

  5. +++ b/core/lib/Drupal/Core/Pager/PagerManagerInterface.php
    @@ -0,0 +1,153 @@
    +   *   $pager_manager = \Drupal::service('pager.manager');
    +   *   // Perform the query, using the requested offset from
    +   *   // PagerManagerInterface::findPage(). This comes from a URL parameter, so
    +   *   // here we are assuming that the URL parameter corresponds to an actual
    +   *   // page of results that will exist within the set.
    +   *   $page = $pager_manager->findPage();
    

    There is no PagerManagerInterface::findPage() method, if I'm not mistaken this code should use the page.parameters service instead.

  6. +++ b/core/lib/Drupal/Core/Pager/PagerManagerInterface.php
    @@ -0,0 +1,153 @@
    +   * Gets the URL query parameter array of a pager link.
    ...
    +  public function updateParameters(array $query, $element, $index);
    

    The combination of PHP doc and method name is confusing: makes me wonder whether this is a getter or a setter. I'd rename this to ::getUpdatedParameters().

  7. +++ b/core/lib/Drupal/Core/Pager/PagerParametersInterface.php
    @@ -0,0 +1,59 @@
    +   * Get all request URL query parameters that are unrelated to paging.
    ...
    +   * Get the request query parameter.
    ...
    +   * Get the 'page' query parameter for the current request.
    

    "Gets"

  8. +++ b/core/lib/Drupal/Core/Render/Element/Pager.php
    @@ -5,10 +5,10 @@
    + * \Drupal::service('pager.manager)->createPager() in order to render
    
    @@ -68,11 +68,12 @@ public function getInfo() {
    +    // \Drupal::service('pager.manager)->queryAddPage(), which maintains the
    

    Since we don't want to promote service location over dependency injection, I'd rather refer generically to PagerManagerInterface::createPager() instead :)

  9. +++ b/core/modules/views/src/Plugin/views/pager/SqlBase.php
    @@ -5,11 +5,71 @@
    +  /**
    +   * The request stack.
    +   *
    +   * @var \Symfony\Component\HttpFoundation\RequestStack
    +   */
    +  protected $requestStack;
    ...
    +   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
    +   *   The request stack.
    ...
    +  public function __construct(array $configuration, $plugin_id, $plugin_definition, PagerManagerInterface $pager_manager = NULL, RequestStack $request_stack = NULL) {
    ...
    +    $this->requestStack = $request_stack;
    
    @@ -250,25 +310,8 @@ public function setCurrentPage($number = NULL) {
    +    $pager_params = new PagerParameters($this->requestStack);
    

    I'm not sure it's ok to instantiate a service directly this way: I normally assume services are singletons and their state is reliable for the whole request lifecycle. If we later added an internal cache or anything else beyond the request stack, that might get out of sync with the container instance. Since we don't use the request stack elsewhere in this class, why don't we just inject the page parameters service instead?

  10. +++ b/core/tests/Drupal/KernelTests/Core/Pager/PagerManagerTest.php
    @@ -0,0 +1,100 @@
    +      $this->assertTrue(isset($GLOBALS[$pager_global]));
    

    I'd also check that the actual values match the ones we set through the pager.

  11. +++ b/core/tests/Drupal/KernelTests/Core/Pager/PagerManagerTest.php
    @@ -0,0 +1,100 @@
    +    // Update globals outside the PagerManager.
    +    $GLOBALS['pager_page_array'][] = ['foo'];
    +    $GLOBALS['pager_total_items'][] = ['bar'];
    +    $GLOBALS['pager_total'][] = ['baz'];
    +    $GLOBALS['pager_limits'][] = ['wiz'];
    +
    +    // Trigger the check if globals have changed.
    +    $pager_manager->createPager(5, 1);
    +
    +  }
    

    Maybe I'm missing something, but I'd expect this to check that the values stored in the global did not override the pager values, i.e. I'd assert that the pager holds the expected values before and after the last ::createPager() invocation.
    Also, I'd expect scalar values to be assigned to the global.

  12. +++ b/core/tests/Drupal/KernelTests/Core/Pager/RequestPagerTest.php
    @@ -0,0 +1,48 @@
    +    $this->assertEquals(10, $pager_params->findPage(1));
    

    Can we assert that also pager 0 has the expected value?

kim.pepper’s picture

StatusFileSize
new62.32 KB
new20.49 KB

Thanks everyone for the feedback. I hope there is still time to get this in!

Re: #285 & #286 Applied the patch, and tweaked the deprecation message.

Re: #287

  1. That's all @larowlan :-)
  2. Fixed
  3. Fixed
  4. Created followup #3087486: Update docs in PagerManagerInterface

Re: #289

  1. They are initialized to zero, and the method returns an int, so I've just removed the casting.
  2. Fixed
  3. Fixed
  4. Fixed
  5. Fixed
  6. Fixed
  7. Fixed
  8. Fixed
  9. Good catch! Fixed
  10. Fixed
  11. Fixed
  12. Fixed
kim.pepper’s picture

andypost’s picture

Status: Needs review » Reviewed & tested by the community

Filed follow-up from slack to not block it #3087509: Convert comment reply subrequest to new pager manager

  • plach committed 38741fc on 9.0.x
    Issue #2044435 by mondrake, kim.pepper, Mile23, andypost, martin107,...

  • plach committed b617d5f on 8.9.x
    Issue #2044435 by mondrake, kim.pepper, Mile23, andypost, martin107,...

  • plach committed 64cf795 on 8.8.x
    Issue #2044435 by mondrake, kim.pepper, Mile23, andypost, martin107,...
plach’s picture

Status: Reviewed & tested by the community » Fixed

Committed 38741fc and pushed to 9.0.x and friends.

Thanks for keeping this alive!

kim.pepper’s picture

Awesome! 🎉💃🕺 So happy to see this get in. 🥂🍾

Noticed a tiny clean-up issue so created followup #3087514: Remove unused RequestStack property on pager/SqlBase.

kim.pepper’s picture

kim.pepper’s picture

mondrake’s picture

Thank you all for seeing through this to its final docking :)

mondrake’s picture

Unfortunately this is breaking tests in contrib Pagerer module - filed follow-up #3087602: DeprecatedArray implements \ArrayAccess, traversals not possible.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.