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; } }