AutoDiscoverySpot → AutoDiscoveryDirectory + manage AutoPrefix

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

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

@ -2,10 +2,12 @@
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
*/
@ -47,31 +49,30 @@ 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 {
$commands = array_unique($this->all(), SORT_REGULAR); // Remove commands duplicate caused by aliases
foreach ($commands as $command) {
foreach ($this->getAutoPrefixManagers() as $autoPrefixManager) {
if (($namesPrefix = $autoPrefixManager->getCommandPrefix($command)) !== null) {
if (mb_strlen($namesPrefix) > 0) {
$namesPrefix .= ':';
}
$command->setName($namesPrefix . $command->getName());
protected function applyAutoPrefixOnCommand (Command $command): Command {
foreach ($this->getAutoPrefixManagers() as $autoPrefixManager) {
if (($namesPrefix = $autoPrefixManager->getCommandPrefix($command)) !== null) {
if (mb_strlen($namesPrefix) > 0) {
$namesPrefix .= ':';
}
$aliases = $command->getAliases();
foreach ($aliases as &$alias) {
$alias = $namesPrefix . $alias;
}
$command->setAliases($aliases);
$command->setName($namesPrefix . $command->getName());
break;
$aliases = $command->getAliases();
foreach ($aliases as &$alias) {
$alias = $namesPrefix . $alias;
}
$command->setAliases($aliases);
break;
}
}
return $this;
return $command;
}
}
Loading…
Cancel
Save