Problem/Motivation

core/modules/update/update.compare.inc contains 1 method, update_process_project_info() that is called from outside of the file.

update_calculate_project_update_status() is almost 300 lines long and its docblock is over 60 lines. It very hard to follow what this function is doing.

Now that we did #3094304: Create tests that cover contrib non-full releases and contrib patch versions we have much better test coverage for this functionality

In #3100115: The update module should recommend updates based on supported_branches rather than major versions majors we will be changing much of the logic of update_calculate_project_update_status(). If this isn't in a class then we either have to add to the existing method or add more global functions. If we move to the class within #3100115 then reviewing the changes becomes much hard because the diff of the refactor for the new logic around support branches would not be clear.

Automatic Updates will also additional information from the Update XML that would easier to get if this function was already refactored. See #3252187: [Meta] Deal with core Update module recommending the latest release in the major version not the latest in the Minor version and #3254207: Only allow 1 patch release update increment in Cron

Proposed resolution

Instead of just creating 1 new class that encapsulates all update_calculate_project_update_status() we should take all the logic possible out of this method that doesn't deal directly with assigning values to the specific elements of the $project_data array and move this new ProjectStatusCalculator class.

ProjectStatusCalculator would have these public methods

  1. getLatestDev() currently set as <code>$project_data['latest_dev'] if this is either the latest development release of the target major or the latest release, which ever is more recent
  2. getRecommendReleaseForTargetMajor currently set as $project_data['recommended'] the recommended version for the target major. This may be the current version is there is not recommended update
  3. getLatestReleaseForTargetMajor() currently set as $project_data['latest_version']
  4. getReleasesInMajorsGreaterThanTarget(better name?) currently set as $project_data['also'] now. This is the latest version for each target major greater than the current target major
  5. getSecurityReleases() set as currently set as $project_data['security updates']
  6. getStatus(). This does not directly correlate to what is stored in
    $project_data<code>....  
    
    Because for instance 
    <code>$project_data['status'] = UpdateManagerInterface::NOT_SUPPORTED

    appears multiple times. Once it is stores project_status from the XML, another time it is because the installed version is not in a supported branch and another time it is because installed version is marked unsupported.

    for more details see #33

    So this will return the status based on what the currently has installed. Not on the status of the Update XML.

  7. getInstallType() a wrapper around $project_data['install_type']

All of these public methods will not return any versions that should never be installed as defined by being, insecure, unpublished, revoked, unsupported or or an unsupported branch. They all rely on private method getInstallableReleases() to ensure that they don't consider uninstallable versions. In the future we could make this public so that we could #3252190: Store all installable releases in update_calculate_project_update_status() which allow Automatic Updates to select recommendations use different criteria while not having to worry figuring out which versions should never be recommended.

Nothing in ProjectStatusCalculator should be considered an API except the public methods therefore it should be final and all non-pubic internals should be private.

Remaining tasks

Review

User interface changes

None

API changes

Introduces ProjectStatusCalculator class we should consider if this should be @internal or now.

Data model changes

None

Release notes snippet

Note needed

Issue fork drupal-3100110

Command icon Show commands

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

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

Comments

tedbow created an issue. See original summary.

tedbow’s picture

Status: Active » Postponed

We should get #3074993: Use "current" in update URLs instead of CORE_COMPATIBILITY to retrieve all future updates in first because it is critical

We should also get #3099825: Test available updates when the current major and next major of core are supported in first because it adds more test coverage for what we would be refactoring here

tedbow’s picture

Status: Postponed » Needs review
StatusFileSize
new39.91 KB
new226.81 KB

#3074993: Use "current" in update URLs instead of CORE_COMPATIBILITY to retrieve all future updates is RTBC. I would really love to do this issue next before #3100115: The update module should recommend updates based on supported_branches rather than major versions majors which will be a blocker for Drupal.org supporting semantic contrib releases but I don't also want to waste alot time refactoring update_calculate_project_update_status() because #3100115 will again change it.

If we do #3100115 first I fear we are just going to make update_calculate_project_update_status() more of jumbled mess.

