Verified Commit 3412b7bd authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3261447 by xjm, lauriii, ravi.shankar, Wim Leers, alexpott, tedbow,...

Issue #3261447 by xjm, lauriii, ravi.shankar, Wim Leers, alexpott, tedbow, daffie: Add an API for dynamically setting recommended and supported PHP versions based on known and predicted PHP release schedules
parent a40e8436
Loading
Loading
Loading
Loading
+0 −11
Original line number Diff line number Diff line
@@ -87,17 +87,6 @@ class Drupal {
   */
  const CORE_MINIMUM_SCHEMA_VERSION = 8000;

  /**
   * Minimum supported version of PHP.
   *
   * Below this version:
   * - New sites cannot be installed, except from within tests.
   * - Updates from previous Drupal versions can be run, but users are warned
   *   that Drupal no longer supports that PHP version.
   * - An error is shown in the status report that the PHP version is too old.
   */
  const MINIMUM_SUPPORTED_PHP = '8.1.0';

  /**
   * Minimum allowed version of PHP for Drupal to be bootstrapped.
   *
+109 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\Core\Utility;

/**
 * Provides an object for dynamically identifying the minimum supported PHP.
 */
final class PhpRequirements {

  /**
   * The minimum PHP version requirement for the installed Drupal version.
   *
   * This property is maintained to make the class testable.
   *
   * @var string
   *
   * @see version_compare()
   */
  private static $drupalMinimumPhp = \Drupal::MINIMUM_PHP;

  /**
   * The expected PHP version end-of-life dates, keyed by PHP minor version.
   *
   * The array keys are in 'major.minor' format, and the date values are in ISO
   * 8601 format.
   *
   * @var string[]
   *   An array of end-of-life dates in ISO 8601 format, keyed by the PHP minor
   *   version in 'major.minor' format. The list must be sorted in an ascending
   *   order by the date. Multiple versions EOL on the same day must be sorted
   *   by the PHP version.
   */
  private static $phpEolDates = [
    '7.3' => '2021-12-06',
    '7.4' => '2022-11-28',
    '8.0' => '2023-11-26',
    '8.1' => '2024-11-25',
  ];

  /**
   * This class should not be instantiated.
   */
  private function __construct() {
  }

  /**
   * Dynamically identifies the minimum supported PHP version based on the date.
   *
   * Drupal automatically increases the minimum supported PHP version from
   * \Drupal::MINIMUM_PHP to a newer version after PHP's documented end-of-life
   * date for the previous version.
   *
   * Below this version:
   * - New sites can be installed (to allow update deployment workflows that
   *   reinstall sites from configuration), but a warning is displayed in the
   *   installer that the PHP version is too old (except within tests).
   * - Updates from previous Drupal versions can be run, but users are warned
   *   that Drupal no longer supports that PHP version.
   * - An error is shown in the status report that the PHP version is too old.
   *
   * @param \DateTime|null $date
   *   The DateTime to check. Defaults to the current datetime (now) if NULL.
   *
   * @return string
   *   The minimum supported PHP version on the date in a PHP-standardized
   *   number format supported by version_compare(). For example, '8.0.2' or
   *   '8.1'. This will be the lowest PHP version above the minimum PHP version
   *   supported by Drupal that is still supported, or the highest known PHP
   *   version if no known versions are still supported.
   *
   * @see version_compare()
   */
  public static function getMinimumSupportedPhp(?\DateTime $date = NULL): string {
    // By default, use the current date (right now).
    $date = $date ?? new \DateTime('now');

    // In case no data are available or all known PHP versions in this class
    // are already end-of-life, default to the version that had the most recent
    // end-of-life (the key of the last element in the sorted array).
    // The string cast ensures the value is a string, even if the PHP EOL date
    // array is empty. As of PHP 8.1, version_compare() no longer accepts NULL
    // as a parameter; empty string must be used instead.
    $lowest_supported_version = $lowest_supported_version ?? (string) array_key_last(static::$phpEolDates);

    // Next, look at versions that are end-of-life after the current date.
    // Find the lowest PHP version that is still supported.
    foreach (static::$phpEolDates as $version => $eol_date) {
      $eol_datetime = new \DateTime($eol_date);

      if ($eol_datetime > $date) {
        // If $version is less than the previously discovered lowest supported
        // version, use $version as the lowest supported version instead.
        if (version_compare($version, $lowest_supported_version) < 0) {
          $lowest_supported_version = $version;
        }
      }
    }

    // If PHP versions older than the Drupal minimum PHP version are still
    // supported, return Drupal minimum PHP version instead.
    if (version_compare($lowest_supported_version, static::$drupalMinimumPhp) < 0) {
      return static::$drupalMinimumPhp;
    }

    // Otherwise, return the lowest supported PHP version.
    return $lowest_supported_version;
  }

}
+7 −2
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
use Drupal\Core\Extension\ExtensionLifecycle;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Link;
use Drupal\Core\Utility\PhpRequirements;
use Drupal\Core\Render\Markup;
use Drupal\Core\Site\Settings;
use Drupal\Core\StreamWrapper\PrivateStream;
@@ -31,6 +32,10 @@
 */
function system_requirements($phase) {
  global $install_state;

  // Get the current default PHP requirements for this version of Drupal.
  $minimum_supported_php = PhpRequirements::getMinimumSupportedPhp();

  // Reset the extension lists.
  /** @var \Drupal\Core\Extension\ModuleExtensionList $module_extension_list */
  $module_extension_list = \Drupal::service('extension.list.module');
@@ -284,10 +289,10 @@ function system_requirements($phase) {
  }

  // Check if the PHP version is below what Drupal supports.
  if (version_compare($phpversion, \Drupal::MINIMUM_SUPPORTED_PHP) < 0) {
  if (version_compare($phpversion, $minimum_supported_php) < 0) {
    $requirements['php']['description'] = t('Your PHP installation is too old. Drupal requires at least PHP %version. It is recommended to upgrade to PHP version %recommended or higher for the best ongoing support. See <a href="http://php.net/supported-versions.php">PHP\'s version support documentation</a> and the <a href=":php_requirements">Drupal PHP requirements</a> page for more information.',
      [
        '%version' => \Drupal::MINIMUM_SUPPORTED_PHP,
        '%version' => $minimum_supported_php,
        '%recommended' => \Drupal::RECOMMENDED_PHP,
        ':php_requirements' => 'https://www.drupal.org/docs/system-requirements/php-requirements',
      ]
+5 −3
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@

namespace Drupal\Tests\system\Functional\System;

use Drupal\Core\Utility\PhpRequirements;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\RequirementsPageTrait;

@@ -47,6 +48,7 @@ protected function setUp(): void {
   * Tests status report messages regarding the PHP version.
   */
  public function testStatusPage() {
    $minimum_php_version = PhpRequirements::getMinimumSupportedPhp();
    // Go to Administration.
    $this->drupalGet('admin/reports/status');
    $this->assertSession()->statusCodeEquals(200);
@@ -57,13 +59,13 @@ public function testStatusPage() {

    // Verify that an error is displayed about the PHP version if it is below
    // the minimum supported PHP.
    if (version_compare($phpversion, \Drupal::MINIMUM_SUPPORTED_PHP) < 0) {
    if (version_compare($phpversion, $minimum_php_version) < 0) {
      $this->assertErrorSummaries(['PHP']);
      $this->assertSession()->pageTextContains('Your PHP installation is too old. Drupal requires at least PHP ' . \Drupal::MINIMUM_SUPPORTED_PHP);
      $this->assertSession()->pageTextContains('Your PHP installation is too old. Drupal requires at least PHP ' . $minimum_php_version);
    }
    // Otherwise, there should be no error.
    else {
      $this->assertSession()->pageTextNotContains('Your PHP installation is too old. Drupal requires at least PHP ' . \Drupal::MINIMUM_SUPPORTED_PHP);
      $this->assertSession()->pageTextNotContains('Your PHP installation is too old. Drupal requires at least PHP ' . $minimum_php_version);
      $this->assertSession()->pageTextNotContains('Errors found');
    }

+2 −1
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@
use Drupal\Core\Session\UserSession;
use Drupal\Core\Site\Settings;
use Drupal\Core\Test\HttpClientMiddleware\TestHttpClientMiddleware;
use Drupal\Core\Utility\PhpRequirements;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\RequirementsPageTrait;
use GuzzleHttp\HandlerStack;
@@ -252,7 +253,7 @@ protected function setUpSettings() {
   * @see system_requirements()
   */
  protected function setUpRequirementsProblem() {
    if (version_compare(phpversion(), \Drupal::MINIMUM_SUPPORTED_PHP) < 0) {
    if (version_compare(phpversion(), PhpRequirements::getMinimumSupportedPhp()) < 0) {
      $this->continueOnExpectedWarnings(['PHP']);
    }
  }
Loading