diff --git a/app/Config/Autoload.php b/app/Config/Autoload.php index 76cd9263556e..784121fdd04d 100644 --- a/app/Config/Autoload.php +++ b/app/Config/Autoload.php @@ -40,7 +40,7 @@ class Autoload extends AutoloadConfig * @var array|string> */ public $psr4 = [ - APP_NAMESPACE => APPPATH, + APP_NAMESPACE => [APPPATH, TESTPATH . 'app'], ]; /** diff --git a/system/Autoloader/Autoloader.php b/system/Autoloader/Autoloader.php index 4c6fed350fe2..c6ddd84f2efc 100644 --- a/system/Autoloader/Autoloader.php +++ b/system/Autoloader/Autoloader.php @@ -63,14 +63,14 @@ class Autoloader /** * Stores namespaces as key, and path as values. * - * @var array> + * @var array> */ protected $prefixes = []; /** * Stores class name as key, and path as values. * - * @var array + * @var array */ protected $classmap = []; @@ -215,7 +215,8 @@ public function addNamespace($namespace, ?string $path = null) * * If a prefix param is set, returns only paths to the given prefix. * - * @return array + * @return array>|list + * @phpstan-return ($prefix is null ? array> : list) */ public function getNamespace(?string $prefix = null) { diff --git a/system/Autoloader/FileLocator.php b/system/Autoloader/FileLocator.php index 37a13e159557..f176bd6c0d7e 100644 --- a/system/Autoloader/FileLocator.php +++ b/system/Autoloader/FileLocator.php @@ -175,6 +175,8 @@ public function getClassname(string $file): string * 'app/Modules/foo/Config/Routes.php', * 'app/Modules/bar/Config/Routes.php', * ] + * + * @return list */ public function search(string $path, string $ext = 'php', bool $prioritizeApp = true): array { @@ -203,7 +205,7 @@ public function search(string $path, string $ext = 'php', bool $prioritizeApp = } // Remove any duplicates - return array_unique($foundPaths); + return array_values(array_unique($foundPaths)); } /** @@ -237,7 +239,7 @@ protected function getNamespaces() foreach ($this->autoloader->getNamespace() as $prefix => $paths) { foreach ($paths as $path) { if ($prefix === 'CodeIgniter') { - $system = [ + $system[] = [ 'prefix' => $prefix, 'path' => rtrim($path, '\\/') . DIRECTORY_SEPARATOR, ]; @@ -252,9 +254,7 @@ protected function getNamespaces() } } - $namespaces[] = $system; - - return $namespaces; + return array_merge($namespaces, $system); } /** diff --git a/system/Commands/Generators/TestGenerator.php b/system/Commands/Generators/TestGenerator.php index d09ecda5ae83..60863886e121 100644 --- a/system/Commands/Generators/TestGenerator.php +++ b/system/Commands/Generators/TestGenerator.php @@ -14,7 +14,9 @@ namespace CodeIgniter\Commands\Generators; use CodeIgniter\CLI\BaseCommand; +use CodeIgniter\CLI\CLI; use CodeIgniter\CLI\GeneratorTrait; +use Config\Services; /** * Generates a skeleton command file. @@ -66,7 +68,8 @@ class TestGenerator extends BaseCommand * @var array */ protected $options = [ - '--force' => 'Force overwrite existing file.', + '--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".', + '--force' => 'Force overwrite existing file.', ]; /** @@ -76,9 +79,70 @@ public function run(array $params) { $this->component = 'Test'; $this->template = 'test.tpl.php'; - $this->namespace = 'Tests'; $this->classNameLang = 'CLI.generator.className.test'; $this->generateClass($params); } + + /** + * Builds the test file path from the class name. + * + * @param string $class namespaced classname or namespaced view. + */ + 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); + } + + /** + * Returns test file path for the namespace. + */ + private function searchTestFilePath(string $namespace): ?string + { + $bases = Services::autoloader()->getNamespace($namespace); + + $base = null; + + foreach ($bases as $candidate) { + if (str_contains($candidate, '/tests/')) { + $base = $candidate; + + break; + } + } + + return $base; + } } diff --git a/system/Config/AutoloadConfig.php b/system/Config/AutoloadConfig.php index f70d07155613..3866ce760a56 100644 --- a/system/Config/AutoloadConfig.php +++ b/system/Config/AutoloadConfig.php @@ -91,7 +91,7 @@ class AutoloadConfig * @var array */ protected $corePsr4 = [ - 'CodeIgniter' => SYSTEMPATH, + 'CodeIgniter' => [SYSTEMPATH, TESTPATH . 'system'], 'Config' => APPPATH . 'Config', 'Tests' => ROOTPATH . 'tests', ]; @@ -106,7 +106,7 @@ class AutoloadConfig * searched for within one or more directories as they would if they * were being autoloaded through a namespace. * - * @var array + * @var array */ protected $coreClassmap = [ AbstractLogger::class => SYSTEMPATH . 'ThirdParty/PSR/Log/AbstractLogger.php', diff --git a/tests/system/Autoloader/AutoloaderTest.php b/tests/system/Autoloader/AutoloaderTest.php index fbefbe20b0ef..6a1b496b05c9 100644 --- a/tests/system/Autoloader/AutoloaderTest.php +++ b/tests/system/Autoloader/AutoloaderTest.php @@ -97,13 +97,13 @@ public function testInitializeTwice(): void $loader->initialize(new Autoload(), new Modules()); $ns = $loader->getNamespace(); - $this->assertCount(1, $ns['App']); + $this->assertCount(2, $ns['App']); $this->assertSame('ROOTPATH/app', clean_path($ns['App'][0])); $loader->initialize(new Autoload(), new Modules()); $ns = $loader->getNamespace(); - $this->assertCount(1, $ns['App']); + $this->assertCount(2, $ns['App']); $this->assertSame('ROOTPATH/app', clean_path($ns['App'][0])); } diff --git a/tests/system/Commands/TestGeneratorTest.php b/tests/system/Commands/TestGeneratorTest.php index 8ffe89c3de55..888d8b03d1a1 100644 --- a/tests/system/Commands/TestGeneratorTest.php +++ b/tests/system/Commands/TestGeneratorTest.php @@ -41,6 +41,6 @@ protected function tearDown(): void public function testGenerateTest(): void { command('make:test Foo/Bar'); - $this->assertFileExists(ROOTPATH . 'tests/Foo/Bar.php'); + $this->assertFileExists(ROOTPATH . 'tests/app/Foo/Bar.php'); } } diff --git a/user_guide_src/source/cli/cli_generators.rst b/user_guide_src/source/cli/cli_generators.rst index 2f8d7c9d55bf..ff539b3c848d 100644 --- a/user_guide_src/source/cli/cli_generators.rst +++ b/user_guide_src/source/cli/cli_generators.rst @@ -248,6 +248,7 @@ Argument: Options: ======== +* ``--namespace``: Set the root namespace. Defaults to value of ``APP_NAMESPACE``. * ``--force``: Set this flag to overwrite existing files on destination. make:migration