Module.php 16.6 KB
Newer Older
w  
Qiang Xue committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14
<?php
/**
 * Module class file.
 *
 * @link http://www.yiiframework.com/
 * @copyright Copyright &copy; 2008-2012 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\base;

/**
 * Module is the base class for module and application classes.
 *
Qiang Xue committed
15
 * Module mainly manages application components and sub-modules that belongs to a module.
w  
Qiang Xue committed
16
 *
Qiang Xue committed
17 18
 * @property string $uniqueId An ID that uniquely identifies this module among all modules within
 * the current application.
Qiang Xue committed
19 20 21 22 23 24
 * @property string $basePath The root directory of the module. Defaults to the directory containing the module class.
 * @property array $modules The configuration of the currently installed modules (module ID => configuration).
 * @property array $components The application components (indexed by their IDs).
 * @property array $import List of aliases to be imported. This property is write-only.
 * @property array $aliases List of aliases to be defined. This property is write-only.
 *
w  
Qiang Xue committed
25 26 27
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
Qiang Xue committed
28
abstract class Module extends Component implements Initable
w  
Qiang Xue committed
29
{
m  
Qiang Xue committed
30 31 32 33
	/**
	 * @var array custom module parameters (name => value).
	 */
	public $params = array();
w  
Qiang Xue committed
34
	/**
Qiang Xue committed
35
	 * @var array the IDs of the application components that should be preloaded when this module is created.
w  
Qiang Xue committed
36 37
	 */
	public $preload = array();
Qiang Xue committed
38 39 40 41 42 43 44 45
	/**
	 * @var string an ID that uniquely identifies this module among other modules which have the same [[parent]].
	 */
	public $id;
	/**
	 * @var Module the parent module of this module. Null if this module does not have a parent.
	 */
	public $module;
Qiang Xue committed
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
	/**
	 * @var array mapping from controller ID to controller configurations.
	 * Each name-value pair specifies the configuration of a single controller.
	 * A controller configuration can be either a string or an array.
	 * If the former, the string should be the class name or path alias of the controller.
	 * If the latter, the array must contain a 'class' element which specifies
	 * the controller's class name or path alias, and the rest of the name-value pairs
	 * in the array are used to initialize the corresponding controller properties. For example,
	 *
	 * ~~~
	 * array(
	 *   'account' => '@application/controllers/UserController',
	 *   'article' => array(
	 *      'class' => '@application/controllers/PostController',
	 *      'pageTitle' => 'something new',
	 *   ),
	 * )
	 * ~~~
	 */
	public $controllers = array();
	/**
	 * @return string the default route of this module. Defaults to 'default'.
	 * The route may consist of child module ID, controller ID, and/or action ID.
	 * For example, `help`, `post/create`, `admin/post/create`.
	 * If action ID is not given, it will take the default value as specified in
	 * [[Controller::defaultAction]].
	 */
	public $defaultRoute = 'default';
	/**
	 * @var string the root directory of the module.
	 * @see getBasePath
	 * @see setBasePath
	 */
	protected $_basePath;
	/**
	 * @var string the directory containing controller classes in the module.
	 * @see getControllerPath
	 * @see setControllerPath
	 */
	protected $_controllerPath;
	/**
	 * @var array child modules of this module
	 */
	protected $_modules = array();
	/**
	 * @var array application components of this module
	 */
	protected $_components = array();
w  
Qiang Xue committed
94 95 96 97

	/**
	 * Constructor.
	 * @param string $id the ID of this module
Qiang Xue committed
98
	 * @param Module $parent the parent module (if any)
w  
Qiang Xue committed
99
	 */
Qiang Xue committed
100
	public function __construct($id, $parent = null)
