CModule.php 17 KB

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