Skip to content

Commit 4e60d95

Browse files
authored
Merge pull request #396 from dereuromark/default-table
Allow adding defaultTable for non existing models in controllers.
2 parents 73a512b + 28a13e0 commit 4e60d95

File tree

3 files changed

+295
-0
lines changed

3 files changed

+295
-0
lines changed
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
<?php
2+
3+
namespace IdeHelper\Illuminator\Task;
4+
5+
use Cake\Core\Configure;
6+
use IdeHelper\Annotator\Traits\DocBlockTrait;
7+
use IdeHelper\Annotator\Traits\FileTrait;
8+
use PHP_CodeSniffer\Files\File;
9+
10+
/**
11+
* Adds $defaultTable = '' property to controllers that don't have a corresponding table class.
12+
*/
13+
class ControllerDefaultTableTask extends AbstractTask {
14+
15+
use FileTrait;
16+
use DocBlockTrait;
17+
18+
/**
19+
* @param string $path
20+
* @return bool
21+
*/
22+
public function shouldRun(string $path): bool {
23+
$className = pathinfo($path, PATHINFO_FILENAME);
24+
if (!str_ends_with($className, 'Controller')) {
25+
return false;
26+
}
27+
28+
if ($className === 'AppController' || preg_match('#[a-z0-9]AppController$#', $className)) {
29+
return false;
30+
}
31+
32+
if (!str_contains($path, DS . 'Controller' . DS)) {
33+
return false;
34+
}
35+
36+
return true;
37+
}
38+
39+
/**
40+
* @param string $content
41+
* @param string $path Path to file.
42+
* @return string
43+
*/
44+
public function run(string $content, string $path): string {
45+
if (preg_match('/\bprotected \?string \$defaultTable\b/', $content)) {
46+
return $content;
47+
}
48+
49+
$className = pathinfo($path, PATHINFO_FILENAME);
50+
$namespace = $this->extractNamespace($content);
51+
if (!$namespace) {
52+
return $content;
53+
}
54+
55+
$baseNamespace = $this->detectPluginFromNamespace($namespace);
56+
57+
$modelName = substr($className, 0, -10);
58+
$modelClassName = str_replace('/', '\\', $baseNamespace) . '\\Model\\Table\\' . $modelName . 'Table';
59+
60+
if (class_exists($modelClassName)) {
61+
return $content;
62+
}
63+
64+
$file = $this->getFile('', $content);
65+
$classIndex = $file->findNext(T_CLASS, 0);
66+
if (!$classIndex) {
67+
return $content;
68+
}
69+
70+
return $this->addDefaultTableProperty($file, $classIndex, $content);
71+
}
72+
73+
/**
74+
* @param \PHP_CodeSniffer\Files\File $file
75+
* @param int $classIndex
76+
* @param string $content
77+
* @return string
78+
*/
79+
protected function addDefaultTableProperty(File $file, int $classIndex, string $content): string {
80+
$tokens = $file->getTokens();
81+
82+
$openBraceIndex = $tokens[$classIndex]['scope_opener'];
83+
$closeBraceIndex = $tokens[$classIndex]['scope_closer'];
84+
if (!$openBraceIndex || !$closeBraceIndex) {
85+
return $content;
86+
}
87+
88+
$indentation = Configure::read('IdeHelper.illuminatorIndentation') ?? "\t";
89+
90+
$fixer = $this->getFixer($file);
91+
92+
$property = PHP_EOL . PHP_EOL . $indentation . 'protected ?string $defaultTable = \'\';';
93+
94+
$fixer->addContent($openBraceIndex, $property);
95+
96+
return $fixer->getContents();
97+
}
98+
99+
/**
100+
* @param \PHP_CodeSniffer\Files\File $file
101+
* @param int $index
102+
* @return string
103+
*/
104+
protected function detectIndentation(File $file, int $index): string {
105+
$tokens = $file->getTokens();
106+
107+
$line = $tokens[$index]['line'];
108+
$firstOfLine = $index;
109+
while ($firstOfLine - 1 >= 0 && $tokens[$firstOfLine - 1]['line'] === $line) {
110+
$firstOfLine--;
111+
}
112+
113+
$whitespace = '';
114+
for ($i = $firstOfLine; $i < $index; $i++) {
115+
if ($tokens[$i]['code'] === T_WHITESPACE) {
116+
$whitespace .= $tokens[$i]['content'];
117+
}
118+
}
119+
120+
return $whitespace;
121+
}
122+
123+
/**
124+
* @param string $content
125+
* @return string|null
126+
*/
127+
protected function extractNamespace(string $content): ?string {
128+
if (preg_match('/^namespace\s+([^;]+);/m', $content, $matches)) {
129+
return $matches[1];
130+
}
131+
132+
return null;
133+
}
134+
135+
/**
136+
* @param string $namespace
137+
* @return string
138+
*/
139+
protected function detectPluginFromNamespace(string $namespace): string {
140+
$plugin = $this->getConfig(static::CONFIG_PLUGIN);
141+
if ($plugin) {
142+
return $plugin;
143+
}
144+
145+
$parts = explode('\\', $namespace);
146+
if (count($parts) >= 2 && $parts[1] === 'Controller') {
147+
return $parts[0];
148+
}
149+
150+
return Configure::read('App.namespace', 'App');
151+
}
152+
153+
}

src/Illuminator/TaskCollection.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Cake\Core\Configure;
77
use IdeHelper\Console\Io;
88
use IdeHelper\Illuminator\Task\AbstractTask;
9+
use IdeHelper\Illuminator\Task\ControllerDefaultTableTask;
910
use IdeHelper\Illuminator\Task\EntityFieldTask;
1011
use InvalidArgumentException;
1112
use RuntimeException;
@@ -30,6 +31,7 @@ class TaskCollection {
3031
* @var array<class-string<\IdeHelper\Illuminator\Task\AbstractTask>, class-string<\IdeHelper\Illuminator\Task\AbstractTask>>
3132
*/
3233
protected array $defaultTasks = [
34+
ControllerDefaultTableTask::class => ControllerDefaultTableTask::class,
3335
EntityFieldTask::class => EntityFieldTask::class,
3436
];
3537

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<?php
2+
3+
namespace IdeHelper\Test\TestCase\Illuminator\Task;
4+
5+
use Cake\Console\ConsoleIo;
6+
use Cake\TestSuite\TestCase;
7+
use IdeHelper\Annotator\AbstractAnnotator;
8+
use IdeHelper\Console\Io;
9+
use IdeHelper\Illuminator\Task\ControllerDefaultTableTask;
10+
use Shim\TestSuite\ConsoleOutput;
11+
12+
class ControllerDefaultTableTaskTest extends TestCase {
13+
14+
protected ConsoleOutput $out;
15+
16+
protected ConsoleOutput $err;
17+
18+
protected Io $io;
19+
20+
/**
21+
* @return void
22+
*/
23+
protected function setUp(): void {
24+
parent::setUp();
25+
26+
$this->out = new ConsoleOutput();
27+
$this->err = new ConsoleOutput();
28+
$consoleIo = new ConsoleIo($this->out, $this->err);
29+
$this->io = new Io($consoleIo);
30+
}
31+
32+
/**
33+
* @return void
34+
*/
35+
public function testShouldRun() {
36+
$task = $this->_getTask();
37+
38+
$result = $task->shouldRun('src/Controller/NoTableController.php');
39+
$this->assertTrue($result);
40+
41+
$result = $task->shouldRun('src/Controller/AppController.php');
42+
$this->assertFalse($result);
43+
44+
$result = $task->shouldRun('src/Model/Table/Wheels.php');
45+
$this->assertFalse($result);
46+
}
47+
48+
/**
49+
* @return void
50+
*/
51+
public function testRunWithExistingTable() {
52+
$task = $this->_getTask();
53+
54+
$path = APP . 'Controller/FoosController.php';
55+
$this->assertFileExists($path);
56+
57+
$content = file_get_contents($path);
58+
$result = $task->run($content, $path);
59+
60+
// Should not add defaultTable if table exists
61+
$this->assertSame($content, $result);
62+
}
63+
64+
/**
65+
* @return void
66+
*/
67+
public function testRunWithoutTable() {
68+
$task = $this->_getTask();
69+
70+
$content = <<<'PHP'
71+
<?php
72+
namespace App\Controller;
73+
74+
use Cake\Controller\Controller;
75+
76+
class NoTableController extends Controller {
77+
}
78+
79+
PHP;
80+
81+
$expected = <<<'PHP'
82+
<?php
83+
namespace App\Controller;
84+
85+
use Cake\Controller\Controller;
86+
87+
class NoTableController extends Controller {
88+
89+
protected ?string $defaultTable = '';
90+
}
91+
92+
PHP;
93+
94+
$path = 'src/Controller/NoTableController.php';
95+
$result = $task->run($content, $path);
96+
97+
$this->assertTextEquals($expected, $result);
98+
$this->assertStringContainsString("protected ?string \$defaultTable = '';", $result);
99+
}
100+
101+
/**
102+
* @return void
103+
*/
104+
public function testRunWithExistingProperty() {
105+
$task = $this->_getTask();
106+
107+
$content = <<<'PHP'
108+
<?php
109+
namespace App\Controller;
110+
111+
use Cake\Controller\Controller;
112+
113+
class CustomController extends Controller {
114+
115+
protected ?string $defaultTable = 'CustomTable';
116+
}
117+
118+
PHP;
119+
120+
$path = 'src/Controller/CustomController.php';
121+
$result = $task->run($content, $path);
122+
123+
// Should not add if property already exists
124+
$this->assertSame($content, $result);
125+
}
126+
127+
/**
128+
* @param array $params
129+
* @return \IdeHelper\Illuminator\Task\ControllerDefaultTableTask
130+
*/
131+
protected function _getTask(array $params = []) {
132+
$params += [
133+
AbstractAnnotator::CONFIG_DRY_RUN => true,
134+
AbstractAnnotator::CONFIG_VERBOSE => true,
135+
];
136+
137+
return new ControllerDefaultTableTask($params);
138+
}
139+
140+
}

0 commit comments

Comments
 (0)