Skip to content

Commit dc48235

Browse files
committed
Issue #3490843 by nicxvan, smustgrave, oily, joachim, berdir, alexpott, nikolay shapovalov, dww: Create class to replace hook_requirements('install')
1 parent 93974c2 commit dc48235

File tree

10 files changed

+280
-3
lines changed

10 files changed

+280
-3
lines changed

includes/install.inc

+51-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ use Drupal\Component\Utility\Unicode;
99
use Drupal\Component\Utility\UrlHelper;
1010
use Drupal\Core\Database\Database;
1111
use Drupal\Core\Extension\Dependency;
12+
use Drupal\Core\Extension\Extension;
1213
use Drupal\Core\Extension\ExtensionDiscovery;
14+
use Drupal\Core\Extension\InstallRequirementsInterface;
1315
use Drupal\Core\Installer\InstallerKernel;
1416
use Symfony\Component\HttpFoundation\RedirectResponse;
1517

@@ -616,6 +618,8 @@ function drupal_check_profile($profile) {
616618
if (function_exists($function)) {
617619
$requirements = array_merge($requirements, $function('install'));
618620
}
621+
622+
$requirements = array_merge($requirements, install_check_class_requirements($module_list[$module]));
619623
}
620624
}
621625

@@ -625,6 +629,9 @@ function drupal_check_profile($profile) {
625629
$requirements = array_merge($requirements, $function('install'));
626630
}
627631

632+
$extension = \Drupal::service('extension.list.profile')->get($profile);
633+
$requirements = array_merge($requirements, install_check_class_requirements($extension));
634+
628635
return $requirements;
629636
}
630637

@@ -660,13 +667,16 @@ function drupal_requirements_severity(&$requirements) {
660667
function drupal_check_module($module) {
661668
/** @var \Drupal\Core\Extension\ModuleExtensionList $module_list */
662669
$module_list = \Drupal::service('extension.list.module');
663-
$file = DRUPAL_ROOT . '/' . $module_list->getPath($module) . "/$module.install";
670+
$extension = $module_list->get($module);
671+
$file = \Drupal::root() . '/' . $extension->getPath() . "/$module.install";
664672
if (is_file($file)) {
665673
require_once $file;
666674
}
667675
// Check requirements
668-
$requirements = \Drupal::moduleHandler()->invoke($module, 'requirements', ['install']);
669-
if (is_array($requirements) && drupal_requirements_severity($requirements) == REQUIREMENT_ERROR) {
676+
$requirements = \Drupal::moduleHandler()->invoke($module, 'requirements', ['install']) ?? [];
677+
$requirements = array_merge($requirements, install_check_class_requirements($extension));
678+
679+
if (!empty($requirements) && drupal_requirements_severity($requirements) == REQUIREMENT_ERROR) {
670680
// Print any error messages
671681
foreach ($requirements as $requirement) {
672682
if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
@@ -790,3 +800,41 @@ function install_profile_info($profile, $langcode = 'en') {
790800
}
791801
return $cache[$profile][$langcode];
792802
}
803+
804+
/**
805+
* Checks a module or profile requirements.
806+
*
807+
* See \Drupal\Core\Extension\InstallRequirementsInterface for more information.
808+
*
809+
* @param \Drupal\Core\Extension $extension
810+
* The extension to check install requirements for.
811+
*
812+
* @return array
813+
* The requirements.
814+
*
815+
* @internal
816+
*/
817+
function install_check_class_requirements(Extension $extension): array {
818+
$extension_path = $extension->getPath();
819+
$extension_name = $extension->getName();
820+
$dir = \Drupal::root() . "/$extension_path/src/Install/Requirements";
821+
$requirements = [];
822+
if (is_dir($dir)) {
823+
$fileSystemIterator = new FilesystemIterator($dir);
824+
foreach ($fileSystemIterator as $fileInfo) {
825+
if ($fileInfo->isFile() && $fileInfo->getExtension() === 'php') {
826+
$filename = $fileInfo->getFilename();
827+
$requirements_path = $dir . '/' . $filename;
828+
require_once $requirements_path;
829+
830+
$namespace = "Drupal\\$extension_name\\Install\\Requirements";
831+
$class_name = $namespace . '\\' . $fileInfo->getBasename('.php');
832+
if (class_exists($class_name) && class_implements($class_name, InstallRequirementsInterface::class)) {
833+
$requirements = $class_name::getRequirements();
834+
}
835+
}
836+
}
837+
838+
}
839+
return $requirements;
840+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace Drupal\Core\Extension;
4+
5+
/**
6+
* Provides method for checking requirements during install time.
7+
*/
8+
interface InstallRequirementsInterface {
9+
10+
/**
11+
* Check installation requirements.
12+
*
13+
* Classes implementing this must be in the Install/Requirements namespace.
14+
* For example src/Install/Requirements/ModuleNameRequirements.php.
15+
*
16+
* During the 'install' phase, modules can for example assert that
17+
* library or server versions are available or sufficient.
18+
* Note that the installation of a module can happen during installation of
19+
* Drupal itself (by install.php) with an installation profile or later by hand.
20+
* As a consequence, install-time requirements must be checked without access
21+
* to the full Drupal API, because it is not available during install.php.
22+
* If a requirement has a severity of REQUIREMENT_ERROR, install.php will abort
23+
* or at least the module will not install.
24+
* Other severity levels have no effect on the installation.
25+
* Module dependencies do not belong to these installation requirements,
26+
* but should be defined in the module's .info.yml file.
27+
*
28+
* During installation, if you need to load a class from your module,
29+
* you'll need to include the class file directly.
30+
*
31+
* @return array
32+
* An associative array where the keys are arbitrary but must be unique (it
33+
* is suggested to use the module short name as a prefix) and the values are
34+
* themselves associative arrays with the following elements:
35+
* - title: The name of the requirement.
36+
* - value: This should only be used for version numbers, do not set it if
37+
* not applicable.
38+
* - description: The description of the requirement/status.
39+
* - severity: (optional) The requirement's result/severity level, one of:
40+
* - REQUIREMENT_INFO: For info only.
41+
* - REQUIREMENT_OK: The requirement is satisfied.
42+
* - REQUIREMENT_WARNING: The requirement failed with a warning.
43+
* - REQUIREMENT_ERROR: The requirement failed with an error.
44+
* Defaults to REQUIREMENT_OK when installing.
45+
*/
46+
public static function getRequirements(): array;
47+
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
name: 'Test Install Requirements Class'
2+
type: module
3+
description: 'Support module for testing install requirements.'
4+
package: Testing
5+
version: VERSION
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Drupal\module_install_requirements\Install\Requirements;
6+
7+
use Drupal\Core\Extension\InstallRequirementsInterface;
8+
9+
/**
10+
* Provides method for checking requirements during install time.
11+
*/
12+
class ModuleInstallRequirements implements InstallRequirementsInterface {
13+
14+
/**
15+
* {@inheritdoc}
16+
*/
17+
public static function getRequirements(): array {
18+
$GLOBALS['module_install_requirements'] = 'module_install_requirements';
19+
20+
return [];
21+
}
22+
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
name: 'Test Install Requirements Class'
2+
type: module
3+
description: 'Support module for testing install unmet requirements.'
4+
package: Testing
5+
version: VERSION
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Drupal\module_install_unmet_requirements\Install\Requirements;
6+
7+
use Drupal\Core\Extension\InstallRequirementsInterface;
8+
9+
/**
10+
* Provides method for checking requirements during install time.
11+
*/
12+
class ModuleInstallUnmetRequirementsRequirements implements InstallRequirementsInterface {
13+
14+
/**
15+
* {@inheritdoc}
16+
*/
17+
public static function getRequirements(): array {
18+
$requirements['testing_requirements'] = [
19+
'title' => t('Testing requirements'),
20+
'severity' => REQUIREMENT_ERROR,
21+
'description' => t('Testing requirements failed requirements.'),
22+
];
23+
24+
return $requirements;
25+
}
26+
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
name: 'Test Install Requirements'
2+
type: profile
3+
hidden: true
4+
description: 'Support profile for testing install requirements.'
5+
version: VERSION
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Drupal\profile_install_requirements\Install\Requirements;
6+
7+
use Drupal\Core\Extension\InstallRequirementsInterface;
8+
9+
/**
10+
* Provides method for checking requirements during install time.
11+
*/
12+
class ProfileInstallRequirementsRequirements implements InstallRequirementsInterface {
13+
14+
/**
15+
* {@inheritdoc}
16+
*/
17+
public static function getRequirements(): array {
18+
$requirements['testing_requirements'] = [
19+
'title' => t('Testing requirements'),
20+
'severity' => REQUIREMENT_ERROR,
21+
'description' => t('Testing requirements failed requirements.'),
22+
];
23+
24+
return $requirements;
25+
}
26+
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Drupal\FunctionalTests\Installer;
6+
7+
/**
8+
* Tests installing a profile that implements InstallRequirementsInterface.
9+
*
10+
* @group Installer
11+
*/
12+
class ProfileRequirementsTest extends InstallerTestBase {
13+
14+
/**
15+
* {@inheritdoc}
16+
*/
17+
protected $profile = 'profile_install_requirements';
18+
19+
/**
20+
* {@inheritdoc}
21+
*/
22+
protected $defaultTheme = 'stark';
23+
24+
/**
25+
* {@inheritdoc}
26+
*/
27+
protected function setUpSettings(): void {
28+
// This form will never be reached.
29+
}
30+
31+
/**
32+
* {@inheritdoc}
33+
*/
34+
protected function setUpRequirementsProblem(): void {
35+
// The parent method asserts that there are no requirements errors, but
36+
// this test expects a requirements error in the test method below.
37+
// Therefore, we override this method to suppress the parent's assertions.
38+
}
39+
40+
/**
41+
* {@inheritdoc}
42+
*/
43+
protected function setUpSite(): void {
44+
// This form will never be reached.
45+
}
46+
47+
/**
48+
* Test Requirements are picked up.
49+
*/
50+
public function testRequirementsFailure(): void {
51+
$this->assertSession()->pageTextContains('Testing requirements failed requirements.');
52+
}
53+
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Drupal\KernelTests\Core\Installer;
6+
7+
use Drupal\KernelTests\KernelTestBase;
8+
9+
/**
10+
* Tests for install requirements.
11+
*
12+
* @group Installer
13+
*/
14+
class InstallRequirementsTest extends KernelTestBase {
15+
16+
/**
17+
* Confirm installer checks requirements in designated classes.
18+
*/
19+
public function testRequirements(): void {
20+
require_once 'core/includes/install.inc';
21+
22+
$this->assertFalse(isset($GLOBALS['module_install_requirements']));
23+
drupal_check_module('module_install_requirements');
24+
$this->assertTrue(isset($GLOBALS['module_install_requirements']));
25+
}
26+
27+
/**
28+
* Tests that the installer returns false if module requirements are not met.
29+
*/
30+
public function testRequirementsFailure(): void {
31+
require_once 'core/includes/install.inc';
32+
$this->assertFalse(drupal_check_module('module_install_unmet_requirements'));
33+
}
34+
35+
}

0 commit comments

Comments
 (0)