Skip to content

Commit aba50df

Browse files
authored
Merge pull request #558 from paulbalandan/shield-model-spark
feat: add `shield:model` command
2 parents c7d2243 + 7e5c4a2 commit aba50df

File tree

5 files changed

+278
-4
lines changed

5 files changed

+278
-4
lines changed

docs/concepts.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,24 @@ on the standard Config class if nothing is found in the database.
2424

2525
## User Providers
2626

27-
You can use your own models to handle user persistence. Shield calls this the "User Provider" class. A default model
28-
is provided for you at `CodeIgniter\Shield\Models\UserModel`. You can change this in the `Config\Auth::$userProvider` setting.
29-
The only requirement is that your new class MUST extend the provided `UserModel`.
27+
You can use your own models to handle user persistence. Shield calls this the "User Provider" class.
28+
A default model is provided for you by the `CodeIgniter\Shield\Models\UserModel` class. You can change
29+
this in the `Config\Auth::$userProvider` setting. The only requirement is that your new class
30+
MUST extend the provided `UserModel`.
31+
32+
Shield has a CLI command to quickly create a custom `UserModel` class by running the following
33+
command in the terminal:
34+
35+
```console
36+
php spark shield:model UserModel
37+
```
38+
39+
The class name is optional. If none is provided, the generated class name would be `UserModel`.
40+
41+
You should set `Config\Auth::$userProvider` as follows:
3042

3143
```php
32-
public string $userProvider = UserModel::class;
44+
public string $userProvider = \App\Models\UserModel::class;
3345
```
3446