So this patch on top of #307499 simply moves update_calculate_project_update_status() into a new class as \Drupal\update\UpdateProjectStatus::getUpdateProjectData().

The logic has not changed at all. My hope would be that we can get this in and then in #3100115 we are free to move our new logic into private functions to make it more understandable.

tedbow’s picture

Title: Convert update.compare.inc into a class » Convert update_calculate_project_update_status() into a class

updating title. update_calculate_project_update_status() is the only function that deals with 1 project at a time

tedbow’s picture

StatusFileSize
new962 bytes
new40.09 KB

#3074993: Use "current" in update URLs instead of CORE_COMPATIBILITY to retrieve all future updates was committed 🎉

Here is with a couple doc updates.

Notice I didn't update the doc for \Drupal\update\UpdateProjectStatus::getUpdateProjectData() I copied this directly from the current function. There are probably problem with it but the logic of the function didn't change at all. So I don't think we should go down that rabbit hole.

dww’s picture

Status: Needs review » Needs work

First of all, thanks for trying to make sense of my 10+ year old giant function, and trying to reorganize it to be more maintainable. If only Drupal was OO friendly and API-by-array wasn't the Drupal Way(tm) to do everything when I wrote all this in the first place, perhaps you wouldn't be faced with this annoying task now. ;)

  1. +++ b/core/modules/update/src/UpdateProjectStatus.php
    @@ -0,0 +1,451 @@
    +  /**
    +   * Calculates the current update status of a specific project.
    +   *
    +   * This function is the heart of the update status feature. For each project it
    ...
    +   * example:
    ...
    +  public function getUpdateProjectData() {
    +    $this->doCalculateProjectStatus();
    +    return $this->projectData;
    +  }
    

    The comment implies this function is still "the heart of the update status feature", yet it's a 2-liner that calls a private method and returns the results.

    a) Seems like a weird code organization. Why bother with the private method at all? It's only called exactly once. The whole class is "final" and "@internal", and the API of the public function (@return array) can remain the same even if we further refactor the method or change implementation details.

    b) If we're set on having a private method that does the heavy lifting, seems we should move the detailed docblock to document the function that actually does what the docs explain. The documentation on the public method could be simplified to explaining the structure of the returned array in detail, which is mostly what callers care about.

  2. +++ b/core/modules/update/src/UpdateProjectStatus.php
    @@ -0,0 +1,451 @@
    +   * if there are multiple releases such as betas from the same major.patch
    

    Since part of the goal is prepping for semver, we should adopt the @mixologic et al idea that previous/current Drupal contrib versions are API-MAJOR.MINOR, not API-MAJOR.PATCH.

  3. +++ b/core/modules/update/src/UpdateProjectStatus.php
    @@ -0,0 +1,451 @@
    +   * version (e.g., 5.x-1.5-beta1, 5.x-1.5-beta2, and 5.x-1.5). Development
    

    We should probably update these from 5.x examples, too. ;)

  4. +++ b/core/modules/update/src/UpdateProjectStatus.php
    @@ -0,0 +1,451 @@
    +   * @return array
    +   *   The updated project data.
    

    "updated" how? If we're going through the trouble to make this a class, let's document what changes to projectData we're actually making.

  5. +++ b/core/modules/update/src/UpdateProjectStatus.php
    @@ -0,0 +1,451 @@
    +  /**
    +   * Calculates the project status.
    +   *
    +   * For a detailed description of how the status is calculated see
    +   * ::getUpdateProjectData().
    +   */
    +  private function doCalculateProjectStatus() {
    

    a) This is still a huge function. Is the plan to have a follow-up issue to actually do what the summary says: "The class should refactor
    update_calculate_project_update_status() into smaller methods that can be more easily understood."?

    Comment #3 says: "The logic has not changed at all. My hope would be that we can get this in and then in #3100115 we are free to move our new logic into private functions to make it more understandable."

    So either the summary needs an update or the patch needs more refactoring. I'd personally prefer the later, since the premise of this issue is that this logic could be split up into smaller pieces to be more easily understandable by someone who didn't write it. ;) If that's true, let's see a proposed reorganization that actually works and results in something with smaller methods. If we're making this a class just because we like classes, but it turns out that you still need a giant function to do everything we need, I'm less inclined to try to get this into core at all.

  6. +++ b/core/modules/update/src/UpdateProjectStatus.php
    @@ -0,0 +1,451 @@
    +            'label' => t('Project not secure'),
    

    Since this is now an object, do we care about t() or should we inject the right t() method to use?

  7. +++ b/core/modules/update/src/UpdateProjectStatus.php
    @@ -0,0 +1,451 @@
    +      // compute. Record the project status into  $this->projectData and we're done.
    

    Nits: extra space before $this and longer than 80 chars.

  8. +++ b/core/modules/update/src/UpdateProjectStatus.php
    @@ -0,0 +1,451 @@
    +          $target_major = ModuleVersion::createFromSupportBranch($supported_branch)
    

    Not the fault of this patch, but "createFromSupportBranch()" is a weird name for this method. Should have been "createFromSupportedBranch()" -- the branch is either supported (by the maintainer) or not. It's not a "support branch"... *shrug*

  9. +++ b/core/modules/update/src/UpdateProjectStatus.php
    @@ -0,0 +1,451 @@
    +
    

    Extra newline.

  10. +++ b/core/modules/update/src/UpdateProjectStatus.php
    @@ -0,0 +1,451 @@
    +        // If there are no valid support branches, use the current major.
    

    s/support/supported/

  11. +++ b/core/modules/update/src/UpdateProjectStatus.php
    @@ -0,0 +1,451 @@
    +    return;
    

    This return is unneeded.

  12. +++ b/core/modules/update/update.compare.inc
    @@ -100,7 +101,7 @@ function update_calculate_project_data($available) {
    -    update_calculate_project_update_status($projects['drupal'], $available['drupal']);
    +    $projects['drupal'] = (new UpdateProjectStatus($projects['drupal'], $available['drupal']))->getUpdateProjectData();
    

    This is the central API change of this patch. We used to have a method that took the project_data array by reference, did a bunch of computations and comparisons, and updated the array in place with the new information.

    Now, we instantiate a new object with both the project data and the available releases data, call an (oddly named) method to process the project array, and overwrite the array we started with. I'm not totally convinced this is a win for code readability or maintenance. I'm sure it's a small (but perhaps measurable) step backwards in the required memory footprint of this module. I sure hope PHP's garbage collection is working properly and it cleans up all the memory used by the protected arrays in each of these UpdateProjectStatus objects.

    Regardless, seems like a lot of churn and copying to still have a 300+ line function. ;)

  13. #NeedsTests ? If part of the goal of this refactoring is to put it into a unit-testable-object, can/should we add some unit tests at the same time?

Thanks/sorry,
-Derek

dww’s picture

p.s. re: "There are probably problem with it but the logic of the function didn't change at all. So I don't think we should go down that rabbit hole."

If we're reorganizing all this code, I think we have to update the docs to match. When will we "go down that rabbit hole" if not now? IMHO, this patch is of almost no use if it just wraps the huge function (and comment) in class syntax but otherwise leaves it as-is. It has the potential to really help mere mortals contribute to the update module. But in its current state, it doesn't help that goal at all, and perhaps is even a step backwards...

tedbow’s picture

@dww thanks the review. It is amazing that this 10+ year old code has served us well for so long!

re #6

  1. removed the private function. Was trying to change the code copied as little as possible and since we don't have the $project_date by reference now without the private function we need to change all the return statement to return it.

    But I agree the private method was weird and just changing the return statements is better.

  2. I don't want to change the comment here at all. #3100115: The update module should recommend updates based on supported_branches rather than major versions majors has just become critical and it is now a 9.0.0-beta1 blocker. In that issue we will have to overhaul this methods comment completely so I think discussing that here just delays the critical issue.

    Since the logic is not changing at all and the only code that is changing is the return statement rather relying on the $project_data reference I think fixing the comment is not necessary. Also there are probably a bunch of stuff we could fix about the comment and I would rather not get into that I feel like they should have been handled in #3074993: Use "current" in update URLs instead of CORE_COMPATIBILITY to retrieve all future updates if we missed something we should add follow-up to that. But hopefully I think follow-ups are necessary since this will change anyways in 3100115

  3. see above
  4. Added all the possible keys that by be updated.
  5. Removed the private function

    If we're making this a class just because we like classes, but it turns out that you still need a giant function to do everything we need, I'm less inclined to try to get this into core at all.

    Putting this into a class so we don't have to do that and the update logic in #3100115: The update module should recommend updates based on supported_branches rather than major versions majors. I would like keep 3100115 to only the logic changes needed and not moving it into a class. It is going a lot of changes in that issue and would like to make it as small as we can so we get it right.

    If that's true, let's see a proposed reorganization that actually works and results in something with smaller methods.

    I have started to look at #3100115 and was working on patch on top of this. Here is rough non-work version of this class I was working on https://github.com/tedbow/drupal/blob/3100115-from-3100110-5/core/module...
    note this doesn't have fixes above and probably others but it might give some idea of the new logic that could be split out into new methods

      /**
       * Compares to support branches.
       *
       * Support branches can be in 3 formats, 8.x-1., 1., 1.1.. Comparison should
       * ignore the core prefix and @todo more
       *
       * @param string $branch1
       * @param string $branch2
       *
       * @return int
       *
       */
      private function compareSupportBranches($branch1, $branch2) {
    

    You could have 2.1. or 2. branch this actually after 8.x-1. so the comparison is tricky and benefits from a dedicated docblock

      /**
       * Determines if a support branch would be after a specified version.
       *
       * Support branches can be in 3 formats, 8.x-1., 1., 1.1.. Versions can be
       * in the format 8.x-1.1 or 1.0.1.
       *
       * @param string $supported_branch
       * @param string $version
       */
      private function isSupportBranchAfterVersion($supported_branch, $version) {
    

    again with branches and version in different formats it is tricky

      /**
       * Gets the support branch for a version.
       *
       * @param $version
       *
       * @return string|null
       */
      private function getVersionSupportedBranch($version) {
    

    This is all could maybe wrapped in a new method getTargetSupportBranch() because it is all more complicated then the existing logic for getting the target major. I think this demonstrates why I want have this all i class before #3100115. If the logic was moving files and being split out into these new functions the patch would be much harder to review.

    This is all I have so far and I didn't look into refactoring any of the existing logic that really isn't going to change. That could be non-urgent follow.There is probably the needed for other new methods that I have encountered yet. But I think not adding the new logic directly in the main function is already a clear enough benefit.,

  6. fixed
  7. fixed the space and changed to ::$projectData so now it is below 80 chars
  8. I don't think the memory problem is big enough out weigh benefits I mentioned in 5)
  9. I don't think we need unit tests since the logic is not changing also they would all need to be immediately re-written

re #7 I think we will go down the rabbit in #3100115 and that fact that all the logic change will be in 1 file will just make it easier to review.

tedbow’s picture

Issue summary: View changes

updated the summary.

the only other thing I wondering is if we should actually more all the methods in update.compare.inc into the class. The new version of \update_calculate_project_data() would be the only method that would need to made public. Nobody should be calling \update_calculate_project_update_status() directly But that would mean \update_calculate_project_update_status() could not call the new version in the and be deprecated. It would have to removed. Maybe we can't do that

Status: Needs review » Needs work

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

dww’s picture

Re: #8: Okay, I guess I need to look more closely at #3100115: The update module should recommend updates based on supported_branches rather than major versions majors ;)

Meanwhile:

  1. +++ b/core/modules/update/src/UpdateProjectStatus.php
    @@ -0,0 +1,468 @@
    +   * UpdateProjectStatus constructor.
    +   * @param array $project_data
    

    Nit: Aren't we supposed to have a newline between the 1-liner and the @param statements?

  2. +++ b/core/modules/update/src/UpdateProjectStatus.php
    @@ -0,0 +1,468 @@
    +   * @return array
    +   *   The updated project data. The following keys may be updated:
    

    Thanks for adding these docs!

  3. +++ b/core/modules/update/src/UpdateProjectStatus.php
    @@ -0,0 +1,468 @@
    +   *   - title: Copied from ::$availableReleases if not already set.
    +   *   - link: Copied from ::$availableReleases if not already set.
    

    You "typehint" most of the keys, but not these. Can we be consistent in the format / content of this info?

  4. +++ b/core/modules/update/src/UpdateProjectStatus.php
    @@ -0,0 +1,468 @@
    +   *   - 'extra'(array): An array of extra items for the update
    

    None of the other keys are 'quoted' like this. Any reason why you did that here?

  5. +++ b/core/modules/update/src/UpdateProjectStatus.php
    @@ -0,0 +1,468 @@
    +   *   - project_status: If the 'status' key is set to a non-empty value this
    +   *    will be copied from ::$availableReleases['project_status'].
    

    Need an extra space before "will" on the 2nd line for it to line up...

  6. +++ b/core/modules/update/src/UpdateProjectStatus.php
    @@ -0,0 +1,468 @@
    +   *   - also (array): Other available releases. The keys are major version
    +   *     numbers and the values are arrays of releases keyed by version number.
    

    Dumb question: are these actually keyed by "major" version number now? ;) I've lost track...

  7. +++ b/core/modules/update/src/UpdateProjectStatus.php
    @@ -0,0 +1,468 @@
    +       $this->projectData;
    ...
    +       $this->projectData;
    ...
    +       $this->projectData;
    ...
    +       $this->projectData;
    ...
    +       $this->projectData;
    

    WTF? ;) Search/replace gone wrong? I think all of these are missing a return in front of them? Sure hope the bot fails #8. ;)

  8. +++ b/core/modules/update/update.compare.inc
    @@ -141,60 +142,10 @@ function update_calculate_project_data($available) {
    + * @deprecated in Drupal 8.9.0 and will be removed before Drupal 9.0.0. There is
    
    @@ -202,340 +153,6 @@ function update_calculate_project_data($available) {
    +  @trigger_error(__FUNCTION__ . '() is deprecated in Drupal 8.9.0 and will be removed before Drupal 9.0.0. There is no replacement. See https://why-did-u-call-this.com', E_USER_DEPRECATED);
    

    I don't think this can be true. We're not adding new deprecations in 8.9.x that are immediately removed in 9.0.0.

dww’s picture

Hah, x-post with the bot. Phew! Glad that failed. The fails even look reasonable on a quick skim given the bugs in #8 with the missing returns.

tedbow’s picture

Status: Needs work » Needs review
StatusFileSize
new2.51 KB
new41.71 KB
+++ b/core/modules/update/src/UpdateProjectStatus.php
@@ -0,0 +1,468 @@
+       $this->projectData;

I had a bunch of these where I meant search/replace return; for return $this->projectData but somehow messed it up.

dww’s picture

Status: Needs review » Needs work

Interdiff in #13 looks like it fixes #11.7. Everything else in #11 is still to-be-addressed.

Thanks,
-Derek

tedbow’s picture

Status: Needs work » Needs review
StatusFileSize
new3.64 KB
new41.68 KB

re #14 yeah I was just addressing test failure didn't notice you had already posted another review

re #11
@dww thanks for the review

  1. fixed also update the comment to be more standard for what we use for constructors
  2. sure!
  3. yeah wasn't going to type hint because they are just copied we are absolutely sure what they are.

    but now see the ultimately come from the xml. Adding type hints and removing the comment about where they are copied from.
    also update fetch_status

  4. Nope just forgot to remove the quotes from this one when I figured out they weren't needed.
  5. fixed. also add (string) figured out this comes from the xml directly
  6. yep see
    $this->projectData['also'][$release_major_version] = $version;
    I think the idea here is you maybe in support major like 8.x-1.x but there multiple new majors like 8.x-2.x and 8.x-3.x
  7. already fixed
  8. Change say it will be removed in 10.0.0

    I guess we need to make change record unless update_calculate_project_update_status() was never to be called directly.(also see #9)

tedbow’s picture

Status: Needs review » Needs work

I talked with @tim.plunkett about this issue too. He had some the concerns that @dww had about just making it a class without actually refactoring but also saw my point about #3100115: The update module should recommend updates based on supported_branches rather than major versions majors being very hard to review if we don't make the new class first.

Really refactoring this into a value object would be bigger task than we probably have time for now. Because then we would need to update a lot more places in the update module that use this project info.

So as an alternative is we could really just make a new utility class that has a public static method that keeps a similar signature as update_calculate_project_update_status() in that takes the project data by reference. So instead of doing
$projects[$project] =(new UpdateProjectStatus($projects['drupal'], $available['drupal']))->getUpdateProjectData()

We would do something like
UpdateProjectCalculator::calculateStatus($projects['drupal'], $available['drupal']);

This would still make it possible change the logic in #3100115: The update module should recommend updates based on supported_branches rather than major versions majors and split the new logic into methods without introducing new global functions(which then become defacto apis) and not adding a bunch of new anonymous functions like we added $is_in_supported_branch that are more difficult to document.

I will work on this.

tedbow’s picture

Status: Needs work » Needs review
StatusFileSize
new24.22 KB
new40.2 KB

here is the idea in #16 basically changing a value object to a static helper function.

I haven't had a chance to fix coding standard issues but this should at least show the idea

tedbow’s picture

StatusFileSize
new7.58 KB
new40.35 KB

Fixing coding standards for comments. I didn't fix everything in the original function but since it is indented more I fixed that.

tedbow’s picture

🤷‍♂️👽

tedbow’s picture

+++ b/core/modules/update/update.compare.inc
@@ -141,60 +142,13 @@ function update_calculate_project_data($available) {
+ * \Drupal\update\UpdateProjectStatus::getUpdateProjectData().
+ * @deprecated in Drupal 8.9.0 and will be removed before Drupal 10.0.0. There
+ * is no replacement. To get the calculated project update status for all
+ * projects update_calculate_project_data() should be used.

I suggested update_calculate_project_data() as an alternative because this is the only place the original update_calculate_project_update_status() was called. Before it was called we have

$projects = \Drupal::service('update.manager')->getProjects();
update_process_project_info($projects);

And the logic expects this. So it seems like update_calculate_project_data() should be the entry point for the API.

if a module needed to call it just for 1 module they could do
update_calculate_project_data([$available['my_module']])

They would have until Drupal 10 to fix this.

dww’s picture

Status: Needs review » Needs work

A) Some other patches that touch this function have already landed, and hopefully #2992631: Update report incorrectly recommends security releases for old minors when a security update is needed and a secure version of the old minor is also available will also land soon. So this will need a re-roll at least. Should probably wait for #2992631 to land first.

B) After working on a bunch of recent Update manager issues, some of which deal with obscure bugs in this function, I'm getting a sense of ways we could split this up to be more understandable and less buggy/fragile. This function does 4 main things:

  1. Checks the status of your currently installed version of each project. E.g. to deal with things like revoked/unpublished, etc. Included in this would be project-wide status values, which take precedence over release-specific status.
  2. Finds the recommended, and optionally latest/also available releases we want to handle and inform admins about.
  3. Find all the relevant security updates a site is missing. Whether we print all of them (current) or only the most recent (e.g. #2865920: Add tests for when a single supported branch has 2 security updates that are both not insecure) is a UI decision, but either way, we have to know everything you're missing that impacts you.
  4. Deals with date comparisons and other mojo if you're running a -dev release.

It currently does all 4 jobs in one convoluted pass through the release history XML for a given project. Originally, this was an attempt to be more efficient, use less RAM, spend less time computing all this stuff, etc. But weird interactions between the 4 leads to bugs like #2992631. In exchange for spending more cycles (and potentially eating more RAM), we could have something easier to make sense of and more resilient. We could iterate over the release history 4 times, in 4 separate/targeted protected helper functions, that each handle one of these jobs. Then we're not mixing up responsibilities, there will be no weird side effects, it'll be easier to document (and test!) each method, and each one will be a lot shorter and less magical than what we have now.

If we do *that* as part of this issue, I think we'll really be gaining something valuable. Short of that, I think we're just playing with OO syntax for little or no benefit.

Cheers,
-Derek

p.s. Edit: And we wouldn't need to iterate the 4th time for -dev unless you're on a -dev release, so for most projects on most sites, we'd only iterate thrice.

dww’s picture

Adding #3111767: API to provide a recommended next version for project as also related. If we do it right, that could be solved by #21B.2

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

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

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

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

tedbow’s picture

Created a related issue #3206293: Create ProjectRelease class to represent Update XML releases

I am starting to work on the Automatic Updates functionality and found myself accessing the release data via array keys. So I think that would something nice to do before we have the new module. Also I think it makes the current function update_calculate_project_data more readable.

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

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

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

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

tedbow’s picture

Pushing up my work in progress on another idea.

+++ b/core/modules/update/update.compare.inc
@@ -111,7 +112,7 @@ function update_calculate_project_data($available) {
-      update_calculate_project_update_status($projects[$project], $available[$project]);
+      UpdateProjectStatus::calculateProjectStatus($projects[$project], $available[$project]);

My previous idea was just swap this function directly for a class but now I think it is not a great to just pass in $projects[$project] by reference and have it add values directly to this array.

So I started a Merge Request from scratch that has a new class called ProjectStatusCalculator that will have well defined methods like getRecommendReleaseForTargetMajor()

So the new merge request will leave update_calculate_project_update_status() in place for now but replace a lot of its logic with calls to the methods on ProjectStatusCalculator

dww’s picture

Thanks for working on this again!

Before you get too far on a whole new approach, what do you think of my proposal in #21 on how to reorganize the logic?

Thanks again,
-Derek

tedbow’s picture

Assigned: Unassigned » tedbow

I think the new MR kind of handles points 2) and 3) from #21

I am not opposed to the other points but I am not sure we have to do them all in 1 issue. If we only handled part of it I think we would want to rescope this issue.

from #21

We could iterate over the release history 4 times, in 4 separate/targeted protected helper functions, that each handle one of these jobs. Then we're not mixing up responsibilities, there will be no weird side effects, it'll be easier to document (and test!) each method, and each one will be a lot shorter and less magical than what we have now.

That is very similar to what I am doing in this MR.

the new

/**
   * Gets all the installable releases up to and including the existing version.
   *
   * @return array[]
   *   The releases.
   */
  private function getInstallableReleases(): array {

allows 4 separate new public functions on the class to not have handle the actually looping and filtering of releases that should not be consider in any of the cases, and aren't now.

Also because it keeps static $releases even though each of the helper functions can all it but iterating through all releases will still only happen once.

I have to take off now but will look back more at #21 to see how it compares to this and leave a more detailed comment

tedbow’s picture

Note more tests are passing with the last few commits
PHP 8.0 & MySQL 5.7 6 pass, 8 fail
Only show if the test class all pass not individual test methods.

tedbow’s picture

I thinking has evolved since I first made this issue 😜

re @dww in #7

IMHO, this patch is of almost no use if it just wraps the huge function (and comment) in class syntax but otherwise leaves it as-is.

I mostly agree with this now. It would be of some use but I won't leave us in a much better state. My first plan was we would then iterate over the class in follow-up issue but.....

re @dww in #6

If only Drupal was OO friendly and API-by-array wasn't the Drupal Way(tm) to do everything when I wrote all this in the first place, perhaps you wouldn't be faced with this annoying task now. ;)

I think another problem with my patches in #18 and before I was still making a class that was just passing the $project_data array by reference and altering it so continuing at our API-by-array problem

In the current merge request(1537) I have changed this to follow the pattern we used in #3206293: Create ProjectRelease class to represent Update XML releases. We created the public factory method \Drupal\update\ProjectRelease::createFromArray() and kept the constructor private. This way it allows in the future, if it makes sense, when we are able to not depend on the array as API to deprecate createFromArray() and either create another factory or make the constructor public. Then everywhere we need information about the release we no longer have to rely on the array structure but just call the public methods like ProjectRelease::getDownloadUrl()

We can't do much about the fact that update_calculate_project_update_status() follows the API-by-array pattern. It is public function so we can't just remove it or change it's signature. At some point we should probably deprecate it but I am not sure we can do that before Drupal 10. We can however change it internally to as much as possible to just alter the array by calling public methods on well scoped classes. This what I am trying to do in the merge request. I am not sure if we need to do it all in 1 issue.

Back to @dww suggestions in #21

  1. For project status or really $project_data['status'] as this is set in the currently this is bit tricky to replace completely with ProjectStatusCalculator::getStatus() by doing something like

    $project_data['status'] = $status_calculator->getStatus();

    Because for instance
    $project_data['status'] = UpdateManagerInterface::NOT_SUPPORTED
    appears multiple times. Once it is stores project_status from the XML, another time it is because the installed version is not in a supported branch and another time it is because installed version is marked unsupported.

    I don't think we want to cover all those cases in getStatus() of the new class because the method would give no indication of how whether that status reflects project status on Drupal.org from the XML or is related to the specific release they had have installed.
    Currently update_calculate_project_update_status() handles this by in some cases by setting $project_data['extra'] and other cases by settings $project_data['reason'] to give more context. But these seem very tied to how they will be displayed because they seem to both store the human readable "reason" for that status.

    One way to handle this would to not move the "Reason" and "Extra" logic into the new class in this issue. The current merge request adds another class UpdateServerProjectInfo(open to a better name) we could add UpdateServerProjectInfo::getStatus() and let the logic for reacting to that stay in update_calculate_project_update_status() for now.

  2. Finds the recommended, and optionally latest/also available releases we want to handle and inform admins about.

    These are covered by the new public methods on ProjectStatusCalculator: getLatestReleaseForTargetMajor(), getRecommendReleaseForTargetMajor(), getReleasesInMajorsGreaterThanTarget()currently "also" getDevReleaseForTargetMajor()and getLatestDev()

  3. re security releases, this handled by getSecurityReleases()
tedbow’s picture

Assigned: tedbow » Unassigned
Status: Needs work » Needs review
tedbow’s picture

tedbow’s picture

Tagging with "Automatic Updates Initiative" because this would make it much use to write a new method like getRecommendReleaseInTargetMinor()

phenaproxima’s picture

Hiding the patches in favor of the merge request.

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

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

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

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new150 bytes

The Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

voleger’s picture

Issue tags: +Needs reroll

Needs reroll against 10.1.x branch.

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

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

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

quietone’s picture

Issue summary: View changes

Good sign that tests are passing. I've updated the remaining tasks.

quietone’s picture

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

smustgrave changed the visibility of the branch 3100110-compare-new-old to hidden.

smustgrave changed the visibility of the branch 3100110-minimal-changes to hidden.

smustgrave’s picture

Status: Needs review » Needs work

Introduces ProjectStatusCalculator class we should consider if this should be @internal or now.

Left some comments but looking at the issue summary should the class be called ProjectStatusCalculator?

quietone’s picture

Issue summary: View changes

Rebased the older MR, 1761, to 11.x and merged MR 4711. Then closed MR 4711.

quietone changed the visibility of the branch 3100110-minimal-changes to active.

quietone changed the visibility of the branch 3100110-11.x to hidden.

quietone’s picture

Issue summary: View changes
quietone’s picture

Status: Needs work » Needs review

Found an earlier issue which creates an UpdateParser servicer with a method calculateProjectUpdateStatus that replaces this function. #2167779: Break up Update system classes. Has that approach been considered?

Changing status to get answers to the this question.

quietone’s picture

Issue summary: View changes

It is understandable why it would make some things easier if this was done before #2990476: If the site is on an insecure version of an old minor and there is a secure version of that old minor available, the update status report should link that release, or any issue that touches update_calculate_project_update_status(). However, that issue is a Critical bug and that should not wait on an architectural change. That bug was reported in 2018, 16 months before this issue was opened and judging by the comments more discussion is needed here.

I've updated the Issue Summary accordingly.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new90 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

quietone’s picture

Status: Needs work » Needs review
Issue tags: +no-needs-review-bot
voleger’s picture

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

MR !1761 needs rebase

Version: 11.x-dev » main

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

Read more in the announcement.

nicxvan’s picture