Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions system/CLI/AbstractGeneratorCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,14 @@ protected function provideGeneratorOptions(): void
$this->addNamespaceOption()->addSuffixOption()->addForceOption();
}

final protected function addNamespaceOption(): static
final protected function addNamespaceOption(string $default = APP_NAMESPACE): static
{
return $this->addOption(new Option(
name: 'namespace',
shortcut: 'n',
description: 'Set the root namespace.',
requiresValue: true,
default: APP_NAMESPACE,
default: $default,
));
}

Expand Down Expand Up @@ -259,28 +259,34 @@ protected function buildContent(string $class): string
protected function buildPath(string $class): string
{
$namespace = $this->getNamespace();
$basePath = $this->getBasePath($namespace);

$bases = service('autoloader')->getNamespace($namespace);
$base = reset($bases);

if ($base === false || $base === '') {
if ($basePath === null) {
CLI::error(lang('CLI.namespaceNotDefined', [$namespace]));

return '';
}

$realpath = realpath($base);
$base = ($realpath !== false) ? $realpath : $base;
$realpath = realpath($basePath);
$basePath = ($realpath !== false) ? $realpath : $basePath;

$prefix = $namespace . '\\';
$relative = str_starts_with($class, $prefix) ? substr($class, strlen($prefix)) : $class;

$file = $base . DIRECTORY_SEPARATOR
$file = $basePath . DIRECTORY_SEPARATOR
. str_replace('\\', DIRECTORY_SEPARATOR, trim($relative, '\\')) . '.php';

return dirname($file) . DIRECTORY_SEPARATOR . $this->basename($file);
}

/**
* Returns the directory registered for the namespace in the autoloader, or `null` when it is not defined.
*/
protected function getBasePath(string $namespace): ?string
{
return service('autoloader')->getNamespace($namespace)[0] ?? null;
}

/**
* Gets the root namespace from the attribute override or the `--namespace` option.
*/
Expand Down Expand Up @@ -321,7 +327,7 @@ private function generate(string $class): int
*/
private function generateFile(string $target, string $content): int
{
if ($this->getNamespace() === 'CodeIgniter') {
if (str_starts_with($target, SYSTEMPATH)) {
CLI::write(lang('CLI.generator.usingCINamespace'), 'yellow');

if (
Expand Down
190 changes: 39 additions & 151 deletions system/Commands/Generators/TestGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,177 +13,65 @@

namespace CodeIgniter\Commands\Generators;

use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use CodeIgniter\CLI\GeneratorTrait;

/**
* Generates a skeleton command file.
*/
class TestGenerator extends BaseCommand
use CodeIgniter\CLI\AbstractGeneratorCommand;
use CodeIgniter\CLI\Attributes\Command;
use CodeIgniter\CLI\Attributes\GeneratorCommand;

#[Command(name: 'make:test', description: 'Generates a new test file.', group: 'Generators')]
#[GeneratorCommand(
component: 'Test',
template: 'test.tpl.php',
classNameLang: 'CLI.generator.className.test',
)]
class TestGenerator extends AbstractGeneratorCommand
{
use GeneratorTrait;

/**
* The Command's Group
*
* @var string
*/
protected $group = 'Generators';

/**
* The Command's Name
*
* @var string
*/
protected $name = 'make:test';

/**
* The Command's Description
*
* @var string
*/
protected $description = 'Generates a new test file.';

/**
* The Command's Usage
*
* @var string
*/
protected $usage = 'make:test <name> [options]';

/**
* The Command's Arguments
*
* @var array<string, string>
*/
protected $arguments = [
'name' => 'The test class name.',
];

/**
* The Command's Options
*
* @var array<string, string>
*/
protected $options = [
'--namespace' => 'Set root namespace. Default: "Tests".',
'--force' => 'Force overwrite existing file.',
];

/**
* Actually execute a command.
*/
public function run(array $params)
{
// Ensure tests are always suffixed with 'Test'
$params['suffix'] = null;

$this->component = 'Test';
$this->template = 'test.tpl.php';

$this->classNameLang = 'CLI.generator.className.test';
private const DEFAULT_NAMESPACE = 'Tests';

$autoload = service('autoloader');
$autoload->addNamespace('CodeIgniter', TESTPATH . 'system');
$autoload->addNamespace('Tests', ROOTPATH . 'tests');
protected function provideGeneratorOptions(): void
{
$this->addNamespaceOption(self::DEFAULT_NAMESPACE)->addForceOption();
}

$this->generateClass($params);
protected function initialize(array &$arguments, array &$options): void
{
$autoloader = service('autoloader');
$autoloader->addNamespace('CodeIgniter', TESTPATH . 'system');
$autoloader->addNamespace(self::DEFAULT_NAMESPACE, ROOTPATH . 'tests');
}

return EXIT_SUCCESS;
protected function shouldAppendSuffix(): bool
{
return true;
}

/**
* Gets the namespace from input or the default namespace.
*/
protected function getNamespace(): string
{
if ($this->namespace !== null) {
return $this->namespace;
if ($this->hasUnboundOption('namespace')) {
return parent::getNamespace();
}

if ($this->getOption('namespace') !== null) {
return trim(
str_replace(
'/',
'\\',
$this->getOption('namespace'),
),
'\\',
);
}
helper('inflector');

$class = $this->normalizeInputClassName();
$classPaths = explode('\\', $class);
$name = $this->getValidatedArgument('name');
$segments = array_map(pascalize(...), explode('\\', str_replace('/', '\\', $name)));
$autoloader = service('autoloader');

$namespaces = service('autoloader')->getNamespace();
while ($segments !== []) {
array_pop($segments);

while ($classPaths !== []) {
array_pop($classPaths);
$namespace = implode('\\', $classPaths);
$namespace = implode('\\', $segments);

foreach (array_keys($namespaces) as $prefix) {
if ($prefix === $namespace) {
// The input classname is FQCN, and use the namespace.
return $namespace;
}
if ($namespace !== '' && $autoloader->getNamespace($namespace) !== []) {
return $namespace;
}
}

return 'Tests';
}

/**
* Builds the test file path from the class name.
*
* @param string $class namespaced classname.
*/
protected function buildPath(string $class): string
{
$namespace = $this->getNamespace();

$base = $this->searchTestFilePath($namespace);

if ($base === null) {
CLI::error(
lang('CLI.namespaceNotDefined', [$namespace]),
'light_gray',
'red',
);
CLI::newLine();

return '';
}

$realpath = realpath($base);
$base = ($realpath !== false) ? $realpath : $base;

$file = $base . DIRECTORY_SEPARATOR
. str_replace(
'\\',
DIRECTORY_SEPARATOR,
trim(str_replace($namespace . '\\', '', $class), '\\'),
) . '.php';

return implode(
DIRECTORY_SEPARATOR,
array_slice(
explode(DIRECTORY_SEPARATOR, $file),
0,
-1,
),
) . DIRECTORY_SEPARATOR . $this->basename($file);
return self::DEFAULT_NAMESPACE;
}

/**
* Returns test file path for the namespace.
*/
private function searchTestFilePath(string $testNamespace): ?string
protected function getBasePath(string $namespace): ?string
{
/** @var list<non-empty-string> $testPaths */
$testPaths = service('autoloader')->getNamespace($testNamespace);

foreach ($testPaths as $candidate) {
foreach (service('autoloader')->getNamespace($namespace) as $candidate) {
if (str_contains($candidate, DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR)) {
return $candidate;
}
Expand Down
Loading
Loading