DrupalKernel.php 12.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
<?php

/**
 * @file
 * Definition of Drupal\Core\DrupalKernel.
 */

namespace Drupal\Core;

10
use Drupal\Core\Cache\CacheBackendInterface;
11
12
use Symfony\Component\ClassLoader\UniversalClassLoader;
use Drupal\Core\Config\FileStorage;
13
use Drupal\Core\CoreBundle;
14
use Drupal\Component\PhpStorage\PhpStorageInterface;
15
16
17
18
use Symfony\Component\HttpKernel\Kernel;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
19
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
20
21
22

/**
 * The DrupalKernel class is the core of Drupal itself.
23
24
25
26
27
28
29
 *
 * This class is responsible for building the Dependency Injection Container and
 * also deals with the registration of bundles. It allows registered bundles to
 * add their services to the container. Core provides the CoreBundle, which adds
 * the services required for all core subsystems. Each module can then add its
 * own bundle, i.e. a subclass of Symfony\Component\HttpKernel\Bundle, to
 * register services to the container.
30
 */
31
class DrupalKernel extends Kernel implements DrupalKernelInterface {
32

33
34
35
36
  /**
   * Holds the list of enabled modules.
   *
   * @var array
37
38
   *   An associative array whose keys are module names and whose values are
   *   ignored.
39
40
41
42
   */
  protected $moduleList;

  /**
43
   * Holds an updated list of enabled modules.
44
   *
45
46
47
48
49
50
51
52
53
54
55
56
57
   * @var array
   *   An associative array whose keys are module names and whose values are
   *   ignored.
   */
  protected $newModuleList;

  /**
   * An array of module data objects.
   *
   * The data objects have the same data structure as returned by
   * file_scan_directory() but only the uri property is used.
   *
   * @var array
58
   */
59
  protected $moduleData = array();
60
61
62
63
64
65
66
67

  /**
   * PHP code storage object to use for the compiled container.
   *
   * @var \Drupal\Component\PhpStorage\PhpStorageInterface
   */
  protected $storage;

68
69
70
71
72
73
74
75
76
77
78
79
80
81
  /**
   * The classloader object.
   *
   * @var \Symfony\Component\ClassLoader\UniversalClassLoader
   */
  protected $classLoader;

  /**
   * The list of the classnames of the bundles in this kernel.
   *
   * @var array
   */
  protected $bundleClasses;

82
83
84
85
86
87
88
89
90
91
92
  /**
   * Constructs a DrupalKernel object.
   *
   * @param string $environment
   *   String indicating the environment, e.g. 'prod' or 'dev'. Used by
   *   Symfony\Component\HttpKernel\Kernel::__construct(). Drupal does not use
   *   this value currently. Pass 'prod'.
   * @param bool $debug
   *   Boolean indicating whether we are in debug mode. Used by
   *   Symfony\Component\HttpKernel\Kernel::__construct(). Drupal does not use
   *   this value currently. Pass TRUE.
93
94
95
96
97
98
99
100
101
102
103
   * @param \Symfony\Component\ClassLoader\UniversalClassLoader $class_loader
   *   (optional) The classloader is only used if $storage is not given or
   *   the load from storage fails and a container rebuild is required. In
   *   this case, the loaded modules will be registered with this loader in
   *   order to be able to find the module bundles.
   * @param \Drupal\Component\PhpStorage\PhpStorageInterface $storage
   *   (optional) An object handling the load and save of the compiled
   *   container. If not specified, the container will neither be stored to
   *   disk nor read from there.
   */
  public function __construct($environment, $debug, UniversalClassLoader $class_loader, PhpStorageInterface $storage = NULL) {
104
    parent::__construct($environment, $debug);
105
106
    $this->storage = $storage;
    $this->classLoader = $class_loader;
107
108
  }

109
110
111
112
113
114
115
116
117
118
  /**
   * Overrides Kernel::init().
   */
  public function init() {
    // Intentionally empty. The sole purpose is to not execute Kernel::init(),
    // since that overrides/breaks Drupal's current error handling.
    // @todo Investigate whether it is possible to migrate Drupal's error
    //   handling to the one of Kernel without losing functionality.
  }

119
120
121
122
123
124
125
126
127
128
129
  /**
   * Overrides Kernel::boot().
   */
  public function boot() {
    if ($this->booted) {
      return;
    }
    $this->initializeContainer();
    $this->booted = TRUE;
  }

130
131
132
  /**
   * Returns an array of available bundles.
   */
133
  public function registerBundles() {
134
    $bundles = array(
135
      new CoreBundle(),
136
    );
137
138
139
140
141
    if (!isset($this->moduleList)) {
      $storage = new FileStorage(config_get_config_directory());
      $module_list = $storage->read('system.module');
      $this->moduleList = isset($module_list['enabled']) ? $module_list['enabled'] : array();
    }
142

143
144
145
146
147
148
149
150
    $namespaces = $this->classLoader->getNamespaces();
    foreach ($this->moduleList as $module => $weight) {
      // When installing new modules, the modules in the list passed to
      // updateModules() do not yet have their namespace registered.
      $namespace = 'Drupal\\' . $module;
      if (!isset($namespaces[$namespace]) && $this->moduleData($module)) {
        $this->classLoader->registerNamespace($namespace, dirname(DRUPAL_ROOT . '/' . $this->moduleData($module)->uri) . '/lib');
      }
katbailey's avatar
katbailey committed
151
      $camelized = ContainerBuilder::camelize($module);
152
      $class = "Drupal\\{$module}\\{$camelized}Bundle";
153
154
      if (class_exists($class)) {
        $bundles[] = new $class();
155
        $this->bundleClasses[] = $class;
156
      }
157
    }
158
159
    return $bundles;
  }
160

161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
  /**
   * Returns module data on the filesystem.
   *
   * @param $module
   *   The name of the module.
   *
   * @return \stdClass|bool
   *   Returns a stdClass object if the module data is found containing at
   *   least an uri property with the module path, for example
   *   core/modules/user/user.module.
   */
  protected function moduleData($module) {
    if (!$this->moduleData) {
      // Find filenames to prime the classloader. First, find profiles.
      // Profiles might want to add a bundle too and they also can contain
      // modules.
      $profiles_scanner = new SystemListing();
      $all_profiles = $profiles_scanner->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles');
      $profiles = array_keys(array_intersect_key($this->moduleList, $all_profiles));
      $storage = new FileStorage(config_get_config_directory());
      // If a module is within a profile directory but specifies another
      // profile for testing, it needs to be found in the parent profile.
      if (($parent_profile_config = $storage->read('simpletest.settings')) && isset($parent_profile_config['parent_profile']) && $parent_profile_config['parent_profile'] != $profiles[0]) {
        // In case both profile directories contain the same extension, the
        // actual profile always has precedence.
        array_unshift($profiles, $parent_profile_config['parent_profile']);
      }
      // Now find modules.
      $modules_scanner = new SystemListing($profiles);
      $this->moduleData = $all_profiles + $modules_scanner->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules');
    }
    return isset($this->moduleData[$module]) ? $this->moduleData[$module] : FALSE;
  }

195
196
197
  /**
   * Implements Drupal\Core\DrupalKernelInterface::updateModules().
   */
198
199
200
201
202
  public function updateModules(array $module_list, array $module_paths = array()) {
    $this->newModuleList = $module_list;
    foreach ($module_paths as $module => $path) {
      $this->moduleData[$module] = (object) array('uri' => $path);
    }
203
204
205
206
207
208
209
210
211
    // If we haven't yet booted, we don't need to do anything: the new module
    // list will take effect when boot() is called. If we have already booted,
    // then reboot in order to refresh the bundle list and container.
    if ($this->booted) {
      drupal_container(NULL, TRUE);
      $this->booted = FALSE;
      $this->boot();
    }
  }
212

213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
  /**
   * Returns the classname based on environment, debug and testing prefix.
   *
   * @return string
   *   The class name.
   */
  protected function getClassName() {
    $parts = array('service_container', $this->environment, $this->debug);
    // Make sure to use a testing-specific container even in the parent site.
    if (!empty($GLOBALS['drupal_test_info']['test_run_id'])) {
      $parts[] = $GLOBALS['drupal_test_info']['test_run_id'];
    }
    elseif ($prefix = drupal_valid_test_ua()) {
      $parts[] = $prefix;
    }
    return implode('_', $parts);
  }

231
232
233
  /**
   * Initializes the service container.
   */
234
  protected function initializeContainer() {
235
    $this->container = NULL;
236
237
238
239
240
241
242
243
244
245
246
247
    $class = $this->getClassName();
    $cache_file = $class . '.php';

    if ($this->storage) {
      // First, try to load.
      if (!class_exists($class, FALSE)) {
        $this->storage->load($cache_file);
      }
      // If the load succeeded or the class already existed, use it.
      if (class_exists($class, FALSE)) {
        $fully_qualified_class_name = '\\' . $class;
        $this->container = new $fully_qualified_class_name;
248
249
      }
    }
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
    // First check whether the list of modules changed in this request.
    if (isset($this->newModuleList)) {
      if (isset($this->container) && isset($this->moduleList) && array_keys($this->moduleList) !== array_keys($this->newModuleList)) {
        unset($this->container);
      }
      $this->moduleList = $this->newModuleList;
      unset($this->newModuleList);
    }
    // Second, verify that some other request -- for example on another
    // web frontend or during the installer -- changed the list of enabled
    // modules.
    if (isset($this->container)) {
      $module_list = $this->moduleList ?: $this->container->get('config.factory')->get('system.module')->load()->get('enabled');
      if (array_keys((array)$module_list) !== $this->container->getParameter('container.modules')) {
        unset($this->container);
      }
    }

268
269
    if (!isset($this->container)) {
      $this->container = $this->buildContainer();
270
      if ($this->storage && !$this->dumpDrupalContainer($this->container, $this->getContainerBaseClass())) {
271
272
273
274
275
276
        // We want to log this as an error but we cannot call watchdog() until
        // the container has been fully built and set in drupal_container().
        $error = 'Container cannot be written to disk';
      }
    }

277
    $this->container->set('kernel', $this);
278

279
    drupal_container($this->container);
280
281
282
283

    if (isset($error)) {
      watchdog('DrupalKernel', $error);
    }
284
  }
285

286
287
288
289
290
  /**
   * Builds the service container.
   *
   * @return ContainerBuilder The compiled service container
   */
291
  protected function buildContainer() {
292
    $this->initializeBundles();
293
    $container = $this->getContainerBuilder();
294
295
    $container->setParameter('container.bundles', $this->bundleClasses);
    $container->setParameter('container.modules', array_keys($this->moduleList));
katbailey's avatar
katbailey committed
296

297
    // Merge in the minimal bootstrap container.
katbailey's avatar
katbailey committed
298
299
300
    if ($bootstrap_container = drupal_container()) {
      $container->merge($bootstrap_container);
    }
301
302
    foreach ($this->bundles as $bundle) {
      $bundle->build($container);
303
    }
304
    $container->compile();
305
306
    return $container;
  }
307

308
309
310
311
312
  /**
   * Gets a new ContainerBuilder instance used to build the service container.
   *
   * @return ContainerBuilder
   */
313
  protected function getContainerBuilder() {
314
315
316
    return new ContainerBuilder(new ParameterBag($this->getKernelParameters()));
  }

317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
  /**
   * Dumps the service container to PHP code in the config directory.
   *
   * This method is based on the dumpContainer method in the parent class, but
   * that method is reliant on the Config component which we do not use here.
   *
   * @param ContainerBuilder $container
   *   The service container.
   * @param string $baseClass
   *   The name of the container's base class
   *
   * @return bool
   *   TRUE if the container was successfully dumped to disk.
   */
  protected function dumpDrupalContainer(ContainerBuilder $container, $baseClass) {
    if (!$this->storage->writeable()) {
      return FALSE;
    }
    // Cache the container.
    $dumper = new PhpDumper($container);
337
338
    $class = $this->getClassName();
    $content = $dumper->dump(array('class' => $class, 'base_class' => $baseClass));
339
340
341
    return $this->storage->save($class . '.php', $content);
  }

katbailey's avatar
katbailey committed
342
  /**
Crell's avatar
Crell committed
343
344
   * Overrides and eliminates this method from the parent class. Do not use.
   *
katbailey's avatar
katbailey committed
345
346
347
348
349
350
351
   * This method is part of the KernelInterface interface, but takes an object
   * implementing LoaderInterface as its only parameter. This is part of the
   * Config compoment from Symfony, which is not provided by Drupal core.
   *
   * Modules wishing to provide an extension to this class which uses this
   * method are responsible for ensuring the Config component exists.
   */
352
  public function registerContainerConfiguration(LoaderInterface $loader) {
353
  }
354

355
}