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); |
|||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
It seems like
$path can also be of type false ; however, parameter $string1 of strncmp() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
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)); |
|||
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 |