diff --git a/app/Config/Generators.php b/app/Config/Generators.php index c766d321648b..6566a31e851e 100644 --- a/app/Config/Generators.php +++ b/app/Config/Generators.php @@ -27,6 +27,7 @@ class Generators extends BaseConfig */ public array $views = [ 'make:cell' => 'CodeIgniter\Commands\Generators\Views\cell.tpl.php', + 'make:cell_view' => 'CodeIgniter\Commands\Generators\Views\cell_view.tpl.php', 'make:command' => 'CodeIgniter\Commands\Generators\Views\command.tpl.php', 'make:config' => 'CodeIgniter\Commands\Generators\Views\config.tpl.php', 'make:controller' => 'CodeIgniter\Commands\Generators\Views\controller.tpl.php', diff --git a/system/CLI/GeneratorTrait.php b/system/CLI/GeneratorTrait.php index 97f011f80ca6..21ae0c165d8a 100644 --- a/system/CLI/GeneratorTrait.php +++ b/system/CLI/GeneratorTrait.php @@ -235,15 +235,15 @@ protected function qualifyClassName(): string $component = singular($this->component); /** - * @see https://regex101.com/r/a5KNCR/1 + * @see https://regex101.com/r/a5KNCR/2 */ - $pattern = sprintf('/([a-z][a-z0-9_\/\\\\]+)(%s)/i', $component); + $pattern = sprintf('/([a-z][a-z0-9_\/\\\\]+)(%s)$/i', $component); if (preg_match($pattern, $class, $matches) === 1) { $class = $matches[1] . ucfirst($matches[2]); } - if ($this->enabledSuffixing && $this->getOption('suffix') && ! strripos($class, $component)) { + if ($this->enabledSuffixing && $this->getOption('suffix') && preg_match($pattern, $class) !== 1) { $class .= ucfirst($component); } diff --git a/system/Commands/Generators/CellGenerator.php b/system/Commands/Generators/CellGenerator.php index 38eedae63eb2..cdb819d869f9 100644 --- a/system/Commands/Generators/CellGenerator.php +++ b/system/Commands/Generators/CellGenerator.php @@ -65,7 +65,6 @@ class CellGenerator extends BaseCommand */ protected $options = [ '--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".', - '--suffix' => 'Append the component title to the class name (e.g. User => UserCell).', '--force' => 'Force overwrite existing file.', ]; @@ -74,27 +73,26 @@ class CellGenerator extends BaseCommand */ public function run(array $params) { - // Generate the Class first - $this->component = 'Cell'; - $this->directory = 'Cells'; + $this->component = 'Cell'; + $this->directory = 'Cells'; + + $params = array_merge($params, ['suffix' => null]); + $this->template = 'cell.tpl.php'; $this->classNameLang = 'CLI.generator.className.cell'; - $this->generateClass($params); - // Generate the View + $this->name = 'make:cell_view'; + $this->template = 'cell_view.tpl.php'; $this->classNameLang = 'CLI.generator.viewName.cell'; - // Form the view name - $segments = explode('\\', $this->qualifyClassName()); - - $view = array_pop($segments); - $view = decamelize($view); - $segments[] = $view; - $view = implode('\\', $segments); + $className = $this->qualifyClassName(); + $viewName = decamelize(class_basename($className)); + $viewName = preg_replace('/([a-z][a-z0-9_\/\\\\]+)(_cell)$/i', '$1', $viewName) ?? $viewName; + $namespace = substr($className, 0, strrpos($className, '\\') + 1); - $this->template = 'cell_view.tpl.php'; + $this->generateView($namespace . $viewName, $params); - $this->generateView($view, $params); + return 0; } } diff --git a/system/View/Cells/Cell.php b/system/View/Cells/Cell.php index 7cf1c5d7527c..962f89217861 100644 --- a/system/View/Cells/Cell.php +++ b/system/View/Cells/Cell.php @@ -12,6 +12,7 @@ namespace CodeIgniter\View\Cells; use CodeIgniter\Traits\PropertiesTrait; +use LogicException; use ReflectionClass; /** @@ -64,6 +65,8 @@ public function setView(string $view) * from within the view, this method extracts $data into the * current scope and captures the output buffer instead of * relying on the view service. + * + * @throws LogicException */ final protected function view(?string $view, array $data = []): string { @@ -71,30 +74,42 @@ final protected function view(?string $view, array $data = []): string $properties = $this->includeComputedProperties($properties); $properties = array_merge($properties, $data); - // If no view is specified, we'll try to guess it based on the class name. - if (empty($view)) { - // According to the docs, the name of the view file should be the - // snake_cased version of the cell's class name, but for backward - // compatibility, the name also accepts '_cell' being omitted. - $ref = new ReflectionClass($this); - $view = decamelize($ref->getShortName()); - $viewPath = dirname($ref->getFileName()) . DIRECTORY_SEPARATOR . $view . '.php'; - $view = is_file($viewPath) ? $viewPath : str_replace('_cell', '', $view); + $view = (string) $view; + + if ($view === '') { + $viewName = decamelize(class_basename(static::class)); + $directory = dirname((new ReflectionClass($this))->getFileName()) . DIRECTORY_SEPARATOR; + + $possibleView1 = $directory . substr($viewName, 0, strrpos($viewName, '_cell')) . '.php'; + $possibleView2 = $directory . $viewName . '.php'; } - // Locate our view, preferring the directory of the class. - if (! is_file($view)) { - // Get the local pathname of the Cell - $ref = new ReflectionClass($this); - $view = dirname($ref->getFileName()) . DIRECTORY_SEPARATOR . $view . '.php'; + if ($view !== '' && ! is_file($view)) { + $directory = dirname((new ReflectionClass($this))->getFileName()) . DIRECTORY_SEPARATOR; + + $view = $directory . $view . '.php'; } - return (function () use ($properties, $view): string { + $candidateViews = array_filter( + [$view, $possibleView1 ?? '', $possibleView2 ?? ''], + static fn (string $path): bool => $path !== '' && is_file($path) + ); + + if ($candidateViews === []) { + throw new LogicException(sprintf( + 'Cannot locate the view file for the "%s" cell.', + static::class + )); + } + + $foundView = current($candidateViews); + + return (function () use ($properties, $foundView): string { extract($properties); ob_start(); - include $view; + include $foundView; - return ob_get_clean() ?: ''; + return ob_get_clean(); })(); } diff --git a/tests/_support/View/Cells/BadCell.php b/tests/_support/View/Cells/BadCell.php new file mode 100644 index 000000000000..9571e2a07746 --- /dev/null +++ b/tests/_support/View/Cells/BadCell.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests\Support\View\Cells; + +use CodeIgniter\View\Cells\Cell; + +final class BadCell extends Cell +{ +} diff --git a/tests/system/Commands/CellGeneratorTest.php b/tests/system/Commands/CellGeneratorTest.php index 41b8bc51f031..274d7b7173eb 100644 --- a/tests/system/Commands/CellGeneratorTest.php +++ b/tests/system/Commands/CellGeneratorTest.php @@ -46,35 +46,54 @@ protected function getFileContents(string $filepath): string return file_get_contents($filepath) ?: ''; } - public function testGenerateCell() + public function testGenerateCell(): void { command('make:cell RecentCell'); // Check the class was generated $file = APPPATH . 'Cells/RecentCell.php'; + $this->assertStringContainsString('File created: ' . clean_path($file), $this->getStreamFilterBuffer()); $this->assertFileExists($file); - $contents = $this->getFileContents($file); - $this->assertStringContainsString('class RecentCell extends Cell', $contents); + $this->assertStringContainsString('class RecentCell extends Cell', $this->getFileContents($file)); // Check the view was generated - $file = APPPATH . 'Cells/recent_cell.php'; - $this->assertStringContainsString('File created: ', $this->getStreamFilterBuffer()); + $file = APPPATH . 'Cells/recent.php'; + $this->assertStringContainsString('File created: ' . clean_path($file), $this->getStreamFilterBuffer()); $this->assertFileExists($file); + $this->assertSame("
type - = esc($type) ?>
message - = esc($message) ?>
@@ -230,7 +237,7 @@ Sometimes you need to perform additional logic for the view, but you don't want } } - // app/Cells/recent_posts_cell.php + // app/Cells/recent_posts.php