You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
188 lines
6.0 KiB
PHP
188 lines
6.0 KiB
PHP
<?php
|
|
|
|
namespace jrosset\CliProgram\AutoDiscovery;
|
|
|
|
use FilesystemIterator;
|
|
use jrosset\BetterPhpToken\BetterPhpToken;
|
|
use jrosset\CliProgram\AutoPrefix\TAutoPrefixManagement;
|
|
use ReflectionClass;
|
|
use ReflectionException;
|
|
use Symfony\Component\Console\Command\Command;
|
|
|
|
/**
|
|
* A spot for command auto discovery
|
|
*/
|
|
class AutoDiscoveryDirectory implements IAutoDiscoverySpot {
|
|
use TAutoPrefixManagement;
|
|
|
|
/**
|
|
* @var string The directory path
|
|
*/
|
|
private string $directoryPath;
|
|
/**
|
|
* @var bool True if search in subdirectories too, else False
|
|
*/
|
|
private bool $processSubDirectories;
|
|
|
|
/**
|
|
* Initialization
|
|
*
|
|
* @param string $directoryPath The directory path
|
|
* @param bool $processSubDirectories True if search in subdirectories too, else False
|
|
*/
|
|
public function __construct (string $directoryPath, bool $processSubDirectories = true) {
|
|
$this->setDirectoryPath($directoryPath);
|
|
$this->setProcessSubDirectories($processSubDirectories);
|
|
}
|
|
|
|
/**
|
|
* The directory path
|
|
*
|
|
* @return string The directory path
|
|
*/
|
|
public function getDirectoryPath (): string {
|
|
return $this->directoryPath;
|
|
}
|
|
/**
|
|
* Set the directory path
|
|
*
|
|
* @param string $directoryPath The directory path
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setDirectoryPath (string $directoryPath): self {
|
|
$this->directoryPath = $directoryPath;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* True if search in subdirectories too, else False
|
|
*
|
|
* @return bool True if search in subdirectories too, else False
|
|
*/
|
|
public function isProcessSubDirectories (): bool {
|
|
return $this->processSubDirectories;
|
|
}
|
|
/**
|
|
* Set if search in subdirectories too
|
|
*
|
|
* @param bool $processSubDirectories True if search in subdirectories too, else False
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setProcessSubDirectories (bool $processSubDirectories): self {
|
|
$this->processSubDirectories = $processSubDirectories;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function getCommands (): array {
|
|
return $this->getClassesOfDirectory($this->getDirectoryPath());
|
|
}
|
|
/**
|
|
* Get spot's classes of a directory
|
|
*
|
|
* @param string $directoryPath The directory path
|
|
*
|
|
* @return Command[] Spot's classes
|
|
*/
|
|
private function getClassesOfDirectory (string $directoryPath): array {
|
|
$classes = [];
|
|
|
|
$directoryIterator = new FilesystemIterator($directoryPath, FilesystemIterator::CURRENT_AS_SELF);
|
|
foreach ($directoryIterator as $fileInfo) {
|
|
if ($fileInfo->isDot() || mb_substr($fileInfo->getFilename(), 0, 1) === '.') {
|
|
continue;
|
|
}
|
|
if ($fileInfo->isDir()) {
|
|
if ($this->isProcessSubDirectories()) {
|
|
$classes = array_merge(
|
|
$classes,
|
|
$this->getClassesOfDirectory($fileInfo->getPathname())
|
|
);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (array_key_exists($fileInfo->getRealPath(), $classes)) {
|
|
continue;
|
|
}
|
|
|
|
$className = static::getClassNameFromFile($fileInfo->getRealPath());
|
|
if ($className === null) {
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
$class = new ReflectionClass($className);
|
|
if ($class->isAbstract()
|
|
|| $class->isInterface()
|
|
|| $class->isTrait()
|
|
|| !is_subclass_of($class->getName(), Command::class)
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
$classes[$fileInfo->getRealPath()] = $this->applyAutoPrefixOnCommand($class->newInstance());
|
|
}
|
|
catch (ReflectionException $exception) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return $classes;
|
|
}
|
|
/**
|
|
* Extract the class name from a file
|
|
*
|
|
* Extract only the first class name
|
|
*
|
|
* @param string $path The class file path
|
|
*
|
|
* @return string|null The class name ou null if none
|
|
*/
|
|
private static function getClassNameFromFile (string $path): ?string {
|
|
defined('T_NAME_QUALIFIED') || define('T_NAME_QUALIFIED', 10002);
|
|
|
|
$fileHandler = fopen($path, 'r');
|
|
|
|
$namespace = $buffer = '';
|
|
$currTokenGlobal = 0;
|
|
while (!feof($fileHandler)) {
|
|
$buffer .= fread($fileHandler, 512);
|
|
if (mb_strpos($buffer, '{') === false) {
|
|
continue;
|
|
}
|
|
|
|
$tokens = BetterPhpToken::tokenize($buffer);
|
|
$nbTokens = count($tokens);
|
|
for (; $currTokenGlobal < $nbTokens; $currTokenGlobal++) {
|
|
$token = $tokens[$currTokenGlobal];
|
|
if ($token->is(T_NAMESPACE)) {
|
|
for ($currTokenSub = $currTokenGlobal + 1; $currTokenSub < $nbTokens; $currTokenSub++) {
|
|
$subToken = $tokens[$currTokenSub];
|
|
|
|
if ($subToken->is(T_STRING, T_NAME_QUALIFIED)) {
|
|
$namespace .= '\\' . $subToken->getText();
|
|
}
|
|
elseif ($subToken->getText() === '{' || $subToken->getText() === ';') {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
elseif ($token->is(T_CLASS) && ($currTokenGlobal === 0 || !$tokens[$currTokenGlobal - 1]->is(T_DOUBLE_COLON))) {
|
|
for ($currTokenSub = $currTokenGlobal + 1; $currTokenSub < $nbTokens; $currTokenSub++) {
|
|
$subToken = $tokens[$currTokenSub];
|
|
if ($subToken->getText() === '{') {
|
|
return (mb_strlen($namespace) > 0 ? $namespace . '\\' : '') . $tokens[$currTokenGlobal + 2]->getText();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
} |