1: <?php
2: /**
3: * CModule class file.
4: *
5: * @author Qiang Xue <qiang.xue@gmail.com>
6: * @link http://www.yiiframework.com/
7: * @copyright 2008-2013 Yii Software LLC
8: * @license http://www.yiiframework.com/license/
9: */
10:
11: /**
12: * CModule is the base class for module and application classes.
13: *
14: * CModule mainly manages application components and sub-modules.
15: *
16: * @property string $id The module ID.
17: * @property string $basePath The root directory of the module. Defaults to the directory containing the module class.
18: * @property CAttributeCollection $params The list of user-defined parameters.
19: * @property string $modulePath The directory that contains the application modules. Defaults to the 'modules' subdirectory of {@link basePath}.
20: * @property CModule $parentModule The parent module. Null if this module does not have a parent.
21: * @property array $modules The configuration of the currently installed modules (module ID => configuration).
22: * @property array $components The application components (indexed by their IDs).
23: * @property array $import List of aliases to be imported.
24: * @property array $aliases List of aliases to be defined. The array keys are root aliases,
25: * while the array values are paths or aliases corresponding to the root aliases.
26: * For example,
27: * <pre>
28: * array(
29: * 'models'=>'application.models', // an existing alias
30: * 'extensions'=>'application.extensions', // an existing alias
31: * 'backend'=>dirname(__FILE__).'/../backend', // a directory
32: * )
33: * </pre>.
34: *
35: * @author Qiang Xue <qiang.xue@gmail.com>
36: * @package system.base
37: */
38: abstract class CModule extends CComponent
39: {
40: /**
41: * @var array the IDs of the application components that should be preloaded.
42: */
43: public $preload=array();
44: /**
45: * @var array the behaviors that should be attached to the module.
46: * The behaviors will be attached to the module when {@link init} is called.
47: * Please refer to {@link CModel::behaviors} on how to specify the value of this property.
48: */
49: public $behaviors=array();
50:
51: private $_id;
52: private $_parentModule;
53: private $_basePath;
54: private $_modulePath;
55: private $_params;
56: private $_modules=array();
57: private $_moduleConfig=array();
58: private $_components=array();
59: private $_componentConfig=array();
60:
61:
62: /**
63: * Constructor.
64: * @param string $id the ID of this module
65: * @param CModule $parent the parent module (if any)
66: * @param mixed $config the module configuration. It can be either an array or
67: * the path of a PHP file returning the configuration array.
68: */
69: public function __construct($id,$parent,$config=null)
70: {
71: $this->_id=$id;
72: $this->_parentModule=$parent;
73:
74: // set basePath at early as possible to avoid trouble
75: if(is_string($config))
76: $config=require($config);
77: if(isset($config['basePath']))
78: {
79: $this->setBasePath($config['basePath']);
80: unset($config['basePath']);
81: }
82: Yii::setPathOfAlias($id,$this->getBasePath());
83:
84: $this->preinit();
85:
86: $this->configure($config);
87: $this->attachBehaviors($this->behaviors);
88: $this->preloadComponents();
89:
90: $this->init();
91: }
92:
93: /**
94: * Getter magic method.
95: * This method is overridden to support accessing application components
96: * like reading module properties.
97: * @param string $name application component or property name
98: * @return mixed the named property value
99: */
100: public function __get($name)
101: {
102: if($this->hasComponent($name))
103: return $this->getComponent($name);
104: else
105: return parent::__get($name);
106: }
107:
108: /**
109: * Checks if a property value is null.
110: * This method overrides the parent implementation by checking
111: * if the named application component is loaded.
112: * @param string $name the property name or the event name
113: * @return boolean whether the property value is null
114: */
115: public function __isset($name)
116: {
117: if($this->hasComponent($name))
118: return $this->getComponent($name)!==null;
119: else
120: return parent::__isset($name);
121: }
122:
123: /**
124: * Returns the module ID.
125: * @return string the module ID.
126: */
127: public function getId()
128: {
129: return $this->_id;
130: }
131:
132: /**
133: * Sets the module ID.
134: * @param string $id the module ID
135: */
136: public function setId($id)
137: {
138: $this->_id=$id;
139: }
140:
141: /**
142: * Returns the root directory of the module.
143: * @return string the root directory of the module. Defaults to the directory containing the module class.
144: */
145: public function getBasePath()
146: {
147: if($this->_basePath===null)
148: {
149: $class=new ReflectionClass(get_class($this));
150: $this->_basePath=dirname($class->getFileName());
151: }
152: return $this->_basePath;
153: }
154:
155: /**
156: * Sets the root directory of the module.
157: * This method can only be invoked at the beginning of the constructor.
158: * @param string $path the root directory of the module.
159: * @throws CException if the directory does not exist.
160: */
161: public function setBasePath($path)
162: {
163: if(($this->_basePath=realpath($path))===false || !is_dir($this->_basePath))
164: throw new CException(Yii::t('yii','Base path "{path}" is not a valid directory.',
165: array('{path}'=>$path)));
166: }
167:
168: /**
169: * Returns user-defined parameters.
170: * @return CAttributeCollection the list of user-defined parameters
171: */
172: public function getParams()
173: {
174: if($this->_params!==null)
175: return $this->_params;
176: else
177: {
178: $this->_params=new CAttributeCollection;
179: $this->_params->caseSensitive=true;
180: return $this->_params;
181: }
182: }
183:
184: /**
185: * Sets user-defined parameters.
186: * @param array $value user-defined parameters. This should be in name-value pairs.
187: */
188: public function setParams($value)
189: {
190: $params=$this->getParams();
191: foreach($value as $k=>$v)
192: $params->add($k,$v);
193: }
194:
195: /**
196: * Returns the directory that contains the application modules.
197: * @return string the directory that contains the application modules. Defaults to the 'modules' subdirectory of {@link basePath}.
198: */
199: public function getModulePath()
200: {
201: if($this->_modulePath!==null)
202: return $this->_modulePath;
203: else
204: return $this->_modulePath=$this->getBasePath().DIRECTORY_SEPARATOR.'modules';
205: }
206:
207: /**
208: * Sets the directory that contains the application modules.
209: * @param string $value the directory that contains the application modules.
210: * @throws CException if the directory is invalid
211: */
212: public function setModulePath($value)
213: {
214: if(($this->_modulePath=realpath($value))===false || !is_dir($this->_modulePath))
215: throw new CException(Yii::t('yii','The module path "{path}" is not a valid directory.',
216: array('{path}'=>$value)));
217: }
218:
219: /**
220: * Sets the aliases that are used in the module.
221: * @param array $aliases list of aliases to be imported
222: */
223: public function setImport($aliases)
224: {
225: foreach($aliases as $alias)
226: Yii::import($alias);
227: }
228:
229: /**
230: * Defines the root aliases.
231: * @param array $mappings list of aliases to be defined. The array keys are root aliases,
232: * while the array values are paths or aliases corresponding to the root aliases.
233: * For example,
234: * <pre>
235: * array(
236: * 'models'=>'application.models', // an existing alias
237: * 'extensions'=>'application.extensions', // an existing alias
238: * 'backend'=>dirname(__FILE__).'/../backend', // a directory
239: * )
240: * </pre>
241: */
242: public function setAliases($mappings)
243: {
244: foreach($mappings as $name=>$alias)
245: {
246: if(($path=Yii::getPathOfAlias($alias))!==false)
247: Yii::setPathOfAlias($name,$path);
248: else
249: Yii::setPathOfAlias($name,$alias);
250: }
251: }
252:
253: /**
254: * Returns the parent module.
255: * @return CModule the parent module. Null if this module does not have a parent.
256: */
257: public function getParentModule()
258: {
259: return $this->_parentModule;
260: }
261:
262: /**
263: * Retrieves the named application module.
264: * The module has to be declared in {@link modules}. A new instance will be created
265: * when calling this method with the given ID for the first time.
266: * @param string $id application module ID (case-sensitive)
267: * @return CModule the module instance, null if the module is disabled or does not exist.
268: */
269: public function getModule($id)
270: {
271: if(isset($this->_modules[$id]) || array_key_exists($id,$this->_modules))
272: return $this->_modules[$id];
273: elseif(isset($this->_moduleConfig[$id]))
274: {
275: $config=$this->_moduleConfig[$id];
276: if(!isset($config['enabled']) || $config['enabled'])
277: {
278: Yii::trace("Loading \"$id\" module",'system.base.CModule');
279: $class=$config['class'];
280: unset($config['class'], $config['enabled']);
281: if($this===Yii::app())
282: $module=Yii::createComponent($class,$id,null,$config);
283: else
284: $module=Yii::createComponent($class,$this->getId().'/'.$id,$this,$config);
285: return $this->_modules[$id]=$module;
286: }
287: }
288: }
289:
290: /**
291: * Returns a value indicating whether the specified module is installed.
292: * @param string $id the module ID
293: * @return boolean whether the specified module is installed.
294: * @since 1.1.2
295: */
296: public function hasModule($id)
297: {
298: return isset($this->_moduleConfig[$id]) || isset($this->_modules[$id]);
299: }
300:
301: /**
302: * Returns the configuration of the currently installed modules.
303: * @return array the configuration of the currently installed modules (module ID => configuration)
304: */
305: public function getModules()
306: {
307: return $this->_moduleConfig;
308: }
309:
310: /**
311: * Configures the sub-modules of this module.
312: *
313: * Call this method to declare sub-modules and configure them with their initial property values.
314: * The parameter should be an array of module configurations. Each array element represents a single module,
315: * which can be either a string representing the module ID or an ID-configuration pair representing
316: * a module with the specified ID and the initial property values.
317: *
318: * For example, the following array declares two modules:
319: * <pre>
320: * array(
321: * 'admin', // a single module ID
322: * 'payment'=>array( // ID-configuration pair
323: * 'server'=>'paymentserver.com',
324: * ),
325: * )
326: * </pre>
327: *
328: * By default, the module class is determined using the expression <code>ucfirst($moduleID).'Module'</code>.
329: * And the class file is located under <code>modules/$moduleID</code>.
330: * You may override this default by explicitly specifying the 'class' option in the configuration.
331: *
332: * You may also enable or disable a module by specifying the 'enabled' option in the configuration.
333: *
334: * @param array $modules module configurations.
335: * @param boolean $merge whether to merge the new module configuration
336: * with the existing one. Defaults to true, meaning the previously registered
337: * module configuration with the same ID will be merged with the new configuration.
338: * If set to false, the existing configuration will be replaced completely.
339: * This parameter is available since 1.1.16.
340: */
341: public function setModules($modules,$merge=true)
342: {
343: foreach($modules as $id=>$module)
344: {
345: if(is_int($id))
346: {
347: $id=$module;
348: $module=array();
349: }
350: if(isset($this->_moduleConfig[$id]) && $merge)
351: $this->_moduleConfig[$id]=CMap::mergeArray($this->_moduleConfig[$id],$module);
352: else
353: {
354: if(!isset($module['class']))
355: {
356: if (Yii::getPathOfAlias($id)===false)
357: Yii::setPathOfAlias($id,$this->getModulePath().DIRECTORY_SEPARATOR.$id);
358: $module['class']=$id.'.'.ucfirst($id).'Module';
359: }
360: $this->_moduleConfig[$id]=$module;
361: }
362: }
363: }
364:
365: /**
366: * Checks whether the named component exists.
367: * @param string $id application component ID
368: * @return boolean whether the named application component exists (including both loaded and disabled.)
369: */
370: public function hasComponent($id)
371: {
372: return isset($this->_components[$id]) || isset($this->_componentConfig[$id]);
373: }
374:
375: /**
376: * Retrieves the named application component.
377: * @param string $id application component ID (case-sensitive)
378: * @param boolean $createIfNull whether to create the component if it doesn't exist yet.
379: * @return IApplicationComponent the application component instance, null if the application component is disabled or does not exist.
380: * @see hasComponent
381: */
382: public function getComponent($id,$createIfNull=true)
383: {
384: if(isset($this->_components[$id]))
385: return $this->_components[$id];
386: elseif(isset($this->_componentConfig[$id]) && $createIfNull)
387: {
388: $config=$this->_componentConfig[$id];
389: if(!isset($config['enabled']) || $config['enabled'])
390: {
391: Yii::trace("Loading \"$id\" application component",'system.CModule');
392: unset($config['enabled']);
393: $component=Yii::createComponent($config);
394: $component->init();
395: return $this->_components[$id]=$component;
396: }
397: }
398: }
399:
400: /**
401: * Puts a component under the management of the module.
402: * The component will be initialized by calling its {@link CApplicationComponent::init() init()}
403: * method if it has not done so.
404: * @param string $id component ID
405: * @param array|IApplicationComponent $component application component
406: * (either configuration array or instance). If this parameter is null,
407: * component will be unloaded from the module.
408: * @param boolean $merge whether to merge the new component configuration
409: * with the existing one. Defaults to true, meaning the previously registered
410: * component configuration with the same ID will be merged with the new configuration.
411: * If set to false, the existing configuration will be replaced completely.
412: * This parameter is available since 1.1.13.
413: */
414: public function setComponent($id,$component,$merge=true)
415: {
416: if($component===null)
417: {
418: unset($this->_components[$id]);
419: return;
420: }
421: elseif($component instanceof IApplicationComponent)
422: {
423: $this->_components[$id]=$component;
424:
425: if(!$component->getIsInitialized())
426: $component->init();
427:
428: return;
429: }
430: elseif(isset($this->_components[$id]))
431: {
432: if(isset($component['class']) && get_class($this->_components[$id])!==$component['class'])
433: {
434: unset($this->_components[$id]);
435: $this->_componentConfig[$id]=$component; //we should ignore merge here
436: return;
437: }
438:
439: foreach($component as $key=>$value)
440: {
441: if($key!=='class')
442: $this->_components[$id]->$key=$value;
443: }
444: }
445: elseif(isset($this->_componentConfig[$id]['class'],$component['class'])
446: && $this->_componentConfig[$id]['class']!==$component['class'])
447: {
448: $this->_componentConfig[$id]=$component; //we should ignore merge here
449: return;
450: }
451:
452: if(isset($this->_componentConfig[$id]) && $merge)
453: $this->_componentConfig[$id]=CMap::mergeArray($this->_componentConfig[$id],$component);
454: else
455: $this->_componentConfig[$id]=$component;
456: }
457:
458: /**
459: * Returns the application components.
460: * @param boolean $loadedOnly whether to return the loaded components only. If this is set false,
461: * then all components specified in the configuration will be returned, whether they are loaded or not.
462: * Loaded components will be returned as objects, while unloaded components as configuration arrays.
463: * This parameter has been available since version 1.1.3.
464: * @return array the application components (indexed by their IDs)
465: */
466: public function getComponents($loadedOnly=true)
467: {
468: if($loadedOnly)
469: return $this->_components;
470: else
471: return array_merge($this->_componentConfig, $this->_components);
472: }
473:
474: /**
475: * Sets the application components.
476: *
477: * When a configuration is used to specify a component, it should consist of
478: * the component's initial property values (name-value pairs). Additionally,
479: * a component can be enabled (default) or disabled by specifying the 'enabled' value
480: * in the configuration.
481: *
482: * If a configuration is specified with an ID that is the same as an existing
483: * component or configuration, the existing one will be replaced silently.
484: *
485: * The following is the configuration for two components:
486: * <pre>
487: * array(
488: * 'db'=>array(
489: * 'class'=>'CDbConnection',
490: * 'connectionString'=>'sqlite:path/to/file.db',
491: * ),
492: * 'cache'=>array(
493: * 'class'=>'CDbCache',
494: * 'connectionID'=>'db',
495: * 'enabled'=>!YII_DEBUG, // enable caching in non-debug mode
496: * ),
497: * )
498: * </pre>
499: *
500: * @param array $components application components(id=>component configuration or instances)
501: * @param boolean $merge whether to merge the new component configuration with the existing one.
502: * Defaults to true, meaning the previously registered component configuration of the same ID
503: * will be merged with the new configuration. If false, the existing configuration will be replaced completely.
504: */
505: public function setComponents($components,$merge=true)
506: {
507: foreach($components as $id=>$component)
508: $this->setComponent($id,$component,$merge);
509: }
510:
511: /**
512: * Configures the module with the specified configuration.
513: * @param array $config the configuration array
514: */
515: public function configure($config)
516: {
517: if(is_array($config))
518: {
519: foreach($config as $key=>$value)
520: $this->$key=$value;
521: }
522: }
523:
524: /**
525: * Loads static application components.
526: */
527: protected function preloadComponents()
528: {
529: foreach($this->preload as $id)
530: $this->getComponent($id);
531: }
532:
533: /**
534: * Preinitializes the module.
535: * This method is called at the beginning of the module constructor.
536: * You may override this method to do some customized preinitialization work.
537: * Note that at this moment, the module is not configured yet.
538: * @see init
539: */
540: protected function preinit()
541: {
542: }
543:
544: /**
545: * Initializes the module.
546: * This method is called at the end of the module constructor.
547: * Note that at this moment, the module has been configured, the behaviors
548: * have been attached and the application components have been registered.
549: * @see preinit
550: */
551: protected function init()
552: {
553: }
554: }
555: