Skip to content

Commit 61df4df

Browse files
authored
Merge pull request #7481 from paulbalandan/standard-make-cell
fix: standardize behavior of `make:cell` and `Cells`
2 parents 509b38a + 81f280f commit 61df4df

File tree

10 files changed

+126
-51
lines changed

10 files changed

+126
-51
lines changed

app/Config/Generators.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class Generators extends BaseConfig
2727
*/
2828
public array $views = [
2929
'make:cell' => 'CodeIgniter\Commands\Generators\Views\cell.tpl.php',
30+
'make:cell_view' => 'CodeIgniter\Commands\Generators\Views\cell_view.tpl.php',
3031
'make:command' => 'CodeIgniter\Commands\Generators\Views\command.tpl.php',
3132
'make:config' => 'CodeIgniter\Commands\Generators\Views\config.tpl.php',
3233
'make:controller' => 'CodeIgniter\Commands\Generators\Views\controller.tpl.php',

system/CLI/GeneratorTrait.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,15 +235,15 @@ protected function qualifyClassName(): string
235235
$component = singular($this->component);
236236

237237
/**
238-
* @see https://regex101.com/r/a5KNCR/1
238+
* @see https://regex101.com/r/a5KNCR/2
239239
*/
240-
$pattern = sprintf('/([a-z][a-z0-9_\/\\\\]+)(%s)/i', $component);
240+
$pattern = sprintf('/([a-z][a-z0-9_\/\\\\]+)(%s)$/i', $component);
241241

242242
if (preg_match($pattern, $class, $matches) === 1) {
243243
$class = $matches[1] . ucfirst($matches[2]);
244244
}
245245

246-
if ($this->enabledSuffixing && $this->getOption('suffix') && ! strripos($class, $component)) {
246+
if ($this->enabledSuffixing && $this->getOption('suffix') && preg_match($pattern, $class) !== 1) {
247247
$class .= ucfirst($component);
248248
}
249249

system/Commands/Generators/CellGenerator.php

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ class CellGenerator extends BaseCommand
6565
*/
6666
protected $options = [
6767
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',
68-
'--suffix' => 'Append the component title to the class name (e.g. User => UserCell).',
6968
'--force' => 'Force overwrite existing file.',
7069
];
7170

@@ -74,27 +73,26 @@ class CellGenerator extends BaseCommand
7473
*/
7574
public function run(array $params)
7675
{
77-
// Generate the Class first
78-
$this->component = 'Cell';
79-
$this->directory = 'Cells';
76+
$this->component = 'Cell';
77+
$this->directory = 'Cells';
78+
79+
$params = array_merge($params, ['suffix' => null]);
80+
8081
$this->template = 'cell.tpl.php';
8182
$this->classNameLang = 'CLI.generator.className.cell';
82-
8383
$this->generateClass($params);
8484

85-
// Generate the View
85+
$this->name = 'make:cell_view';
86+
$this->template = 'cell_view.tpl.php';
8687
$this->classNameLang = 'CLI.generator.viewName.cell';
8788

88-
// Form the view name
89-
$segments = explode('\\', $this->qualifyClassName());
90-
91-
$view = array_pop($segments);
92-
$view = decamelize($view);
93-
$segments[] = $view;
94-
$view = implode('\\', $segments);
89+
$className = $this->qualifyClassName();
90+
$viewName = decamelize(class_basename($className));
91+
$viewName = preg_replace('/([a-z][a-z0-9_\/\\\\]+)(_cell)$/i', '$1', $viewName) ?? $viewName;
92+
$namespace = substr($className, 0, strrpos($className, '\\') + 1);
9593

96-
$this->template = 'cell_view.tpl.php';
94+
$this->generateView($namespace . $viewName, $params);
9795

98-
$this->generateView($view, $params);
96+
return 0;
9997
}
10098
}

system/View/Cells/Cell.php

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace CodeIgniter\View\Cells;
1313

1414
use CodeIgniter\Traits\PropertiesTrait;
15+
use LogicException;
1516
use ReflectionClass;
1617

1718
/**
@@ -64,37 +65,51 @@ public function setView(string $view)
6465
* from within the view, this method extracts $data into the
6566
* current scope and captures the output buffer instead of
6667
* relying on the view service.
68+
*
69+
* @throws LogicException
6770
*/
6871
final protected function view(?string $view, array $data = []): string
6972
{
7073
$properties = $this->getPublicProperties();
7174
$properties = $this->includeComputedProperties($properties);
7275
$properties = array_merge($properties, $data);
7376

74-
// If no view is specified, we'll try to guess it based on the class name.
75-
if (empty($view)) {
76-
// According to the docs, the name of the view file should be the
77-
// snake_cased version of the cell's class name, but for backward
78-
// compatibility, the name also accepts '_cell' being omitted.
79-
$ref = new ReflectionClass($this);
80-
$view = decamelize($ref->getShortName());
81-
$viewPath = dirname($ref->getFileName()) . DIRECTORY_SEPARATOR . $view . '.php';
82-
$view = is_file($viewPath) ? $viewPath : str_replace('_cell', '', $view);
77+
$view = (string) $view;
78+
79+
if ($view === '') {
80+
$viewName = decamelize(class_basename(static::class));
81+
$directory = dirname((new ReflectionClass($this))->getFileName()) . DIRECTORY_SEPARATOR;
82+
83+
$possibleView1 = $directory . substr($viewName, 0, strrpos($viewName, '_cell')) . '.php';
84+
$possibleView2 = $directory . $viewName . '.php';
8385
}
8486

85-
// Locate our view, preferring the directory of the class.
86-
if (! is_file($view)) {
87-
// Get the local pathname of the Cell
88-
$ref = new ReflectionClass($this);
89-
$view = dirname($ref->getFileName()) . DIRECTORY_SEPARATOR . $view . '.php';
87+
if ($view !== '' && ! is_file($view)) {
88+
$directory = dirname((new ReflectionClass($this))->getFileName()) . DIRECTORY_SEPARATOR;
89+
90+
$view = $directory . $view . '.php';
9091
}
9192

92-
return (function () use ($properties, $view): string {
93+
$candidateViews = array_filter(
94+
[$view, $possibleView1 ?? '', $possibleView2 ?? ''],
95+
static fn (string $path): bool => $path !== '' && is_file($path)
96+
);
97+
98+
if ($candidateViews === []) {
99+
throw new LogicException(sprintf(
100+
'Cannot locate the view file for the "%s" cell.',
101+
static::class
102+
));
103+
}
104+
105+
$foundView = current($candidateViews);
106+
107+
return (function () use ($properties, $foundView): string {
93108
extract($properties);
94109
ob_start();
95-
include $view;
110+
include $foundView;
96111

97-
return ob_get_clean() ?: '';
112+
return ob_get_clean();
98113
})();
99114
}
100115

tests/_support/View/Cells/BadCell.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
/**
4+
* This file is part of CodeIgniter 4 framework.
5+
*
6+
* (c) CodeIgniter Foundation <admin@codeigniter.com>
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace Tests\Support\View\Cells;
13+
14+
use CodeIgniter\View\Cells\Cell;
15+
16+
final class BadCell extends Cell
17+
{
18+
}

tests/system/Commands/CellGeneratorTest.php

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,35 +46,54 @@ protected function getFileContents(string $filepath): string
4646
return file_get_contents($filepath) ?: '';
4747
}
4848

49-
public function testGenerateCell()
49+
public function testGenerateCell(): void
5050
{
5151
command('make:cell RecentCell');
5252

5353
// Check the class was generated
5454
$file = APPPATH . 'Cells/RecentCell.php';
55+
$this->assertStringContainsString('File created: ' . clean_path($file), $this->getStreamFilterBuffer());
5556
$this->assertFileExists($file);
56-
$contents = $this->getFileContents($file);
57-
$this->assertStringContainsString('class RecentCell extends Cell', $contents);
57+
$this->assertStringContainsString('class RecentCell extends Cell', $this->getFileContents($file));
5858

5959
// Check the view was generated
60-
$file = APPPATH . 'Cells/recent_cell.php';
61-
$this->assertStringContainsString('File created: ', $this->getStreamFilterBuffer());
60+
$file = APPPATH . 'Cells/recent.php';
61+
$this->assertStringContainsString('File created: ' . clean_path($file), $this->getStreamFilterBuffer());
6262
$this->assertFileExists($file);
63+
$this->assertSame("<div>\n <!-- Your HTML here -->\n</div>\n", $this->getFileContents($file));
6364
}
6465

65-
public function testGenerateCellSimpleName()
66+
public function testGenerateCellSimpleName(): void
6667
{
6768
command('make:cell Another');
6869

6970
// Check the class was generated
70-
$file = APPPATH . 'Cells/Another.php';
71+
$file = APPPATH . 'Cells/AnotherCell.php';
72+
$this->assertStringContainsString('File created: ' . clean_path($file), $this->getStreamFilterBuffer());
7173
$this->assertFileExists($file);
72-
$contents = $this->getFileContents($file);
73-
$this->assertStringContainsString('class Another extends Cell', $contents);
74+
$this->assertStringContainsString('class AnotherCell extends Cell', $this->getFileContents($file));
7475

7576
// Check the view was generated
7677
$file = APPPATH . 'Cells/another.php';
77-
$this->assertStringContainsString('File created: ', $this->getStreamFilterBuffer());
78+
$this->assertStringContainsString('File created: ' . clean_path($file), $this->getStreamFilterBuffer());
7879
$this->assertFileExists($file);
80+
$this->assertSame("<div>\n <!-- Your HTML here -->\n</div>\n", $this->getFileContents($file));
81+
}
82+
83+
public function testGenerateCellWithCellInBetween(): void
84+
{
85+
command('make:cell PippoCellular');
86+
87+
// Check the class was generated
88+
$file = APPPATH . 'Cells/PippoCellularCell.php';
89+
$this->assertStringContainsString('File created: ' . clean_path($file), $this->getStreamFilterBuffer());
90+
$this->assertFileExists($file);
91+
$this->assertStringContainsString('class PippoCellularCell extends Cell', $this->getFileContents($file));
92+
93+
// Check the view was generated
94+
$file = APPPATH . 'Cells/pippo_cellular.php';
95+
$this->assertStringContainsString('File created: ' . clean_path($file), $this->getStreamFilterBuffer());
96+
$this->assertFileExists($file);
97+
$this->assertSame("<div>\n <!-- Your HTML here -->\n</div>\n", $this->getFileContents($file));
7998
}
8099
}

tests/system/View/ControlledCellTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313

1414
use CodeIgniter\Test\CIUnitTestCase;
1515
use CodeIgniter\View\Exceptions\ViewException;
16+
use LogicException;
1617
use Tests\Support\View\Cells\AdditionCell;
1718
use Tests\Support\View\Cells\AwesomeCell;
19+
use Tests\Support\View\Cells\BadCell;
1820
use Tests\Support\View\Cells\ColorsCell;
1921
use Tests\Support\View\Cells\GreetingCell;
2022
use Tests\Support\View\Cells\ListerCell;
@@ -65,6 +67,14 @@ public function testCellThroughRenderMethodWithExtraData()
6567
$this->assertStringContainsString('42, 23, 16, 15, 8, 4', $result);
6668
}
6769

70+
public function testCellThrowsExceptionWhenCannotFindTheViewFile()
71+
{
72+
$this->expectException(LogicException::class);
73+
$this->expectExceptionMessage('Cannot locate the view file for the "Tests\\Support\\View\\Cells\\BadCell" cell.');
74+
75+
view_cell(BadCell::class);
76+
}
77+
6878
public function testCellWithParameters()
6979
{
7080
$result = view_cell(GreetingCell::class, 'greeting=Hi, name=CodeIgniter');

user_guide_src/source/changelogs/v4.3.5.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ Message Changes
2424
Changes
2525
*******
2626

27+
- **make:cell** When creating a new cell, the controller would always have the ``Cell`` suffixed to the class name.
28+
For the view file, the final ``_cell`` is always removed.
29+
- **Cells** For compatibility with previous versions, view filenames ending with ``_cell`` can still be
30+
located by the ``Cell`` as long as auto-detection of view file is enabled (via setting the ``$view`` property
31+
to an empty string).
32+
2733
Deprecations
2834
************
2935

@@ -34,6 +40,8 @@ Bugs Fixed
3440
**********
3541

3642
- **Validation:** Fixed a bug where a closure used in combination with ``permit_empty`` or ``if_exist`` rules was causing an error.
43+
- **make:cell** Fixed generating view files as classes.
44+
- **make:cell** Fixed treatment of single word class input for case-insensitive OS.
3745

3846
See the repo's
3947
`CHANGELOG.md <https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHANGELOG.md>`_

user_guide_src/source/cli/cli_generators.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,11 @@ Usage:
5959

6060
Argument:
6161
=========
62-
* ``name``: The name of the cell class. It should be in PascalCase.
62+
* ``name``: The name of the cell class. It should be in PascalCase. **[REQUIRED]**
6363

6464
Options:
6565
========
6666
* ``--namespace``: Set the root namespace. Defaults to value of ``APP_NAMESPACE``.
67-
* ``--suffix``: Append the component suffix to the generated class name.
6867
* ``--force``: Set this flag to overwrite existing files on destination.
6968

7069
make:command

user_guide_src/source/outgoing/view_cells.rst

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,14 @@ Controlled Cells
8080

8181
.. versionadded:: 4.3.0
8282

83-
Controlled Cells have two primary goals: to make it as fast as possible to build the cell, and provide additional logic and flexibility to your views, if they need it. The class must extend ``CodeIgniter\View\Cells\Cell``. They should have a view file in the same folder. By convention the class name should be PascalCase and the view should be the snake_cased version of the class name. So, for example, if you have a ``MyCell`` class, the view file should be ``my_cell.php``.
83+
Controlled cells have two primary goals: to make it as fast as possible to build the cell, and provide additional logic and
84+
flexibility to your views, if they need it. The class must extend ``CodeIgniter\View\Cells\Cell``. They should have a view file
85+
in the same folder. By convention, the class name should be in PascalCase suffixed with ``Cell`` and the view should be
86+
the snake_cased version of the class name, without the suffix. For example, if you have a ``MyCell`` class, the view file
87+
should be ``my.php``.
88+
89+
.. note:: Prior to v4.3.5, the generated view file ends with ``_cell.php``. Though v4.3.5 and newer will generate view files
90+
without the ``_cell`` suffix, existing view files will still be located and loaded.
8491

8592
Creating a Controlled Cell
8693
==========================
@@ -99,7 +106,7 @@ At the most basic level, all you need to implement within the class are public p
99106
public $message;
100107
}
101108

102-
// app/Cells/alert_message_cell.php
109+
// app/Cells/alert_message.php
103110
<div class="alert alert-<?= esc($type, 'attr') ?>">
104111
<?= esc($message) ?>
105112
</div>
@@ -199,7 +206,7 @@ If you need to perform additional logic for one or more properties you can use c
199206
}
200207
}
201208

202-
// app/Cells/alert_message_cell.php
209+
// app/Cells/alert_message.php
203210
<div>
204211
<p>type - <?= esc($type) ?></p>
205212
<p>message - <?= esc($message) ?></p>
@@ -230,7 +237,7 @@ Sometimes you need to perform additional logic for the view, but you don't want
230237
}
231238
}
232239

233-
// app/Cells/recent_posts_cell.php
240+
// app/Cells/recent_posts.php
234241
<ul>
235242
<?php foreach ($posts as $post): ?>
236243
<li><?= $this->linkPost($post) ?></li>

0 commit comments

Comments
 (0)