AutoDiscoverySpot → AutoDiscoveryDirectory + manage AutoPrefix

2.x
Julien Rosset 2 years ago
parent 4ecf6b6869
commit 0002837bd6

@ -3,6 +3,7 @@
namespace jrosset\CliProgram; namespace jrosset\CliProgram;
use FilesystemIterator; use FilesystemIterator;
use jrosset\BetterPhpToken\BetterPhpToken;
use ReflectionClass; use ReflectionClass;
use ReflectionException; use ReflectionException;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
@ -10,7 +11,9 @@ use Symfony\Component\Console\Command\Command;
/** /**
* A spot for command auto discovery * A spot for command auto discovery
*/ */
class AutoDiscoverySpot implements IAutoDiscoverySpot { class AutoDiscoveryDirectory implements IAutoDiscoverySpot {
use TAutoPrefixManagement;
/** /**
* @var string The directory path * @var string The directory path
*/ */
@ -75,7 +78,7 @@ class AutoDiscoverySpot implements IAutoDiscoverySpot {
* @inheritDoc * @inheritDoc
*/ */
public function getCommands (): array { public function getCommands (): array {
return static::getClassesOfDirectory($this->getDirectoryPath(), $this->isProcessSubDirectories()); return $this->getClassesOfDirectory($this->getDirectoryPath());
} }
/** /**
* Get spot's classes of a directory * Get spot's classes of a directory
@ -85,7 +88,7 @@ class AutoDiscoverySpot implements IAutoDiscoverySpot {
* *
* @return Command[] Spot's classes * @return Command[] Spot's classes
*/ */
private static function getClassesOfDirectory (string $directoryPath, bool $processSubDirectories): array { private function getClassesOfDirectory (string $directoryPath): array {
$classes = []; $classes = [];
$directoryIterator = new FilesystemIterator($directoryPath, FilesystemIterator::CURRENT_AS_SELF); $directoryIterator = new FilesystemIterator($directoryPath, FilesystemIterator::CURRENT_AS_SELF);
@ -94,24 +97,26 @@ class AutoDiscoverySpot implements IAutoDiscoverySpot {
continue; continue;
} }
if ($fileInfo->isDir()) { if ($fileInfo->isDir()) {
if ($processSubDirectories) { if ($this->isProcessSubDirectories()) {
/** @noinspection PhpConditionAlreadyCheckedInspection */
$classes = array_merge( $classes = array_merge(
$classes, $classes,
static::getClassesOfDirectory($fileInfo->getPathname(), $processSubDirectories) $this->getClassesOfDirectory($fileInfo->getPathname())
); );
} }
continue; continue;
} }
$spotClass = AutoDiscoverySpotClass::createFromFile($fileInfo->getPathname()); if (array_key_exists($fileInfo->getRealPath(), $classes)) {
$classPath = realpath($spotClass->getName()); continue;
if (array_key_exists($classPath, $classes)) { }
$className = static::getClassNameFromFile($fileInfo->getRealPath());
if ($className === null) {
continue; continue;
} }
try { try {
$class = new ReflectionClass($spotClass->getName()); $class = new ReflectionClass($className);
if ($class->isAbstract() if ($class->isAbstract()
|| $class->isInterface() || $class->isInterface()
|| $class->isTrait() || $class->isTrait()
@ -120,7 +125,7 @@ class AutoDiscoverySpot implements IAutoDiscoverySpot {
continue; continue;
} }
$classes[$classPath] = $class->newInstance(); $classes[$fileInfo->getRealPath()] = $this->applyAutoPrefixOnCommand($class->newInstance());
} }
catch (ReflectionException $exception) { catch (ReflectionException $exception) {
continue; continue;
@ -129,4 +134,55 @@ class AutoDiscoverySpot implements IAutoDiscoverySpot {
return $classes; 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;
}
} }

@ -1,136 +0,0 @@
<?php
namespace jrosset\CliProgram;
use jrosset\BetterPhpToken\BetterPhpToken;
use LogicException;
use ReflectionClass;
use ReflectionException;
use RuntimeException;
use Throwable;
/**
* A command auto discovery spot class
*/
class AutoDiscoverySpotClass implements IAutoDiscoverySpotClass {
/**
* @var class-string The class name
*/
private string $name;
/**
* @var string The class file path
*/
private string $path;
/**
* Initialization
*
* @param string $name The class name
* @param string $path The class file path
*
* @throws LogicException If the file path doesn't exist
* @throws RuntimeException If the file path isn't readable
*/
public function __construct (string $name, string $path) {
$this->setPath($path);
$this->setName($name);
}
/**
* Create from file path only
*
* Get the first class name of the file
*
* @param string $path The class file path
*
* @return $this
*/
public static function createFromFile (string $path): self {
defined('T_NAME_QUALIFIED') || define('T_NAME_QUALIFIED', 10002);
$fileHandler = fopen($path, 'r');
$class = $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() === '{') {
$class = $tokens[$currTokenGlobal + 2]->getText();
}
}
}
}
}
return new static((mb_strlen($namespace) > 0 ? $namespace . '\\' : '') . $class, $path);
}
/**
* @inheritDoc
*/
public function getName (): string {
return $this->name;
}
/**
* Set the class name
*
* @param class-string $name The class name
*
* @return $this
*/
public function setName (string $name): self {
$this->name = $name;
return $this;
}
/**
* @inheritDoc
*/
public function getPath (): string {
return $this->path;
}
/**
* Set the class file path
*
* @param string $path The class file path
*
* @return $this
*
* @throws LogicException If the file path doesn't exist
* @throws RuntimeException If the file path isn't readable
*/
public function setPath (string $path): self {
if (!file_exists($path)) {
throw new LogicException('Invalid class path: file is missing');
}
if (!is_readable($path)) {
throw new RuntimeException('Invalid class path: file is not readable');
}
$this->path = realpath($path);
return $this;
}
}