w  
Qiang Xue committed
101
	{
Qiang Xue committed
102 103
		$this->id = $id;
		$this->module = $parent;
w  
Qiang Xue committed
104 105 106 107 108 109 110 111 112 113 114
	}

	/**
	 * Getter magic method.
	 * This method is overridden to support accessing application components
	 * like reading module properties.
	 * @param string $name application component or property name
	 * @return mixed the named property value
	 */
	public function __get($name)
	{
w  
Qiang Xue committed
115
		if ($this->hasComponent($name)) {
w  
Qiang Xue committed
116
			return $this->getComponent($name);
Qiang Xue committed
117
		} else {
w  
Qiang Xue committed
118
			return parent::__get($name);
w  
Qiang Xue committed
119
		}
w  
Qiang Xue committed
120 121 122 123 124 125 126 127 128 129 130
	}

	/**
	 * Checks if a property value is null.
	 * This method overrides the parent implementation by checking
	 * if the named application component is loaded.
	 * @param string $name the property name or the event name
	 * @return boolean whether the property value is null
	 */
	public function __isset($name)
	{
w  
Qiang Xue committed
131
		if ($this->hasComponent($name)) {
w  
Qiang Xue committed
132
			return $this->getComponent($name) !== null;
Qiang Xue committed
133
		} else {
w  
Qiang Xue committed
134
			return parent::__isset($name);
w  
Qiang Xue committed
135 136 137 138
		}
	}

	/**
Qiang Xue committed
139 140
	 * Initializes the module.
	 * This method is called after the module is created and initialized with property values
.  
Qiang Xue committed
141 142
	 * given in configuration. The default implement will create a path alias using the module [[id]]
	 * and then call [[preloadComponents()]] to load components that are declared in [[preload]].
w  
Qiang Xue committed
143
	 */
Qiang Xue committed
144
	public function init()
w  
Qiang Xue committed
145
	{
Qiang Xue committed
146
		\Yii::setAlias('@' . $this->id, $this->getBasePath());
Qiang Xue committed
147
		$this->preloadComponents();
w  
Qiang Xue committed
148 149 150
	}

	/**
Qiang Xue committed
151 152
	 * Returns an ID that uniquely identifies this module among all modules within the current application.
	 * @return string the unique ID of the module.
w  
Qiang Xue committed
153
	 */
Qiang Xue committed
154
	public function getUniqueId()
w  
Qiang Xue committed
155
	{
Qiang Xue committed
156 157 158 159 160
		if ($this->module && !$this->module instanceof Application) {
			return $this->module->getUniqueId() . "/{$this->id}";
		} else {
			return $this->id;
		}
w  
Qiang Xue committed
161 162 163 164
	}

	/**
	 * Returns the root directory of the module.
Qiang Xue committed
165 166
	 * It defaults to the directory containing the module class file.
	 * @return string the root directory of the module.
w  
Qiang Xue committed
167 168 169
	 */
	public function getBasePath()
	{
m  
Qiang Xue committed
170
		if ($this->_basePath === null) {
Qiang Xue committed
171
			$class = new \ReflectionClass($this);
w  
Qiang Xue committed
172 173 174 175 176 177 178 179
			$this->_basePath = dirname($class->getFileName());
		}
		return $this->_basePath;
	}

	/**
	 * Sets the root directory of the module.
	 * This method can only be invoked at the beginning of the constructor.
Qiang Xue committed
180
	 * @param string $path the root directory of the module. This can be either a directory name or a path alias.
m  
Qiang Xue committed
181
	 * @throws Exception if the directory does not exist.
w  
Qiang Xue committed
182 183 184
	 */
	public function setBasePath($path)
	{
Qiang Xue committed
185 186
		$p = \Yii::getAlias($path);
		if ($p === false || !is_dir($p)) {
m  
Qiang Xue committed
187
			throw new Exception('Invalid base path: ' . $path);
Qiang Xue committed
188
		} else {
Qiang Xue committed
189
			$this->_basePath = realpath($p);
w  
Qiang Xue committed
190 191 192
		}
	}

Qiang Xue committed
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
	/**
	 * Returns the directory that contains the controller classes.
	 * Defaults to "[[basePath]]/controllers".
	 * @return string the directory that contains the controller classes.
	 */
	public function getControllerPath()
	{
		if ($this->_controllerPath !== null) {
			return $this->_controllerPath;
		} else {
			return $this->_controllerPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'controllers';
		}
	}

	/**
	 * Sets the directory that contains the controller classes.
	 * @param string $path the directory that contains the controller classes.
	 * This can be either a directory name or a path alias.
	 * @throws Exception if the directory is invalid
	 */
	public function setControllerPath($path)
	{
		$p = \Yii::getAlias($path);
		if ($p === false || !is_dir($p)) {
			throw new Exception('Invalid controller path: ' . $path);
		} else {
			$this->_controllerPath = realpath($p);
		}
	}

w  
Qiang Xue committed
223
	/**
m  
Qiang Xue committed
224
	 * Imports the specified path aliases.
Qiang Xue committed
225 226
	 * This method is provided so that you can import a set of path aliases when configuring a module.
	 * The path aliases will be imported by calling [[\Yii::import()]].
m  
Qiang Xue committed
227
	 * @param array $aliases list of path aliases to be imported
w  
Qiang Xue committed
228 229 230
	 */
	public function setImport($aliases)
	{
m  
Qiang Xue committed
231 232 233
		foreach ($aliases as $alias) {
			\Yii::import($alias);
		}
w  
Qiang Xue committed
234 235 236
	}

	/**
w  
Qiang Xue committed
237
	 * Defines path aliases.
Qiang Xue committed
238 239
	 * This method calls [[\Yii::setAlias()]] to register the path aliases.
	 * This method is provided so that you can define path aliases when configuring a module.
w  
Qiang Xue committed
240
	 * @param array $aliases list of path aliases to be defined. The array keys are alias names
Qiang Xue committed
241
	 * (must start with '@') and the array values are the corresponding paths or aliases.
w  
Qiang Xue committed
242
	 * For example,
w  
Qiang Xue committed
243 244
	 *
	 * ~~~
w  
Qiang Xue committed
245
	 * array(
Qiang Xue committed
246
	 *	'@models' => '@app/models', // an existing alias
Qiang Xue committed
247
	 *	'@backend' => __DIR__ . '/../backend',  // a directory
w  
Qiang Xue committed
248
	 * )
w  
Qiang Xue committed
249
	 * ~~~
w  
Qiang Xue committed
250
	 */
w  
Qiang Xue committed
251
	public function setAliases($aliases)
w  
Qiang Xue committed
252
	{
w  
Qiang Xue committed
253 254
		foreach ($aliases as $name => $alias) {
			\Yii::setAlias($name, $alias);
w  
Qiang Xue committed
255 256 257 258
		}
	}

	/**
Qiang Xue committed
259 260 261 262
	 * Checks whether the named module exists.
	 * @param string $id module ID
	 * @return boolean whether the named module exists. Both loaded and unloaded modules
	 * are considered.
w  
Qiang Xue committed
263
	 */
Qiang Xue committed
264
	public function hasModule($id)
w  
Qiang Xue committed
265
	{
Qiang Xue committed
266 267 268 269 270 271
		return isset($this->_modules[$id]);
	}

	/**
	 * Retrieves the named module.
	 * @param string $id module ID (case-sensitive)
272
	 * @param boolean $load whether to load the module if it is not yet loaded.
Qiang Xue committed
273 274 275 276
	 * @return Module|null the module instance, null if the module
	 * does not exist.
	 * @see hasModule()
	 */
277
	public function getModule($id, $load = true)
