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