1 | <?php |
||
2 | /** |
||
3 | * @link http://www.yiiframework.com/ |
||
4 | * @copyright Copyright (c) 2008 Yii Software LLC |
||
5 | * @license http://www.yiiframework.com/license/ |
||
6 | */ |
||
7 | |||
8 | namespace yii\base; |
||
9 | |||
10 | use Yii; |
||
11 | use yii\di\ServiceLocator; |
||
12 | |||
13 | /** |
||
14 | * Module is the base class for module and application classes. |
||
15 | * |
||
16 | * A module represents a sub-application which contains MVC elements by itself, such as |
||
17 | * models, views, controllers, etc. |
||
18 | * |
||
19 | * A module may consist of [[modules|sub-modules]]. |
||
20 | * |
||
21 | * [[components|Components]] may be registered with the module so that they are globally |
||
22 | * accessible within the module. |
||
23 | * |
||
24 | * For more details and usage information on Module, see the [guide article on modules](guide:structure-modules). |
||
25 | * |
||
26 | * @property-write array $aliases List of path aliases to be defined. The array keys are alias names (must |
||
27 | * start with `@`) and the array values are the corresponding paths or aliases. See [[setAliases()]] for an |
||
28 | * example. This property is write-only. |
||
29 | * @property string $basePath The root directory of the module. |
||
30 | * @property-read string $controllerPath The directory that contains the controller classes. This property is |
||
31 | * read-only. |
||
32 | * @property string $layoutPath The root directory of layout files. Defaults to "[[viewPath]]/layouts". |
||
33 | * @property array $modules The modules (indexed by their IDs). |
||
34 | * @property-read string $uniqueId The unique ID of the module. This property is read-only. |
||
35 | * @property string $version The version of this module. Note that the type of this property differs in getter |
||
36 | * and setter. See [[getVersion()]] and [[setVersion()]] for details. |
||
37 | * @property string $viewPath The root directory of view files. Defaults to "[[basePath]]/views". |
||
38 | * |
||
39 | * @author Qiang Xue <[email protected]> |
||
40 | * @since 2.0 |
||
41 | */ |
||
42 | class Module extends ServiceLocator |
||
43 | { |
||
44 | /** |
||
45 | * @event ActionEvent an event raised before executing a controller action. |
||
46 | * You may set [[ActionEvent::isValid]] to be `false` to cancel the action execution. |
||
47 | */ |
||
48 | const EVENT_BEFORE_ACTION = 'beforeAction'; |
||
49 | /** |
||
50 | * @event ActionEvent an event raised after executing a controller action. |
||
51 | */ |
||
52 | const EVENT_AFTER_ACTION = 'afterAction'; |
||
53 | |||
54 | /** |
||
55 | * @var array custom module parameters (name => value). |
||
56 | */ |
||
57 | public $params = []; |
||
58 | /** |
||
59 | * @var string an ID that uniquely identifies this module among other modules which have the same [[module|parent]]. |
||
60 | */ |
||
61 | public $id; |
||
62 | /** |
||
63 | * @var Module|null the parent module of this module. `null` if this module does not have a parent. |
||
64 | */ |
||
65 | public $module; |
||
66 | /** |
||
67 | * @var string|bool|null the layout that should be applied for views within this module. This refers to a view name |
||
68 | * relative to [[layoutPath]]. If this is not set, it means the layout value of the [[module|parent module]] |
||
69 | * will be taken. If this is `false`, layout will be disabled within this module. |
||
70 | */ |
||
71 | public $layout; |
||
72 | /** |
||
73 | * @var array mapping from controller ID to controller configurations. |
||
74 | * Each name-value pair specifies the configuration of a single controller. |
||
75 | * A controller configuration can be either a string or an array. |
||
76 | * If the former, the string should be the fully qualified class name of the controller. |
||
77 | * If the latter, the array must contain a `class` element which specifies |
||
78 | * the controller's fully qualified class name, and the rest of the name-value pairs |
||
79 | * in the array are used to initialize the corresponding controller properties. For example, |
||
80 | * |
||
81 | * ```php |
||
82 | * [ |
||
83 | * 'account' => 'app\controllers\UserController', |
||
84 | * 'article' => [ |
||
85 | * 'class' => 'app\controllers\PostController', |
||
86 | * 'pageTitle' => 'something new', |
||
87 | * ], |
||
88 | * ] |
||
89 | * ``` |
||
90 | */ |
||
91 | public $controllerMap = []; |
||
92 | /** |
||
93 | * @var string|null the namespace that controller classes are in. |
||
94 | * This namespace will be used to load controller classes by prepending it to the controller |
||
95 | * class name. |
||
96 | * |
||
97 | * If not set, it will use the `controllers` sub-namespace under the namespace of this module. |
||
98 | * For example, if the namespace of this module is `foo\bar`, then the default |
||
99 | * controller namespace would be `foo\bar\controllers`. |
||
100 | * |
||
101 | * See also the [guide section on autoloading](guide:concept-autoloading) to learn more about |
||
102 | * defining namespaces and how classes are loaded. |
||
103 | */ |
||
104 | public $controllerNamespace; |
||
105 | /** |
||
106 | * @var string the default route of this module. Defaults to `default`. |
||
107 | * The route may consist of child module ID, controller ID, and/or action ID. |
||
108 | * For example, `help`, `post/create`, `admin/post/create`. |
||
109 | * If action ID is not given, it will take the default value as specified in |
||
110 | * [[Controller::defaultAction]]. |
||
111 | */ |
||
112 | public $defaultRoute = 'default'; |
||
113 | |||
114 | /** |
||
115 | * @var string the root directory of the module. |
||
116 | */ |
||
117 | private $_basePath; |
||
118 | /** |
||
119 | * @var string the root directory that contains view files for this module |
||
120 | */ |
||
121 | private $_viewPath; |
||
122 | /** |
||
123 | * @var string the root directory that contains layout view files for this module. |
||
124 | */ |
||
125 | private $_layoutPath; |
||
126 | /** |
||
127 | * @var array child modules of this module |
||
128 | */ |
||
129 | private $_modules = []; |
||
130 | /** |
||
131 | * @var string|callable the version of this module. |
||
132 | * Version can be specified as a PHP callback, which can accept module instance as an argument and should |
||
133 | * return the actual version. For example: |
||
134 | * |
||
135 | * ```php |
||
136 | * function (Module $module) { |
||
137 | * //return string|int |
||
138 | * } |
||
139 | * ``` |
||
140 | * |
||
141 | * If not set, [[defaultVersion()]] will be used to determine actual value. |
||
142 | * |
||
143 | * @since 2.0.11 |
||
144 | */ |
||
145 | private $_version; |
||
146 | |||
147 | |||
148 | /** |
||
149 | * Constructor. |
||
150 | * @param string $id the ID of this module. |
||
151 | * @param Module $parent the parent module (if any). |
||
152 | * @param array $config name-value pairs that will be used to initialize the object properties. |
||
153 | */ |
||
154 | 221 | public function __construct($id, $parent = null, $config = []) |
|
155 | { |
||
156 | 221 | $this->id = $id; |
|
157 | 221 | $this->module = $parent; |
|
158 | 221 | parent::__construct($config); |
|
159 | 221 | } |
|
160 | |||
161 | /** |
||
162 | * Returns the currently requested instance of this module class. |
||
163 | * If the module class is not currently requested, `null` will be returned. |
||
164 | * This method is provided so that you access the module instance from anywhere within the module. |
||
165 | * @return static|null the currently requested instance of this module class, or `null` if the module class is not requested. |
||
166 | */ |
||
167 | public static function getInstance() |
||
168 | { |
||
169 | $class = get_called_class(); |
||
170 | return isset(Yii::$app->loadedModules[$class]) ? Yii::$app->loadedModules[$class] : null; |
||
171 | } |
||
172 | |||
173 | /** |
||
174 | * Sets the currently requested instance of this module class. |
||
175 | * @param Module|null $instance the currently requested instance of this module class. |
||
176 | * If it is `null`, the instance of the calling class will be removed, if any. |
||
177 | */ |
||
178 | 5341 | public static function setInstance($instance) |
|
179 | { |
||
180 | 5341 | if ($instance === null) { |
|
181 | unset(Yii::$app->loadedModules[get_called_class()]); |
||
182 | } else { |
||
183 | 5341 | Yii::$app->loadedModules[get_class($instance)] = $instance; |
|
184 | } |
||
185 | 5341 | } |
|
186 | |||
187 | /** |
||
188 | * Initializes the module. |
||
189 | * |
||
190 | * This method is called after the module is created and initialized with property values |
||
191 | * given in configuration. The default implementation will initialize [[controllerNamespace]] |
||
192 | * if it is not set. |
||
193 | * |
||
194 | * If you override this method, please make sure you call the parent implementation. |
||
195 | */ |
||
196 | 221 | public function init() |
|
197 | { |
||
198 | 221 | if ($this->controllerNamespace === null) { |
|
199 | 221 | $class = get_class($this); |
|
200 | 221 | if (($pos = strrpos($class, '\\')) !== false) { |
|
201 | 16 | $this->controllerNamespace = substr($class, 0, $pos) . '\\controllers'; |
|
202 | } |
||
203 | } |
||
204 | 221 | } |
|
205 | |||
206 | /** |
||
207 | * Returns an ID that uniquely identifies this module among all modules within the current application. |
||
208 | * Note that if the module is an application, an empty string will be returned. |
||
209 | * @return string the unique ID of the module. |
||
210 | */ |
||
211 | 186 | public function getUniqueId() |
|
212 | { |
||
213 | 186 | return $this->module ? ltrim($this->module->getUniqueId() . '/' . $this->id, '/') : $this->id; |
|
214 | } |
||
215 | |||
216 | /** |
||
217 | * Returns the root directory of the module. |
||
218 | * It defaults to the directory containing the module class file. |
||
219 | * @return string the root directory of the module. |
||
220 | */ |
||
221 | 5341 | public function getBasePath() |
|
222 | { |
||
223 | 5341 | if ($this->_basePath === null) { |
|
224 | $class = new \ReflectionClass($this); |
||
225 | $this->_basePath = dirname($class->getFileName()); |
||
226 | } |
||
227 | |||
228 | 5341 | return $this->_basePath; |
|
229 | } |
||
230 | |||
231 | /** |
||
232 | * Sets the root directory of the module. |
||
233 | * This method can only be invoked at the beginning of the constructor. |
||
234 | * @param string $path the root directory of the module. This can be either a directory name or a [path alias](guide:concept-aliases). |
||
235 | * @throws InvalidArgumentException if the directory does not exist. |
||
236 | */ |
||
237 | 5341 | public function setBasePath($path) |
|
238 | { |
||
239 | 5341 | $path = Yii::getAlias($path); |
|
240 | 5341 | $p = strncmp($path, 'phar://', 7) === 0 ? $path : realpath($path); |
|
241 | 5341 | if (is_string($p) && is_dir($p)) { |
|
242 | 5341 | $this->_basePath = $p; |
|
243 | } else { |
||
244 | throw new InvalidArgumentException("The directory does not exist: $path"); |
||
245 | } |
||
246 | 5341 | } |
|
247 | |||
248 | /** |
||
249 | * Returns the directory that contains the controller classes according to [[controllerNamespace]]. |
||
250 | * Note that in order for this method to return a value, you must define |
||
251 | * an alias for the root namespace of [[controllerNamespace]]. |
||
252 | * @return string the directory that contains the controller classes. |
||
253 | * @throws InvalidArgumentException if there is no alias defined for the root namespace of [[controllerNamespace]]. |
||
254 | */ |
||
255 | 22 | public function getControllerPath() |
|
256 | { |
||
257 | 22 | return Yii::getAlias('@' . str_replace('\\', '/', $this->controllerNamespace)); |
|
0 ignored issues
–
show
|
|||
258 | } |
||
259 | |||
260 | /** |
||
261 | * Returns the directory that contains the view files for this module. |
||
262 | * @return string the root directory of view files. Defaults to "[[basePath]]/views". |
||
263 | */ |
||
264 | 2 | public function getViewPath() |
|
265 | { |
||
266 | 2 | if ($this->_viewPath === null) { |
|
267 | 2 | $this->_viewPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'views'; |
|
268 | } |
||
269 | |||
270 | 2 | return $this->_viewPath; |
|
271 | } |
||
272 | |||
273 | /** |
||
274 | * Sets the directory that contains the view files. |
||
275 | * @param string $path the root directory of view files. |
||
276 | * @throws InvalidArgumentException if the directory is invalid. |
||
277 | */ |
||
278 | public function setViewPath($path) |
||
279 | { |
||
280 | $this->_viewPath = Yii::getAlias($path); |
||
281 | } |
||
282 | |||
283 | /** |
||
284 | * Returns the directory that contains layout view files for this module. |
||
285 | * @return string the root directory of layout files. Defaults to "[[viewPath]]/layouts". |
||
286 | */ |
||
287 | 1 | public function getLayoutPath() |
|
288 | { |
||
289 | 1 | if ($this->_layoutPath === null) { |
|
290 | 1 | $this->_layoutPath = $this->getViewPath() . DIRECTORY_SEPARATOR . 'layouts'; |
|
291 | } |
||
292 | |||
293 | 1 | return $this->_layoutPath; |
|
294 | } |
||
295 | |||
296 | /** |
||
297 | * Sets the directory that contains the layout files. |
||
298 | * @param string $path the root directory or [path alias](guide:concept-aliases) of layout files. |
||
299 | * @throws InvalidArgumentException if the directory is invalid |
||
300 | */ |
||
301 | public function setLayoutPath($path) |
||
302 | { |
||
303 | $this->_layoutPath = Yii::getAlias($path); |
||
304 | } |
||
305 | |||
306 | /** |
||
307 | * Returns current module version. |
||
308 | * If version is not explicitly set, [[defaultVersion()]] method will be used to determine its value. |
||
309 | * @return string the version of this module. |
||
310 | * @since 2.0.11 |
||
311 | */ |
||
312 | 2 | public function getVersion() |
|
313 | { |
||
314 | 2 | if ($this->_version === null) { |
|
315 | 1 | $this->_version = $this->defaultVersion(); |
|
316 | } else { |
||
317 | 1 | if (!is_scalar($this->_version)) { |
|
318 | 1 | $this->_version = call_user_func($this->_version, $this); |
|
319 | } |
||
320 | } |
||
321 | |||
322 | 2 | return $this->_version; |
|
323 | } |
||
324 | |||
325 | /** |
||
326 | * Sets current module version. |
||
327 | * @param string|callable $version the version of this module. |
||
328 | * Version can be specified as a PHP callback, which can accept module instance as an argument and should |
||
329 | * return the actual version. For example: |
||
330 | * |
||
331 | * ```php |
||
332 | * function (Module $module) { |
||
333 | * //return string |
||
334 | * } |
||
335 | * ``` |
||
336 | * |
||
337 | * @since 2.0.11 |
||
338 | */ |
||
339 | 1 | public function setVersion($version) |
|
340 | { |
||
341 | 1 | $this->_version = $version; |
|
342 | 1 | } |
|
343 | |||
344 | /** |
||
345 | * Returns default module version. |
||
346 | * Child class may override this method to provide more specific version detection. |
||
347 | * @return string the version of this module. |
||
348 | * @since 2.0.11 |
||
349 | */ |
||
350 | 1 | protected function defaultVersion() |
|
351 | { |
||
352 | 1 | if ($this->module === null) { |
|
353 | 1 | return '1.0'; |
|
354 | } |
||
355 | |||
356 | return $this->module->getVersion(); |
||
357 | } |
||
358 | |||
359 | /** |
||
360 | * Defines path aliases. |
||
361 | * This method calls [[Yii::setAlias()]] to register the path aliases. |
||
362 | * This method is provided so that you can define path aliases when configuring a module. |
||
363 | * @property array list of path aliases to be defined. The array keys are alias names |
||
364 | * (must start with `@`) and the array values are the corresponding paths or aliases. |
||
365 | * See [[setAliases()]] for an example. |
||
366 | * @param array $aliases list of path aliases to be defined. The array keys are alias names |
||
367 | * (must start with `@`) and the array values are the corresponding paths or aliases. |
||
368 | * For example, |
||
369 | * |
||
370 | * ```php |
||
371 | * [ |
||
372 | * '@models' => '@app/models', // an existing alias |
||
373 | * '@backend' => __DIR__ . '/../backend', // a directory |
||
374 | * ] |
||
375 | * ``` |
||
376 | */ |
||
377 | 407 | public function setAliases($aliases) |
|
378 | { |
||
379 | 407 | foreach ($aliases as $name => $alias) { |
|
380 | 407 | Yii::setAlias($name, $alias); |
|
381 | } |
||
382 | 407 | } |
|
383 | |||
384 | /** |
||
385 | * Checks whether the child module of the specified ID exists. |
||
386 | * This method supports checking the existence of both child and grand child modules. |
||
387 | * @param string $id module ID. For grand child modules, use ID path relative to this module (e.g. `admin/content`). |
||
388 | * @return bool whether the named module exists. Both loaded and unloaded modules |
||
389 | * are considered. |
||
390 | */ |
||
391 | 1 | public function hasModule($id) |
|
392 | { |
||
393 | 1 | if (($pos = strpos($id, '/')) !== false) { |
|
394 | // sub-module |
||
395 | $module = $this->getModule(substr($id, 0, $pos)); |
||
396 | |||
397 | return $module === null ? false : $module->hasModule(substr($id, $pos + 1)); |
||
398 | } |
||
399 | |||
400 | 1 | return isset($this->_modules[$id]); |
|
401 | } |
||
402 | |||
403 | /** |
||
404 | * Retrieves the child module of the specified ID. |
||
405 | * This method supports retrieving both child modules and grand child modules. |
||
406 | * @param string $id module ID (case-sensitive). To retrieve grand child modules, |
||
407 | * use ID path relative to this module (e.g. `admin/content`). |
||
408 | * @param bool $load whether to load the module if it is not yet loaded. |
||
409 | * @return Module|null the module instance, `null` if the module does not exist. |
||
410 | * @see hasModule() |
||
411 | */ |
||
412 | 7 | public function getModule($id, $load = true) |
|
413 | { |
||
414 | 7 | if (($pos = strpos($id, '/')) !== false) { |
|
415 | // sub-module |
||
416 | $module = $this->getModule(substr($id, 0, $pos)); |
||
417 | |||
418 | return $module === null ? null : $module->getModule(substr($id, $pos + 1), $load); |
||
419 | } |
||
420 | |||
421 | 7 | if (isset($this->_modules[$id])) { |
|
422 | 4 | if ($this->_modules[$id] instanceof self) { |
|
423 | 3 | return $this->_modules[$id]; |
|
424 | 2 | } elseif ($load) { |
|
425 | 2 | Yii::debug("Loading module: $id", __METHOD__); |
|
426 | /* @var $module Module */ |
||
427 | 2 | $module = Yii::createObject($this->_modules[$id], [$id, $this]); |
|
428 | 2 | $module::setInstance($module); |
|
429 | 2 | return $this->_modules[$id] = $module; |
|
430 | } |
||
431 | } |
||
432 | |||
433 | 5 | return null; |
|
434 | } |
||
435 | |||
436 | /** |
||
437 | * Adds a sub-module to this module. |
||
438 | * @param string $id module ID. |
||
439 | * @param Module|array|null $module the sub-module to be added to this module. This can |
||
440 | * be one of the following: |
||
441 | * |
||
442 | * - a [[Module]] object |
||
443 | * - a configuration array: when [[getModule()]] is called initially, the array |
||
444 | * will be used to instantiate the sub-module |
||
445 | * - `null`: the named sub-module will be removed from this module |
||
446 | */ |
||
447 | 2 | public function setModule($id, $module) |
|
448 | { |
||
449 | 2 | if ($module === null) { |
|
450 | unset($this->_modules[$id]); |
||
451 | } else { |
||
452 | 2 | $this->_modules[$id] = $module; |
|
453 | 2 | if ($module instanceof self) { |
|
454 | 2 | $module->module = $this; |
|
455 | } |
||
456 | } |
||
457 | 2 | } |
|
458 | |||
459 | /** |
||
460 | * Returns the sub-modules in this module. |
||
461 | * @param bool $loadedOnly whether to return the loaded sub-modules only. If this is set `false`, |
||
462 | * then all sub-modules registered in this module will be returned, whether they are loaded or not. |
||
463 | * Loaded modules will be returned as objects, while unloaded modules as configuration arrays. |
||
464 | * @return array the modules (indexed by their IDs). |
||
465 | */ |
||
466 | 21 | public function getModules($loadedOnly = false) |
|
467 | { |
||
468 | 21 | if ($loadedOnly) { |
|
469 | $modules = []; |
||
470 | foreach ($this->_modules as $module) { |
||
471 | if ($module instanceof self) { |
||
472 | $modules[] = $module; |
||
473 | } |
||
474 | } |
||
475 | |||
476 | return $modules; |
||
477 | } |
||
478 | |||
479 | 21 | return $this->_modules; |
|
480 | } |
||
481 | |||
482 | /** |
||
483 | * Registers sub-modules in the current module. |
||
484 | * |
||
485 | * Each sub-module should be specified as a name-value pair, where |
||
486 | * name refers to the ID of the module and value the module or a configuration |
||
487 | * array that can be used to create the module. In the latter case, [[Yii::createObject()]] |
||
488 | * will be used to create the module. |
||
489 | * |
||
490 | * If a new sub-module has the same ID as an existing one, the existing one will be overwritten silently. |
||
491 | * |
||
492 | * The following is an example for registering two sub-modules: |
||
493 | * |
||
494 | * ```php |
||
495 | * [ |
||
496 | * 'comment' => [ |
||
497 | * 'class' => 'app\modules\comment\CommentModule', |
||
498 | * 'db' => 'db', |
||
499 | * ], |
||
500 | * 'booking' => ['class' => 'app\modules\booking\BookingModule'], |
||
501 | * ] |
||
502 | * ``` |
||
503 | * |
||
504 | * @param array $modules modules (id => module configuration or instances). |
||
505 | */ |
||
506 | 4 | public function setModules($modules) |
|
507 | { |
||
508 | 4 | foreach ($modules as $id => $module) { |
|
509 | 4 | $this->_modules[$id] = $module; |
|
510 | 4 | if ($module instanceof self) { |
|
511 | 4 | $module->module = $this; |
|
512 | } |
||
513 | } |
||
514 | 4 | } |
|
515 | |||
516 | /** |
||
517 | * Runs a controller action specified by a route. |
||
518 | * This method parses the specified route and creates the corresponding child module(s), controller and action |
||
519 | * instances. It then calls [[Controller::runAction()]] to run the action with the given parameters. |
||
520 | * If the route is empty, the method will use [[defaultRoute]]. |
||
521 | * @param string $route the route that specifies the action. |
||
522 | * @param array $params the parameters to be passed to the action |
||
523 | * @return mixed the result of the action. |
||
524 | * @throws InvalidRouteException if the requested route cannot be resolved into an action successfully. |
||
525 | */ |
||
526 | 12 | public function runAction($route, $params = []) |
|
527 | { |
||
528 | 12 | $parts = $this->createController($route); |
|
529 | 12 | if (is_array($parts)) { |
|
530 | /* @var $controller Controller */ |
||
531 | 12 | list($controller, $actionID) = $parts; |
|
532 | 12 | $oldController = Yii::$app->controller; |
|
533 | 12 | Yii::$app->controller = $controller; |
|
534 | 12 | $result = $controller->runAction($actionID, $params); |
|
535 | 12 | if ($oldController !== null) { |
|
536 | 9 | Yii::$app->controller = $oldController; |
|
537 | } |
||
538 | |||
539 | 12 | return $result; |
|
540 | } |
||
541 | |||
542 | $id = $this->getUniqueId(); |
||
543 | throw new InvalidRouteException('Unable to resolve the request "' . ($id === '' ? $route : $id . '/' . $route) . '".'); |
||
544 | } |
||
545 | |||
546 | /** |
||
547 | * Creates a controller instance based on the given route. |
||
548 | * |
||
549 | * The route should be relative to this module. The method implements the following algorithm |
||
550 | * to resolve the given route: |
||
551 | * |
||
552 | * 1. If the route is empty, use [[defaultRoute]]; |
||
553 | * 2. If the first segment of the route is found in [[controllerMap]], create a controller |
||
554 | * based on the corresponding configuration found in [[controllerMap]]; |
||
555 | * 3. If the first segment of the route is a valid module ID as declared in [[modules]], |
||
556 | * call the module's `createController()` with the rest part of the route; |
||
557 | * 4. The given route is in the format of `abc/def/xyz`. Try either `abc\DefController` |
||
558 | * or `abc\def\XyzController` class within the [[controllerNamespace|controller namespace]]. |
||
559 | * |
||
560 | * If any of the above steps resolves into a controller, it is returned together with the rest |
||
561 | * part of the route which will be treated as the action ID. Otherwise, `false` will be returned. |
||
562 | * |
||
563 | * @param string $route the route consisting of module, controller and action IDs. |
||
564 | * @return array|bool If the controller is created successfully, it will be returned together |
||
565 | * with the requested action ID. Otherwise `false` will be returned. |
||
566 | * @throws InvalidConfigException if the controller class and its file do not match. |
||
567 | */ |
||
568 | 104 | public function createController($route) |
|
569 | { |
||
570 | 104 | if ($route === '') { |
|
571 | 1 | $route = $this->defaultRoute; |
|
572 | } |
||
573 | |||
574 | // double slashes or leading/ending slashes may cause substr problem |
||
575 | 104 | $route = trim($route, '/'); |
|
576 | 104 | if (strpos($route, '//') !== false) { |
|
577 | return false; |
||
578 | } |
||
579 | |||
580 | 104 | if (strpos($route, '/') !== false) { |
|
581 | 16 | list($id, $route) = explode('/', $route, 2); |
|
582 | } else { |
||
583 | 92 | $id = $route; |
|
584 | 92 | $route = ''; |
|
585 | } |
||
586 | |||
587 | // module and controller map take precedence |
||
588 | 104 | if (isset($this->controllerMap[$id])) { |
|
589 | 103 | $controller = Yii::createObject($this->controllerMap[$id], [$id, $this]); |
|
590 | 103 | return [$controller, $route]; |
|
591 | } |
||
592 | 5 | $module = $this->getModule($id); |
|
593 | 5 | if ($module !== null) { |
|
594 | 2 | return $module->createController($route); |
|
595 | } |
||
596 | |||
597 | 5 | if (($pos = strrpos($route, '/')) !== false) { |
|
598 | 1 | $id .= '/' . substr($route, 0, $pos); |
|
599 | 1 | $route = substr($route, $pos + 1); |
|
600 | } |
||
601 | |||
602 | 5 | $controller = $this->createControllerByID($id); |
|
603 | 5 | if ($controller === null && $route !== '') { |
|
604 | 2 | $controller = $this->createControllerByID($id . '/' . $route); |
|
605 | 2 | $route = ''; |
|
606 | } |
||
607 | |||
608 | 5 | return $controller === null ? false : [$controller, $route]; |
|
609 | } |
||
610 | |||
611 | /** |
||
612 | * Creates a controller based on the given controller ID. |
||
613 | * |
||
614 | * The controller ID is relative to this module. The controller class |
||
615 | * should be namespaced under [[controllerNamespace]]. |
||
616 | * |
||
617 | * Note that this method does not check [[modules]] or [[controllerMap]]. |
||
618 | * |
||
619 | * @param string $id the controller ID. |
||
620 | * @return Controller|null the newly created controller instance, or `null` if the controller ID is invalid. |
||
621 | * @throws InvalidConfigException if the controller class and its file name do not match. |
||
622 | * This exception is only thrown when in debug mode. |
||
623 | */ |
||
624 | 6 | public function createControllerByID($id) |
|
625 | { |
||
626 | 6 | $pos = strrpos($id, '/'); |
|
627 | 6 | if ($pos === false) { |
|
628 | 6 | $prefix = ''; |
|
629 | 6 | $className = $id; |
|
630 | } else { |
||
631 | 2 | $prefix = substr($id, 0, $pos + 1); |
|
632 | 2 | $className = substr($id, $pos + 1); |
|
633 | } |
||
634 | |||
635 | 6 | if ($this->isIncorrectClassNameOrPrefix($className, $prefix)) { |
|
636 | 2 | return null; |
|
637 | } |
||
638 | |||
639 | 6 | $className = preg_replace_callback('%-([a-z0-9_])%i', function ($matches) { |
|
640 | 4 | return ucfirst($matches[1]); |
|
641 | 6 | }, ucfirst($className)) . 'Controller'; |
|
642 | 6 | $className = ltrim($this->controllerNamespace . '\\' . str_replace('/', '\\', $prefix) . $className, '\\'); |
|
643 | 6 | if (strpos($className, '-') !== false || !class_exists($className)) { |
|
644 | 2 | return null; |
|
645 | } |
||
646 | |||
647 | 5 | if (is_subclass_of($className, 'yii\base\Controller')) { |
|
648 | 5 | $controller = Yii::createObject($className, [$id, $this]); |
|
649 | 5 | return get_class($controller) === $className ? $controller : null; |
|
650 | } elseif (YII_DEBUG) { |
||
651 | throw new InvalidConfigException('Controller class must extend from \\yii\\base\\Controller.'); |
||
652 | } |
||
653 | |||
654 | return null; |
||
655 | } |
||
656 | |||
657 | /** |
||
658 | * Checks if class name or prefix is incorrect |
||
659 | * |
||
660 | * @param string $className |
||
661 | * @param string $prefix |
||
662 | * @return bool |
||
663 | */ |
||
664 | 6 | private function isIncorrectClassNameOrPrefix($className, $prefix) |
|
665 | { |
||
666 | 6 | if (!preg_match('%^[a-z][a-z0-9\\-_]*$%', $className)) { |
|
667 | 2 | return true; |
|
668 | } |
||
669 | 6 | if ($prefix !== '' && !preg_match('%^[a-z0-9_/]+$%i', $prefix)) { |
|
670 | return true; |
||
671 | } |
||
672 | |||
673 | 6 | return false; |
|
674 | } |
||
675 | |||
676 | /** |
||
677 | * This method is invoked right before an action within this module is executed. |
||
678 | * |
||
679 | * The method will trigger the [[EVENT_BEFORE_ACTION]] event. The return value of the method |
||
680 | * will determine whether the action should continue to run. |
||
681 | * |
||
682 | * In case the action should not run, the request should be handled inside of the `beforeAction` code |
||
683 | * by either providing the necessary output or redirecting the request. Otherwise the response will be empty. |
||
684 | * |
||
685 | * If you override this method, your code should look like the following: |
||
686 | * |
||
687 | * ```php |
||
688 | * public function beforeAction($action) |
||
689 | * { |
||
690 | * if (!parent::beforeAction($action)) { |
||
691 | * return false; |
||
692 | * } |
||
693 | * |
||
694 | * // your custom code here |
||
695 | * |
||
696 | * return true; // or false to not run the action |
||
697 | * } |
||
698 | * ``` |
||
699 | * |
||
700 | * @param Action $action the action to be executed. |
||
701 | * @return bool whether the action should continue to be executed. |
||
702 | */ |
||
703 | 318 | public function beforeAction($action) |
|
704 | { |
||
705 | 318 | $event = new ActionEvent($action); |
|
706 | 318 | $this->trigger(self::EVENT_BEFORE_ACTION, $event); |
|
707 | 318 | return $event->isValid; |
|
708 | } |
||
709 | |||
710 | /** |
||
711 | * This method is invoked right after an action within this module is executed. |
||
712 | * |
||
713 | * The method will trigger the [[EVENT_AFTER_ACTION]] event. The return value of the method |
||
714 | * will be used as the action return value. |
||
715 | * |
||
716 | * If you override this method, your code should look like the following: |
||
717 | * |
||
718 | * ```php |
||
719 | * public function afterAction($action, $result) |
||
720 | * { |
||
721 | * $result = parent::afterAction($action, $result); |
||
722 | * // your custom code here |
||
723 | * return $result; |
||
724 | * } |
||
725 | * ``` |
||
726 | * |
||
727 | * @param Action $action the action just executed. |
||
728 | * @param mixed $result the action return result. |
||
729 | * @return mixed the processed action result. |
||
730 | */ |
||
731 | 310 | public function afterAction($action, $result) |
|
732 | { |
||
733 | 310 | $event = new ActionEvent($action); |
|
734 | 310 | $event->result = $result; |
|
735 | 310 | $this->trigger(self::EVENT_AFTER_ACTION, $event); |
|
736 | 310 | return $event->result; |
|
737 | } |
||
738 | |||
739 | /** |
||
740 | * {@inheritdoc} |
||
741 | * |
||
742 | * Since version 2.0.13, if a component isn't defined in the module, it will be looked up in the parent module. |
||
743 | * The parent module may be the application. |
||
744 | */ |
||
745 | 2077 | public function get($id, $throwException = true) |
|
746 | { |
||
747 | 2077 | if (!isset($this->module)) { |
|
748 | 2077 | return parent::get($id, $throwException); |
|
749 | } |
||
750 | |||
751 | 3 | $component = parent::get($id, false); |
|
752 | 3 | if ($component === null) { |
|
753 | 3 | $component = $this->module->get($id, $throwException); |
|
754 | } |
||
755 | 3 | return $component; |
|
756 | } |
||
757 | |||
758 | /** |
||
759 | * {@inheritdoc} |
||
760 | * |
||
761 | * Since version 2.0.13, if a component isn't defined in the module, it will be looked up in the parent module. |
||
762 | * The parent module may be the application. |
||
763 | */ |
||
764 | 5210 | public function has($id, $checkInstance = false) |
|
765 | { |
||
766 | 5210 | return parent::has($id, $checkInstance) || (isset($this->module) && $this->module->has($id, $checkInstance)); |
|
767 | } |
||
768 | } |
||
769 |
If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.