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.
PhpCliProgram/src/CliProgram/AutoDiscovery/AutoDiscoveryDirectory.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;
}
}