Qiang Xue committed
278 279 280 281
	{
		if (isset($this->_modules[$id])) {
			if ($this->_modules[$id] instanceof Module) {
				return $this->_modules[$id];
282
			} elseif ($load) {
Qiang Xue committed
283 284
				\Yii::trace("Loading \"$id\" module", __CLASS__);
				return $this->_modules[$id] = \Yii::createObject($this->_modules[$id], $id, $this);
w  
Qiang Xue committed
285 286
			}
		}
Qiang Xue committed
287
		return null;
w  
Qiang Xue committed
288 289 290
	}

	/**
Qiang Xue committed
291 292 293 294 295 296 297 298 299
	 * Adds a sub-module to this module.
	 * @param string $id module ID
	 * @param Module|array|null $module the sub-module to be added to this module. This can
	 * be one of the followings:
	 *
	 * - a [[Module]] object
	 * - a configuration array: when [[getModule()]] is called initially, the array
	 *   will be used to instantiate the sub-module
	 * - null: the named sub-module will be removed from this module
w  
Qiang Xue committed
300
	 */
Qiang Xue committed
301
	public function setModule($id, $module)
w  
Qiang Xue committed
302
	{
Qiang Xue committed
303 304 305 306 307
		if ($module === null) {
			unset($this->_modules[$id]);
		} else {
			$this->_modules[$id] = $module;
		}
w  
Qiang Xue committed
308 309 310
	}

	/**
Qiang Xue committed
311 312 313 314 315
	 * Returns the sub-modules in this module.
	 * @param boolean $loadedOnly whether to return the loaded sub-modules only. If this is set false,
	 * then all sub-modules registered in this module will be returned, whether they are loaded or not.
	 * Loaded modules will be returned as objects, while unloaded modules as configuration arrays.
	 * @return array the modules (indexed by their IDs)
w  
Qiang Xue committed
316
	 */
Qiang Xue committed
317
	public function getModules($loadedOnly = false)
w  
Qiang Xue committed
318
	{
Qiang Xue committed
319 320 321 322 323 324 325 326 327 328 329
		if ($loadedOnly) {
			$modules = array();
			foreach ($this->_modules as $module) {
				if ($module instanceof Module) {
					$modules[] = $module;
				}
			}
			return $modules;
		} else {
			return $this->_modules;
		}
w  
Qiang Xue committed
330 331 332
	}

	/**
Qiang Xue committed
333
	 * Registers sub-modules in the current module.
w  
Qiang Xue committed
334
	 *
Qiang Xue committed
335 336 337 338
	 * Each sub-module should be specified as a name-value pair, where
	 * name refers to the ID of the module and value the module or a configuration
	 * array that can be used to create the module. In the latter case, [[\Yii::createObject()]]
	 * will be used to create the module.
w  
Qiang Xue committed
339
	 *
Qiang Xue committed
340
	 * If a new sub-module has the same ID as an existing one, the existing one will be overwritten silently.
w  
Qiang Xue committed
341
	 *
Qiang Xue committed
342
	 * The following is an example for registering two sub-modules:
w  
Qiang Xue committed
343
	 *
Qiang Xue committed
344 345 346 347 348 349 350 351 352 353 354
	 * ~~~
	 * array(
	 *     'comment' => array(
	 *         'class' => 'app\modules\CommentModule',
	 *         'connectionID' => 'db',
	 *     ),
	 *     'booking' => array(
	 *         'class' => 'app\modules\BookingModule',
	 *     ),
	 * )
	 * ~~~
w  
Qiang Xue committed
355
	 *
Qiang Xue committed
356
	 * @param array $modules modules (id => module configuration or instances)
w  
Qiang Xue committed
357 358 359
	 */
	public function setModules($modules)
	{
Qiang Xue committed
360 361
		foreach ($modules as $id => $module) {
			$this->_modules[$id] = $module;
w  
Qiang Xue committed
362 363
		}
	}
364

w  
Qiang Xue committed
365 366 367
	/**
	 * Checks whether the named component exists.
	 * @param string $id application component ID
Qiang Xue committed
368 369
	 * @return boolean whether the named application component exists. Both loaded and unloaded components
	 * are considered.
w  
Qiang Xue committed
370 371 372
	 */
	public function hasComponent($id)
	{
Qiang Xue committed
373
		return isset($this->_components[$id]);
w  
Qiang Xue committed
374 375 376 377 378
	}

	/**
	 * Retrieves the named application component.
	 * @param string $id application component ID (case-sensitive)
379
	 * @param boolean $load whether to load the component if it is not yet loaded.
Qiang Xue committed
380 381 382
	 * @return ApplicationComponent|null the application component instance, null if the application component
	 * does not exist.
	 * @see hasComponent()
w  
Qiang Xue committed
383
	 */
384
	public function getComponent($id, $load = true)
w  
Qiang Xue committed
385
	{
Qiang Xue committed
386
		if (isset($this->_components[$id])) {
Qiang Xue committed
387 388
			if ($this->_components[$id] instanceof ApplicationComponent) {
				return $this->_components[$id];
389
			} elseif ($load) {
Qiang Xue committed
390 391
				\Yii::trace("Loading \"$id\" application component", __CLASS__);
				return $this->_components[$id] = \Yii::createObject($this->_components[$id]);
w  
Qiang Xue committed
392 393
			}
		}
Qiang Xue committed
394
		return null;
w  
Qiang Xue committed
395 396 397
	}

	/**
Qiang Xue committed
398
	 * Registers an application component in this module.
w  
Qiang Xue committed
399
	 * @param string $id component ID
Qiang Xue committed
400 401 402 403 404 405 406
	 * @param ApplicationComponent|array|null $component the component to be added to the module. This can
	 * be one of the followings:
	 *
	 * - an [[ApplicationComponent]] object
	 * - a configuration array: when [[getComponent()]] is called initially for this component, the array
	 *   will be used to instantiate the component
	 * - null: the named component will be removed from the module
w  
Qiang Xue committed
407 408 409
	 */
	public function setComponent($id, $component)
	{
Qiang Xue committed
410
		if ($component === null) {
w  
Qiang Xue committed
411
			unset($this->_components[$id]);
Qiang Xue committed
412
		} else {
w  
Qiang Xue committed
413 414 415 416 417 418 419 420 421 422 423
			$this->_components[$id] = $component;
		}
	}

	/**
	 * Returns the application components.
	 * @param boolean $loadedOnly whether to return the loaded components only. If this is set false,
	 * then all components specified in the configuration will be returned, whether they are loaded or not.
	 * Loaded components will be returned as objects, while unloaded components as configuration arrays.
	 * @return array the application components (indexed by their IDs)
	 */
Qiang Xue committed
424
	public function getComponents($loadedOnly = false)
w  
Qiang Xue committed
425
	{
Qiang Xue committed
426
		if ($loadedOnly) {
Qiang Xue committed
427 428 429 430 431 432 433
			$components = array();
			foreach ($this->_components as $component) {
				if ($component instanceof ApplicationComponent) {
					$components[] = $component;
				}
			}
			return $components;
Qiang Xue committed
434
		} else {
Qiang Xue committed
435
			return $this->_components;
Qiang Xue committed
436
		}
w  
Qiang Xue committed
437 438 439
	}

	/**
Qiang Xue committed
440
	 * Registers a set of application components in this module.
w  
Qiang Xue committed
441
	 *
Qiang Xue committed
442 443 444 445
	 * Each application component should be specified as a name-value pair, where
	 * name refers to the ID of the component and value the component or a configuration
	 * array that can be used to create the component. In the latter case, [[\Yii::createObject()]]
	 * will be used to create the component.
w  
Qiang Xue committed
446
	 *
Qiang Xue committed
447
	 * If a new component has the same ID as an existing one, the existing one will be overwritten silently.
w  
Qiang Xue committed
448
	 *
Qiang Xue committed
449 450 451
	 * The following is an example for setting two components:
	 *
	 * ~~~
w  
Qiang Xue committed
452
	 * array(
Qiang Xue committed
453 454 455 456 457 458 459 460
	 *     'db' => array(
	 *         'class' => 'yii\db\dao\Connection',
	 *         'dsn' => 'sqlite:path/to/file.db',
	 *     ),
	 *     'cache' => array(
	 *         'class' => 'yii\caching\DbCache',
	 *         'connectionID' => 'db',
	 *     ),
w  
Qiang Xue committed
461
	 * )
Qiang Xue committed
462
	 * ~~~
w  
Qiang Xue committed
463
	 *
Qiang Xue committed
464
	 * @param array $components application components (id => component configuration or instance)
w  
Qiang Xue committed
465
	 */