@ -1,21 +0,0 @@
<?php
namespace jrosset\CliProgram;
/**
* Interface for a command auto discovery spot class
*/
interface IAutoDiscoverySpotClass {
/**
* The class name
*
* @return class-string The class name
*/
public function getName (): string;
/**
* The class file path
*
* @return string The class file path
*/
public function getPath(): string;
}

@ -5,7 +5,7 @@ namespace jrosset\CliProgram;
/** /**
* An application with command auto discovery * An application with command auto discovery
*/ */
trait AutoDiscoveryApplication { trait TAutoDiscoveryApplication {
/** /**
* @var IAutoDiscoverySpot[] The list of discovery spots * @var IAutoDiscoverySpot[] The list of discovery spots
*/ */

@ -2,10 +2,12 @@
namespace jrosset\CliProgram; namespace jrosset\CliProgram;
use Symfony\Component\Console\Command\Command;
/** /**
* An application managing commands with auto prefix * Implements a commands with auto prefix management
*/ */
trait AutoPrefixApplication { trait TAutoPrefixManagement {
/** /**
* @var IAutoPrefixManager[] The lists of manager for commands auto prefix * @var IAutoPrefixManager[] The lists of manager for commands auto prefix
*/ */
@ -47,13 +49,13 @@ trait AutoPrefixApplication {
} }
/** /**
* Apply commands auto prefixes * Apply the auto prefix managers on a command (until one of them manage it)
* *
* @return $this * @param Command $command The command
*
* @return Command The command
*/ */
public function applyAutoPrefixes (): self { protected function applyAutoPrefixOnCommand (Command $command): Command {
$commands = array_unique($this->all(), SORT_REGULAR); // Remove commands duplicate caused by aliases
foreach ($commands as $command) {
foreach ($this->getAutoPrefixManagers() as $autoPrefixManager) { foreach ($this->getAutoPrefixManagers() as $autoPrefixManager) {
if (($namesPrefix = $autoPrefixManager->getCommandPrefix($command)) !== null) { if (($namesPrefix = $autoPrefixManager->getCommandPrefix($command)) !== null) {
if (mb_strlen($namesPrefix) > 0) { if (mb_strlen($namesPrefix) > 0) {
@ -71,7 +73,6 @@ trait AutoPrefixApplication {
break; break;
} }
} }
} return $command;
return $this;
} }
} }
Loading…
Cancel
Save