CModule.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  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. * @param boolean $merge whether to merge the new module configuration
  311. * with the existing one. Defaults to true, meaning the previously registered
  312. * module configuration with the same ID will be merged with the new configuration.
  313. * If set to false, the existing configuration will be replaced completely.
  314. * This parameter is available since 1.1.16.
  315. */
  316. public function setModules($modules,$merge=true)
  317. {
  318. foreach($modules as $id=>$module)
  319. {
  320. if(is_int($id))
  321. {
  322. $id=$module;
  323. $module=array();
  324. }
  325. if(isset($this->_moduleConfig[$id]) && $merge)
  326. $this->_moduleConfig[$id]=CMap::mergeArray($this->_moduleConfig[$id],$module);
  327. else
  328. {
  329. if(!isset($module['class']))
  330. {
  331. if (Yii::getPathOfAlias($id)===false)
  332. Yii::setPathOfAlias($id,$this->getModulePath().DIRECTORY_SEPARATOR.$id);
  333. $module['class']=$id.'.'.ucfirst($id).'Module';
  334. }
  335. $this->_moduleConfig[$id]=$module;
  336. }
  337. }
  338. }
  339. /**
  340. * Checks whether the named component exists.
  341. * @param string $id application component ID
  342. * @return boolean whether the named application component exists (including both loaded and disabled.)
  343. */
  344. public function hasComponent($id)
  345. {
  346. return isset($this->_components[$id]) || isset($this->_componentConfig[$id]);
  347. }
  348. /**
  349. * Retrieves the named application component.
  350. * @param string $id application component ID (case-sensitive)
  351. * @param boolean $createIfNull whether to create the component if it doesn't exist yet.
  352. * @return IApplicationComponent the application component instance, null if the application component is disabled or does not exist.
  353. * @see hasComponent
  354. */
  355. public function getComponent($id,$createIfNull=true)
  356. {
  357. if(isset($this->_components[$id]))
  358. return $this->_components[$id];
  359. elseif(isset($this->_componentConfig[$id]) && $createIfNull)
  360. {
  361. $config=$this->_componentConfig[$id];
  362. if(!isset($config['enabled']) || $config['enabled'])
  363. {
  364. Yii::trace("Loading \"$id\" application component",'system.CModule');
  365. unset($config['enabled']);
  366. $component=Yii::createComponent($config);
  367. $component->init();
  368. return $this->_components[$id]=$component;
  369. }
  370. }
  371. }
  372. /**
  373. * Puts a component under the management of the module.
  374. * The component will be initialized by calling its {@link CApplicationComponent::init() init()}
  375. * method if it has not done so.
  376. * @param string $id component ID
  377. * @param array|IApplicationComponent $component application component
  378. * (either configuration array or instance). If this parameter is null,
  379. * component will be unloaded from the module.
  380. * @param boolean $merge whether to merge the new component configuration
  381. * with the existing one. Defaults to true, meaning the previously registered
  382. * component configuration with the same ID will be merged with the new configuration.
  383. * If set to false, the existing configuration will be replaced completely.
  384. * This parameter is available since 1.1.13.
  385. */
  386. public function setComponent($id,$component,$merge=true)
  387. {
  388. if($component===null)
  389. {
  390. unset($this->_components[$id]);
  391. return;
  392. }
  393. elseif($component instanceof IApplicationComponent)
  394. {
  395. $this->_components[$id]=$component;
  396. if(!$component->getIsInitialized())
  397. $component->init();
  398. return;
  399. }
  400. elseif(isset($this->_components[$id]))
  401. {
  402. if(isset($component['class']) && get_class($this->_components[$id])!==$component['class'])
  403. {
  404. unset($this->_components[$id]);
  405. $this->_componentConfig[$id]=$component; //we should ignore merge here
  406. return;
  407. }
  408. foreach($component as $key=>$value)
  409. {
  410. if($key!=='class')
  411. $this->_components[$id]->$key=$value;
  412. }
  413. }
  414. elseif(isset($this->_componentConfig[$id]['class'],$component['class'])
  415. && $this->_componentConfig[$id]['class']!==$component['class'])
  416. {
  417. $this->_componentConfig[$id]=$component; //we should ignore merge here
  418. return;
  419. }
  420. if(isset($this->_componentConfig[$id]) && $merge)
  421. $this->_componentConfig[$id]=CMap::mergeArray($this->_componentConfig[$id],$component);
  422. else
  423. $this->_componentConfig[$id]=$component;
  424. }
  425. /**
  426. * Returns the application components.
  427. * @param boolean $loadedOnly whether to return the loaded components only. If this is set false,
  428. * then all components specified in the configuration will be returned, whether they are loaded or not.
  429. * Loaded components will be returned as objects, while unloaded components as configuration arrays.
  430. * This parameter has been available since version 1.1.3.
  431. * @return array the application components (indexed by their IDs)
  432. */
  433. public function getComponents($loadedOnly=true)
  434. {
  435. if($loadedOnly)
  436. return $this->_components;
  437. else
  438. return array_merge($this->_componentConfig, $this->_components);
  439. }
  440. /**
  441. * Sets the application components.
  442. *
  443. * When a configuration is used to specify a component, it should consist of
  444. * the component's initial property values (name-value pairs). Additionally,
  445. * a component can be enabled (default) or disabled by specifying the 'enabled' value
  446. * in the configuration.
  447. *
  448. * If a configuration is specified with an ID that is the same as an existing
  449. * component or configuration, the existing one will be replaced silently.
  450. *
  451. * The following is the configuration for two components:
  452. * <pre>
  453. * array(
  454. * 'db'=>array(
  455. * 'class'=>'CDbConnection',
  456. * 'connectionString'=>'sqlite:path/to/file.db',
  457. * ),
  458. * 'cache'=>array(
  459. * 'class'=>'CDbCache',
  460. * 'connectionID'=>'db',
  461. * 'enabled'=>!YII_DEBUG, // enable caching in non-debug mode
  462. * ),
  463. * )
  464. * </pre>
  465. *
  466. * @param array $components application components(id=>component configuration or instances)
  467. * @param boolean $merge whether to merge the new component configuration with the existing one.
  468. * Defaults to true, meaning the previously registered component configuration of the same ID
  469. * will be merged with the new configuration. If false, the existing configuration will be replaced completely.
  470. */
  471. public function setComponents($components,$merge=true)
  472. {
  473. foreach($components as $id=>$component)
  474. $this->setComponent($id,$component,$merge);
  475. }
  476. /**
  477. * Configures the module with the specified configuration.
  478. * @param array $config the configuration array
  479. */
  480. public function configure($config)
  481. {
  482. if(is_array($config))
  483. {
  484. foreach($config as $key=>$value)
  485. $this->$key=$value;
  486. }
  487. }
  488. /**
  489. * Loads static application components.
  490. */
  491. protected function preloadComponents()
  492. {
  493. foreach($this->preload as $id)
  494. $this->getComponent($id);
  495. }
  496. /**
  497. * Preinitializes the module.
  498. * This method is called at the beginning of the module constructor.
  499. * You may override this method to do some customized preinitialization work.
  500. * Note that at this moment, the module is not configured yet.
  501. * @see init
  502. */
  503. protected function preinit()
  504. {
  505. }
  506. /**
  507. * Initializes the module.
  508. * This method is called at the end of the module constructor.
  509. * Note that at this moment, the module has been configured, the behaviors
  510. * have been attached and the application components have been registered.
  511. * @see preinit
  512. */
  513. protected function init()
  514. {
  515. }
  516. }