3547
## User Identities
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CodeIgniter\Shield\Commands\Generators;
6+
7+
use CodeIgniter\CLI\BaseCommand;
8+
use CodeIgniter\CLI\CLI;
9+
use CodeIgniter\CLI\GeneratorTrait;
10+
11+
/**
12+
* Generates a custom user model file.
13+
*/
14+
class UserModelGenerator extends BaseCommand
15+
{
16+
use GeneratorTrait;
17+
18+
/**
19+
* @var string
20+
*/
21+
protected $group = 'Shield';
22+
23+
/**
24+
* @var string
25+
*/
26+
protected $name = 'shield:model';
27+
28+
/**
29+
* @var string
30+
*/
31+
protected $description = 'Generate a new UserModel file.';
32+
33+
/**
34+
* @var string
35+
*/
36+
protected $usage = 'shield:model [<name>] [options]';
37+
38+
/**
39+
* @var array<string, string>
40+
*/
41+
protected $arguments = [
42+
'name' => 'The model class name. If not provided, this will default to `UserModel`.',
43+
];
44+
45+
/**
46+
* @var array<string, string>
47+
*/
48+
protected $options = [
49+
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',
50+
'--suffix' => 'Append the component title to the class name (e.g. User => UserModel).',
51+
'--force' => 'Force overwrite existing file.',
52+
];
53+
54+
/**
55+
* Actually execute the command.
56+
*/
57+
public function run(array $params): void
58+
{
59+
$this->component = 'Model';
60+
$this->directory = 'Models';
61+
$this->template = 'usermodel.tpl.php';
62+
63+
$this->classNameLang = 'CLI.generator.className.model';
64+
$this->setHasClassName(false);
65+
66+
$class = $params[0] ?? CLI::getSegment(2) ?? 'UserModel';
67+
68+
if (! $this->verifyChosenModelClassName($class, $params)) {
69+
CLI::error('Cannot use `ShieldUserModel` as class name as this conflicts with the parent class.', 'light_gray', 'red');
70+
71+
return; // @TODO when CI4 is at v4.3+, change this to `return 1;` to signify failing exit
72+
}
73+
74+
$params[0] = $class;
75+
76+
$this->execute($params);
77+
}
78+
79+
/**
80+
* The chosen class name should not conflict with the alias of the parent class.
81+
*/
82+
private function verifyChosenModelClassName(string $class, array $params): bool
83+
{
84+
helper('inflector');
85+
86+
if (array_key_exists('suffix', $params) && ! strripos($class, 'Model')) {
87+
$class .= 'Model';
88+
}
89+
90+
return strtolower(pascalize($class)) !== 'shieldusermodel';
91+
}
92+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<@php
2+
3+
declare(strict_types=1);
4+
5+
namespace {namespace};
6+
7+
use CodeIgniter\Shield\Models\UserModel as ShieldUserModel;
8+
9+
class {class} extends ShieldUserModel
10+
{
11+
protected function initialize(): void
12+
{
13+
$this->allowedFields = [
14+
...$this->allowedFields,
15+
16+
// 'first_name',
17+
];
18+
}
19+
}

src/Config/Registrar.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,13 @@ public static function Toolbar(): array
4949
],
5050
];
5151
}
52+
53+
public static function Generators(): array
54+
{
55+
return [
56+
'views' => [
57+
'shield:model' => 'CodeIgniter\Shield\Commands\Generators\Views\usermodel.tpl.php',
58+
],
59+
];
60+
}
5261
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Commands;
6+
7+
use CodeIgniter\Test\CIUnitTestCase;
8+
use CodeIgniter\Test\Filters\CITestStreamFilter;
9+
10+
/**
11+
* @internal
12+
*/
13+
final class UserModelGeneratorTest extends CIUnitTestCase
14+
{
15+
private $streamFilter;
16+
17+
protected function setUp(): void
18+
{
19+
parent::setUp();
20+
21+
CITestStreamFilter::$buffer = '';
22+
23+
$this->streamFilter = stream_filter_append(STDOUT, 'CITestStreamFilter');
24+
$this->streamFilter = stream_filter_append(STDERR, 'CITestStreamFilter');
25+
26+
if (is_file(HOMEPATH . 'src/Models/UserModel.php')) {
27+
copy(HOMEPATH . 'src/Models/UserModel.php', HOMEPATH . 'src/Models/UserModel.php.bak');
28+
}
29+
30+
$this->deleteTestFiles();
31+
}
32+
33+
protected function tearDown(): void
34+
{
35+
parent::tearDown();
36+
37+
stream_filter_remove($this->streamFilter);
38+
$this->deleteTestFiles();
39+
40+
if (is_file(HOMEPATH . 'src/Models/UserModel.php.bak')) {
41+
copy(HOMEPATH . 'src/Models/UserModel.php.bak', HOMEPATH . 'src/Models/UserModel.php');
42+
unlink(HOMEPATH . 'src/Models/UserModel.php.bak');
43+
}
44+
}
45+
46+
private function deleteTestFiles(): void
47+
{
48+
$possibleFiles = [
49+
APPPATH . 'Models/UserModel.php',
50+
HOMEPATH . 'src/Models/UserModel.php',
51+
];
52+
53+
foreach ($possibleFiles as $file) {
54+
clearstatcache(true, $file);
55+
56+
if (is_file($file)) {
57+
unlink($file);
58+
}
59+
}
60+
}
61+
62+
private function getFileContents(string $filepath): string
63+
{
64+
return (string) @file_get_contents($filepath);
65+
}
66+
67+
public function testGenerateUserModel(): void
68+
{
69+
command('shield:model UserModel');
70+
71+
$filepath = APPPATH . 'Models/UserModel.php';
72+
$this->assertStringContainsString('File created: ', CITestStreamFilter::$buffer);
73+
$this->assertFileExists($filepath);
74+
75+
$contents = $this->getFileContents($filepath);
76+
$this->assertStringContainsString('namespace App\Models;', $contents);
77+
$this->assertStringContainsString('class UserModel extends ShieldUserModel', $contents);
78+
$this->assertStringContainsString('use CodeIgniter\Shield\Models\UserModel as ShieldUserModel;', $contents);
79+
$this->assertStringContainsString('protected function initialize(): void', $contents);
80+
}
81+
82+
public function testGenerateUserModelCustomNamespace(): void
83+
{
84+
command('shield:model UserModel --namespace CodeIgniter\\\\Shield');
85+
86+
$filepath = HOMEPATH . 'src/Models/UserModel.php';
87+
$this->assertStringContainsString('File created: ', CITestStreamFilter::$buffer);
88+
$this->assertFileExists($filepath);
89+
90+
$contents = $this->getFileContents($filepath);
91+
$this->assertStringContainsString('namespace CodeIgniter\Shield\Models;', $contents);
92+
$this->assertStringContainsString('class UserModel extends ShieldUserModel', $contents);
93+
$this->assertStringContainsString('use CodeIgniter\Shield\Models\UserModel as ShieldUserModel;', $contents);
94+
$this->assertStringContainsString('protected function initialize(): void', $contents);
95+
}
96+
97+
public function testGenerateUserModelWithForce(): void
98+
{
99+
command('shield:model UserModel');
100+
command('shield:model UserModel --force');
101+
102+
$this->assertStringContainsString('File overwritten: ', CITestStreamFilter::$buffer);
103+
$this->assertFileExists(APPPATH . 'Models/UserModel.php');
104+
}
105+
106+
public function testGenerateUserModelWithSuffix(): void
107+
{
108+
command('shield:model User --suffix');
109+
110+
$this->assertStringContainsString('File created: ', CITestStreamFilter::$buffer);
111+
112+
$filepath = APPPATH . 'Models/UserModel.php';
113+
$this->assertFileExists($filepath);
114+
$this->assertStringContainsString('class UserModel extends ShieldUserModel', $this->getFileContents($filepath));
115+
}
116+
117+
public function testGenerateUserModelWithoutClassNameInput(): void
118+
{
119+
command('shield:model');
120+
121+
$this->assertStringContainsString('File created: ', CITestStreamFilter::$buffer);
122+
123+
$filepath = APPPATH . 'Models/UserModel.php';
124+
$this->assertFileExists($filepath);
125+
$this->assertStringContainsString('class UserModel extends ShieldUserModel', $this->getFileContents($filepath));
126+
}
127+
128+
public function testGenerateUserCannotAcceptShieldUserModelAsInput(): void
129+
{
130+
command('shield:model ShieldUserModel');
131+
132+
$this->assertStringContainsString('Cannot use `ShieldUserModel` as class name as this conflicts with the parent class.', CITestStreamFilter::$buffer);
133+
$this->assertFileDoesNotExist(APPPATH . 'Models/UserModel.php');
134+
135+
CITestStreamFilter::$buffer = '';
136+
137+
command('shield:model ShieldUser --suffix');
138+
139+
$this->assertStringContainsString('Cannot use `ShieldUserModel` as class name as this conflicts with the parent class.', CITestStreamFilter::$buffer);
140+
$this->assertFileDoesNotExist(APPPATH . 'Models/UserModel.php');
141+
}
142+
}

0 commit comments

Comments
 (0)