Qiang Xue committed
466
	public function setComponents($components)
w  
Qiang Xue committed
467
	{
Qiang Xue committed
468 469
		foreach ($components as $id => $component) {
			$this->_components[$id] = $component;
w  
Qiang Xue committed
470 471 472 473
		}
	}

	/**
Qiang Xue committed
474
	 * Loads application components that are declared in [[preload]].
w  
Qiang Xue committed
475
	 */
w  
Qiang Xue committed
476
	public function preloadComponents()
w  
Qiang Xue committed
477
	{
Qiang Xue committed
478
		foreach ($this->preload as $id) {
w  
Qiang Xue committed
479
			$this->getComponent($id);
Qiang Xue committed
480
		}
w  
Qiang Xue committed
481
	}
Qiang Xue committed
482 483

	/**
Qiang Xue committed
484 485
	 * Creates a controller instance based on the given route.
	 * This method tries to parse the given route (e.g. `post/create`) using the following algorithm:
Qiang Xue committed
486 487 488 489 490
	 *
	 * 1. Get the first segment in route
	 * 2. If the segment matches
	 *    - an ID in [[controllers]], create a controller instance using the corresponding configuration,
	 *      and return the controller with the rest part of the route;
Qiang Xue committed
491
	 *    - an ID in [[modules]], call the [[createController()]] method of the corresponding module.
Qiang Xue committed
492 493 494 495
	 *    - a controller class under [[controllerPath]], create the controller instance, and return it
	 *      with the rest part of the route;
	 *
	 * @param string $route the route which may consist module ID, controller ID and/or action ID (e.g. `post/create`)
Qiang Xue committed
496
	 * @return array|boolean the array of controller instance and action ID. False if the route cannot be resolved.
Qiang Xue committed
497
	 */
Qiang Xue committed
498
	public function createController($route)
Qiang Xue committed
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
	{
		if (($route = trim($route, '/')) === '') {
			$route = $this->defaultRoute;
		}

		if (($pos = strpos($route, '/')) !== false) {
			$id = substr($route, 0, $pos);
			$route = (string)substr($route, $pos + 1);
		} else {
			$id = $route;
			$route = '';
		}

		// Controller IDs must start with a lower-case letter and consist of word characters only
		if (!preg_match('/^[a-z][a-zA-Z0-9_]*$/', $id)) {
Qiang Xue committed
514
			return false;
Qiang Xue committed
515 516 517
		}

		if (isset($this->controllers[$id])) {
Qiang Xue committed
518 519 520 521
			return array(
				\Yii::createObject($this->controllers[$id], $id, $this),
				$route,
			);
Qiang Xue committed
522 523
		}

Qiang Xue committed
524 525 526 527 528 529 530
		if (($module = $this->getModule($id)) !== null) {
			$result = $module->createController($route);
			if ($result !== false) {
				return $result;
			}
		}

Qiang Xue committed
531 532 533 534 535 536 537
		$className = ucfirst($id) . 'Controller';
		$classFile = $this->getControllerPath() . DIRECTORY_SEPARATOR . $className . '.php';
		if (is_file($classFile)) {
			if (!class_exists($className, false)) {
				require($classFile);
			}
			if (class_exists($className, false) && is_subclass_of($className, '\yii\base\Controller')) {
Qiang Xue committed
538 539 540 541
				return array(
					$className::newInstance(array(), $id, $this),
					$route,
				);
Qiang Xue committed
542 543 544
			}
		}

Qiang Xue committed
545
		return false;
Qiang Xue committed
546
	}
w  
Qiang Xue committed
547
}