Skip to content

Instantly share code, notes, and snippets.

@sun
Created January 16, 2014 06:07
Show Gist options
  • Save sun/8450528 to your computer and use it in GitHub Desktop.
Save sun/8450528 to your computer and use it in GitHub Desktop.
Singleton
<?php
/**
* @file
* Contains \Drupal\Core\Utility\Site.
*/
namespace Drupal\Core\Utility;
use Drupal\Component\Utility\Settings;
/**
* A utility class for easy access to the site path.
*/
class Site {
/**
* The absolute path to the Drupal root directory.
*
* @var string
*/
private $root;
/**
* The relative path to the site directory.
*
* May be an empty string, in case the site directory is the root directory.
*
* @var string
*/
private $path;
/**
* Whether the Site singleton was instantiated by the installer.
*
* @var bool
*/
private $isInstaller;
/**
* The original Site instance of the test runner during test execution.
*
* @see \Drupal\Core\Utility\Site::setUpTest()
* @see \Drupal\Core\Utility\Site::tearDownTest()
*
* @var \Drupal\Core\Utility\Site
*/
private $original;
/**
* The Site singleton instance.
*
* @var \Drupal\Core\Utility\Site
*/
private static $instance;
/**
* Initializes the Site singleton.
*
* @param string $root_directory
* The root directory to use for absolute paths; i.e., DRUPAL_ROOT.
* @param array $sites
* (optional) A multi-site mapping, as defined in settings.php.
* @param string $custom_path
* (optional) An explicit site path to set; skipping site negotiation.
* This can be defined as $conf_path in the root /settings.php file.
*
* @see drupal_settings_initialize()
*/
public static function init($root_directory, array $sites = NULL, $custom_path = NULL) {
if (!isset(self::$instance)) {
new self($root_directory);
}
// Only the installer environment is allowed instantiate the Site singleton
// prior to drupal_settings_initialize().
// @see initInstaller()
elseif (!self::$instance->isInstaller()) {
throw new \BadMethodCallException('Site path is initialized already.');
}
// Force-override the site directory in tests.
if ($test_prefix = drupal_valid_test_ua()) {
$custom_path = 'sites/simpletest/' . substr($test_prefix, 10);
}
self::$instance->initializePath($sites, $custom_path);
}
/**
* Initializes the Site singleton for the early installer environment.
*
* The installer uses this function to prime the site directory path very
* early in the installer environmnt. This allows the application to be
* installed into a new and empty site directory, which does not contain a
* settings.php yet.
*
* @param string $root_directory
* The root directory to use for absolute paths; i.e., DRUPAL_ROOT.
*
* @see install_begin_request()
*/
public static function initInstaller($root_directory) {
// WebTestBase::setUp() invokes Site::setUpTest() prior to invoking the
// non-interactive installer, in order to override the singleton and prime
// a custom site directory.
if (drupal_valid_test_ua()) {
return;
}
if (isset(self::$instance)) {
throw new \BadMethodCallException('Site path is initialized already.');
}
// Set a global state flag to denote that we are operating in the special
// installer environment. initializePath() is unnecessary, as init() will be
// called by drupal_settings_initialize(), possibly passing new values from
// the newly created settings.php file.
new self($root_directory, TRUE);
}
/**
* Re-initializes the Site singleton for a test run.
*
* Called by WebTestBase::setUp() prior to invoking the non-interactive
* installer for a test.
*
* @param string $root_directory
* The root directory to use for absolute paths; i.e., DRUPAL_ROOT.
* @param string $custom_path
* The relative site directory path to use during the test run; e.g.,
* 'sites/simpletest/123456'.
*/
public static function setUpTest($root_directory, $custom_path) {
if (!drupal_valid_test_ua()) {
throw new \BadMethodCallException('Site is not executing a test.');
}
if (!file_exists($root_directory . '/' . $custom_path)) {
throw new \InvalidArgumentException("Test site directory '$custom_path' does not exist.");
}
new self($root_directory, TRUE, $custom_path);
}
/**
* Reverts the Site singleton to the original after a test run.
*/
public static function tearDownTest() {
if (!isset(self::$instance->original)) {
throw new \RuntimeException('No original Site to revert to. Missing invocation of Site::setUpTest()?');
}
self::$instance = self::$instance->original;
}
/**
* Constructs the Site singleton.
*/
private function __construct($root_directory, $is_installer = FALSE, $custom_path = NULL) {
if (isset(self::$instance->original)) {
throw new \RuntimeException('Site is overridden for a test already. Duplicate invocation of Site::setUpTest()?');
}
if (isset(self::$instance)) {
$this->original = clone self::$instance;
}
$this->root = $root_directory;
$this->isInstaller = $is_installer;
$this->path = $custom_path;
self::$instance = $this;
}
/**
* Returns whether the Site singleton was instantiated for the installer.
*
* @todo Leverage this to eliminate drupal_installation_attempted()?
*/
private function isInstaller() {
return $this->isInstaller;
}
/**
* Initializes the site path.
*
* @param array $sites
* (optional) A multi-site mapping, as defined in settings.php.
* @param string $custom_path
* (optional) An explicit site path to set; skipping site negotiation.
*/
private function initializePath(array $sites = NULL, $custom_path = NULL) {
// When executing the non-interactive installer in the parent site/
// test-runner, WebTestBase::setUp() primes the site directory path via
// Site::setUpTest() already.
// @todo Remove this case by adding a 'site' parameter to drupal_install().
if (isset($this->path)) {
return;
}
// An explicitly defined $conf_path in /settings.php takes precedence.
if (isset($custom_path)) {
$this->path = $custom_path;
}
// If the multi-site functionality was enabled in /settings.php, discover
// the path for the current site.
// $sites just needs to be defined; an explicit mapping is not required.
elseif (isset($sites)) {
$this->path = $this->determinePath($sites, !$this->isInstaller());
}
// If the multi-site functionality is not enabled, the Drupal root
// directory is the site directory.
else {
$this->path = '';
}
}
/**
* Finds the appropriate configuration directory for a given host and path.
*
* Finds a matching configuration directory file by stripping the website's
* hostname from left to right and pathname from right to left. By default,
* the directory must contain a 'settings.php' file for it to match. If the
* parameter $require_settings is set to FALSE, then a directory without a
* 'settings.php' file will match as well. The first configuration
* file found will be used and the remaining ones will be ignored.
*
* The settings.php file can define aliases in an associative array named
* $sites. For example, to create a directory alias for
* http://www.drupal.org:8080/mysite/test whose configuration file is in
* sites/example.com, the array should be defined as:
* @code
* $sites = array(
* '8080.www.drupal.org.mysite.test' => 'example.com',
* );
* @endcode
*
* @see default.settings.php
*
* @param array $sites
* A multi-site mapping, as defined in settings.php.
* @param bool $require_settings
* Only configuration directories with an existing settings.php file
* will be recognized. Defaults to TRUE. During initial installation,
* this is set to FALSE so that Drupal can detect a matching directory,
* then create a new settings.php file in it.
*
* @return string
* The path of the matching configuration directory. May be an empty string,
* in case the site configuration directory is the root directory.
*
* @todo Inject a Request object in instead of relying on globals?
*/
private function determinePath(array $sites, $require_settings) {
// The hostname and optional port number, e.g. "www.example.com" or
// "www.example.com:8080".
$http_host = $_SERVER['HTTP_HOST'];
// The part of the URL following the hostname, including the leading slash.
$script_name = $_SERVER['SCRIPT_NAME'] ?: $_SERVER['SCRIPT_FILENAME'];
$uri = explode('/', $script_name);
$server = explode('.', implode('.', array_reverse(explode(':', rtrim($http_host, '.')))));
for ($i = count($uri) - 1; $i > 0; $i--) {
for ($j = count($server); $j > 0; $j--) {
$dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i));
// Check for an alias in $sites.
if (isset($sites[$dir])) {
$dir = $sites[$dir];
// A defined site alias from /settings.php should be valid.
// @todo Even skip the settings.php check?
if (!$require_settings) {
return "sites/$dir";
}
}
if ($require_settings) {
if (file_exists($this->root . '/sites/' . $dir . '/settings.php')) {
return "sites/$dir";
}
}
elseif (file_exists($this->root . '/sites/' . $dir)) {
return "sites/$dir";
}
}
}
return '';
}
/**
* Prefixes a given filepath with the site directory, if any.
*
* @param string $filepath
* The filepath to prefix.
*
* @return string
* The prefixed filepath.
*/
private function resolvePath($filepath) {
if ($filepath !== '' && $filepath[0] === '/') {
$filepath = substr($filepath, 1);
}
if ($this->path !== '') {
if ($filepath !== '') {
$filepath = $this->path . '/' . $filepath;
}
else {
$filepath = $this->path;
}
}
return $filepath;
}
/**
* Returns a given path as relative path to the site directory.
*
* Use this function instead of appending strings to the site path manually,
* because the site directory may be the root directory and thus the resulting
* path would be an absolute filesystem path.
*
* @param string $filepath
* (optional) A relative filepath to append to the site path.
*
* @return string
* The given $filepath, potentially prefixed with the site path.
*
* @see \Drupal\Core\Utility\Site::getAbsolutePath()
*/
public static function getPath($filepath = '') {
return self::$instance->resolvePath($filepath);
}
/**
* Returns a given path as absolute path in the site directory.
*
* @param string $filepath
* (optional) A relative filepath to append to the site path.
*
* @return string
* The given $filepath, potentially prefixed with the site path, as an
* absolute filesystem path.
*
* @see \Drupal\Core\Utility\Site::getPath()
*/
public static function getAbsolutePath($filepath = '') {
$filepath = self::$instance->resolvePath($filepath);
if ($filepath !== '') {
return self::$instance->root . '/' . $filepath;
}
else {
return self::$instance->root;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment