Last active
July 4, 2022 00:11
-
-
Save WinterSilence/1d5fe67ec296b7c099264d31ba435725 to your computer and use it in GitHub Desktop.
Yii2 application reflection
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace yii\helpers; | |
use ReflectionClass; | |
use ReflectionMethod; | |
use Yii; | |
use yii\base\Action; | |
use yii\base\Application; | |
use yii\base\Behavior; | |
use yii\base\Controller; | |
use yii\helpers\FileHelper; | |
use yii\helpers\Inflector; | |
use function array_merge; | |
use function class_exists; | |
use function is_string; | |
use function strlen; | |
use function str_replace; | |
use function substr; | |
use function preg_match; | |
use function natsort; | |
/** | |
* Yii application's reflection. | |
*/ | |
class AppHelper | |
{ | |
/** | |
* @var Application The application instance to reflect | |
*/ | |
protected $app; | |
/** | |
* @var string[] The cached list of the application controllers as class(FCQN)/ID pairs | |
*/ | |
protected $controllers; | |
/** | |
* Create reflection. | |
* | |
* @param Application|null $application The application instance or NULL to reflect current application | |
*/ | |
public function __construct(Application $application = null) | |
{ | |
$this->app = $application ?: Yii::$app; | |
} | |
/** | |
* Returns application controllers as class(FCQN)/identifier pairs. | |
* | |
* @param bool $refresh If TRUE, refresh list, else, return cached list | |
* @return string[] | |
*/ | |
public function getControllers(bool $refresh = false): array | |
{ | |
if ($refresh || !isset($this->controllers)) { | |
$files = FileHelper::findFiles( | |
$this->app->controllerPath, | |
['only' => ['*Controller.php']] | |
); | |
$prefixLength = strlen($this->app->controllerPath) + 1; | |
$ns = rtrim($this->app->controllerNamespace, '\\') . '\\'; | |
$this->controllers = []; | |
foreach ($files as $file) { | |
// for example, converts file name '@app/controllers/foo/BarBazController.php' to ID 'foo/bar-baz' | |
$file = substr($file, $prefixLength, -14); | |
$class = $ns . str_replace(DIRECTORY_SEPARATOR, '\\', $file) . 'Controller'; | |
$this->controllers[$class] = $this->controllerClassToId($class); | |
} | |
} | |
return $this->controllers; | |
} | |
/** | |
* Returns inline actions of given controller. | |
* | |
* @param Controller $controller The controller instance | |
* @return string[] An array of inline actions as method/identifier pairs | |
*/ | |
public function getControllerInlineActions(Controller $controller): array | |
{ | |
$actions = []; | |
$class = new ReflectionClass($controller); | |
foreach ($class->getMethods() as $method) { | |
if ($this->isActionMethod($method)) { | |
$actions[$method->name] = $this->actionMethodToId($method->name); | |
} | |
} | |
return $actions; | |
} | |
/** | |
* Returns standalone actions of given controller. | |
* | |
* @param Controller $controller The controller instance | |
* @return string[] An array of standalone actions as class/identifier pairs | |
*/ | |
public function getControllerStandaloneActions(Controller $controller): array | |
{ | |
$actions = []; | |
foreach ($controller->actions() as $id => $action) { | |
if ($action instanceof Action) { | |
$class = get_class($action); | |
$id = $action->id; | |
} elseif (is_string($action)) { | |
$class = $action; | |
} else { | |
/** @var array $action */ | |
$class = $action['class']; | |
} | |
$actions[$class] = $id; | |
} | |
return $actions; | |
} | |
/** | |
* Returns all actions of the given controller. | |
* | |
* @param Controller $controller The controller instance | |
* @param bool $sort If true, sort actions by IDs | |
* @return array An array of action ID/class pairs | |
*/ | |
public function getControllerActions(Controller $controller, bool $sort = false): array | |
{ | |
$actions = array_merge( | |
$this->getControllerInlineActions($controller), | |
$this->getControllerStandaloneActions($controller) | |
); | |
if ($sort) { | |
natsort($actions); | |
} | |
return $actions; | |
} | |
/** | |
* Validates if the given class is a valid web/console controller class. | |
* | |
* @param string $className The class name to test | |
* @param bool $ignoreApp If TRUE, not checking inheritance from application controller | |
* @return bool | |
* @throws \ReflectionException | |
*/ | |
public function isControllerClass(string $className, bool $ignoreApp = false): bool | |
{ | |
if (!class_exists($className)) { | |
return false; | |
} | |
$class = new ReflectionClass($className); | |
if ($class->isAbstract()) { | |
return false; | |
} | |
if ($ignoreApp) { | |
$parentClass = Controller::class; | |
} else { | |
$parentClass = $this->app instanceof \yii\console\Application | |
? \yii\console\Controller::class | |
: \yii\web\Controller::class; | |
} | |
return $class->isSubclassOf($parentClass); | |
} | |
/** | |
* Check is this application is runned in console. | |
* | |
* @return bool | |
*/ | |
public function isConsole(): bool | |
{ | |
if ($this->app instanceof \yii\console\Application) { | |
return true; | |
} | |
return $this->app->request instanceof \yii\base\Request | |
&& $this->app->request->isConsoleRequest; | |
} | |
/** | |
* Checks action method by name. | |
* | |
* @param ReflectionMethod $method The method to test | |
* @return bool | |
*/ | |
public function isActionMethod(ReflectionMethod $method): bool | |
{ | |
return $method->isPublic() | |
&& !$method->isStatic() | |
&& preg_match('/^action[A-Z][A-Za-z\d]+$/', $method->name) === 1; | |
} | |
/** | |
* Converts the class name (FCQN) to controller identifier. | |
* | |
* @param string $name The class name (for example, `app\controllers\SiteController`) | |
* @return string The controller ID | |
*/ | |
public function controllerClassToId(string $name): string | |
{ | |
$ns = rtrim($this->app->controllerNamespace, '\\'); | |
// trim namespace and `Controller` suffix | |
$name = substr(ltrim($name, '\\'), strlen($ns) + 1, -10); | |
return str_replace(['\\', '/-'], '/', Inflector::camel2id($name, '-', true)); | |
} | |
/** | |
* Converts controller's method name to action identifier. | |
* | |
* @param string $name The method name (for example, `actionFooBar`) | |
* @return string The action ID | |
*/ | |
public function actionMethodToId(string $name): string | |
{ | |
return Inflector::camel2id(substr($name, 6)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment