. namespace core; use Psr\Container\ContainerInterface; /** * DI Container Helper. * * @package core * @copyright 2023 Andrew Lyons * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class di { /** @var ContainerInterface The stored container */ protected static ?ContainerInterface $container; /** * Get the DI Container. * * @return ContainerInterface */ public static function get_container(): ContainerInterface { if (!isset(self::$container)) { self::$container = self::create_container(); } return self::$container; } /** * Reset the DI Container. * * This is primarily intended for Unit Testing, and for use in Scheduled tasks. */ public static function reset_container(): void { self::$container = null; } /** * Finds an entry of the container by its identifier and returns it. * * This is a shortcut helper for \core\di::get_container()->get($id). * * @param string $id Identifier of the entry to look for. * @return mixed Entry. */ public static function get(string $id): mixed { return self::get_container()->get($id); } /** * Set an entry in the container by its identifier. * * @param string $id Identifier of the entry to set * @param mixed $value The value to set */ public static function set(string $id, mixed $value): void { // Please note that the `set` method is not a part of the PSR-11 standard. // We currently make use of PHP-DI which does have this method, but its use is not guaranteed. // If Moodle switches to alternative DI resolution, this method _must_ be updated to work with it. /** @var \DI\Container */ $container = self::get_container(); $container->set($id, $value); } /** * Create a new Container Instance. * * @return ContainerInterface */ protected static function create_container(): ContainerInterface { global $CFG; // PHP Does not support function autoloading. We must manually include the file. require_once("{$CFG->libdir}/php-di/php-di/src/functions.php"); // Configure the Container builder. $builder = new \DI\ContainerBuilder(); // At the moment we are using autowiring, but not automatic attribute injection. // Automatic attribute injection is a php-di specific feature. $builder->useAutowiring(true); if (!$CFG->debugdeveloper) { // Enable compilation of the container and write proxies to disk in production. // See https://php-di.org/doc/performances.html for information. $cachedir = make_localcache_directory('di'); $builder->enableCompilation($cachedir); $builder->writeProxiesToFile(true, $cachedir); } // Get the hook manager. $hookmanager = \core\hook\manager::get_instance(); // Configure some basic definitions. $builder->addDefinitions([ // The hook manager should be in the container. \core\hook\manager::class => $hookmanager, // The database. \moodle_database::class => function(): \moodle_database { global $DB; return $DB; }, // The string manager. \core_string_manager::class => fn() => get_string_manager(), // The Moodle Clock implementation, which itself is an extension of PSR-20. // Alias the PSR-20 clock interface to the Moodle clock. They are compatible. \core\clock::class => function () { global $CFG; // Web requests to the Behat site can use a frozen clock if configured. if (defined('BEHAT_SITE_RUNNING') && !empty($CFG->behat_frozen_clock)) { require_once($CFG->libdir . '/testing/classes/frozen_clock.php'); return new \frozen_clock((int)$CFG->behat_frozen_clock); } return new \core\system_clock(); }, \Psr\Clock\ClockInterface::class => \DI\get(\core\clock::class), // Note: libphonenumber PhoneNumberUtil uses a singleton. \libphonenumber\PhoneNumberUtil::class => fn() => \libphonenumber\PhoneNumberUtil::getInstance(), ]); // Add any additional definitions using hooks. $hookmanager->dispatch(new \core\hook\di_configuration($builder)); // Build the container and return. return $builder->build(); } }