diff --git a/docs/concepts.md b/docs/concepts.md index c8cafce74..717313076 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -26,10 +26,16 @@ on the standard Config class if nothing is found in the database. You can use your own models to handle user persistence. Shield calls this the "User Provider" class. A default model is provided for you at `CodeIgniter\Shield\Models\UserModel`. You can change this in the `Config\Auth->userProvider` setting. -The only requirement is that your new class MUST extend the provided `UserModel`. +The only requirement is that your new class MUST extend the provided `UserModel`. Shield has a CLI command to quickly create a custom `MyUserModel` by running the following command in terminal: + +```console +php spark shield:model MyUserModel +``` + +You should set `Config\Auth::$userProvider` as follows: ```php -public $userProvider = 'CodeIgniter\Shield\Models\UserModel'; +public $userProvider = \App\Models\MyUserModel::class; ``` diff --git a/src/Commands/Generators/UserModelGenerator.php b/src/Commands/Generators/UserModelGenerator.php new file mode 100644 index 000000000..f792abe26 --- /dev/null +++ b/src/Commands/Generators/UserModelGenerator.php @@ -0,0 +1,77 @@ + [options]'; + + /** + * The Command's Arguments + * + * @var array + */ + protected $arguments = [ + 'name' => 'The model class name.', + ]; + + /** + * The Command's Options + * + * @var array + */ + protected $options = [ + '--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".', + '--suffix' => 'Append the component title to the class name (e.g. User => UserModel).', + '--force' => 'Force overwrite existing file.', + ]; + + /** + * Actually execute a command. + */ + public function run(array $params): void + { + $this->component = 'Model'; + $this->directory = 'Models'; + $this->template = 'usermodel.tpl.php'; + + $this->classNameLang = 'CLI.generator.className.model'; + $this->execute($params); + } +} diff --git a/src/Commands/Generators/Views/usermodel.tpl.php b/src/Commands/Generators/Views/usermodel.tpl.php new file mode 100644 index 000000000..e25f6cb1a --- /dev/null +++ b/src/Commands/Generators/Views/usermodel.tpl.php @@ -0,0 +1,20 @@ +<@php + +declare(strict_types=1); + +namespace {namespace}; + +use CodeIgniter\Shield\Models\UserModel; + +class {class} extends UserModel +{ + protected function initialize(): void + { + $this->allowedFields = [ + ...$this->allowedFields, + // Add here your custom fields + // 'first_name', + ]; + } +} + diff --git a/src/Config/Registrar.php b/src/Config/Registrar.php index f212e7962..0186a2fec 100644 --- a/src/Config/Registrar.php +++ b/src/Config/Registrar.php @@ -45,4 +45,13 @@ public static function Toolbar(): array ], ]; } + + public static function Generators(): array + { + return [ + 'views' => [ + 'shield:model' => 'CodeIgniter\Shield\Commands\Generators\Views\usermodel.tpl.php', + ], + ]; + } } diff --git a/tests/Commands/UserModelGeneratorTest.php b/tests/Commands/UserModelGeneratorTest.php new file mode 100644 index 000000000..bb8e74b4e --- /dev/null +++ b/tests/Commands/UserModelGeneratorTest.php @@ -0,0 +1,100 @@ +streamFilter = stream_filter_append(STDOUT, 'CITestStreamFilter'); + $this->streamFilter = stream_filter_append(STDERR, 'CITestStreamFilter'); + } + + protected function tearDown(): void + { + parent::tearDown(); + + stream_filter_remove($this->streamFilter); + $result = str_replace(["\033[0;32m", "\033[0m", "\n"], '', CITestStreamFilter::$buffer); + + $filepath = str_replace('APPPATH' . DIRECTORY_SEPARATOR, APPPATH, trim(substr($result, 14))); + if (is_file($filepath)) { + unlink($filepath); + } + } + + protected function getFileContents(string $filepath): string + { + if (! file_exists($filepath)) { + return ''; + } + + return file_get_contents($filepath) ?: ''; + } + + public function testGenerateUserModel(): void + { + command('shield:model MyUserModel'); + $filepath = APPPATH . 'Models/MyUserModel.php'; + + $this->assertStringContainsString('File created: ', CITestStreamFilter::$buffer); + $this->assertFileExists($filepath); + + $this->assertStringContainsString('namespace App\Models;', $this->getFileContents($filepath)); + $this->assertStringContainsString('class MyUserModel extends UserModel', $this->getFileContents($filepath)); + $this->assertStringContainsString('use CodeIgniter\Shield\Models\UserModel;', $this->getFileContents($filepath)); + $this->assertStringContainsString('protected function initialize(): void', $this->getFileContents($filepath)); + } + + public function testGenerateUserModelCustomNamespace(): void + { + command('shield:model MyUserModel --namespace CodeIgniter\\\\Shield'); + $filepath = HOMEPATH . 'src/Models/MyUserModel.php'; + + $this->assertStringContainsString('File created: ', CITestStreamFilter::$buffer); + $this->assertFileExists($filepath); + + $this->assertStringContainsString('namespace CodeIgniter\Shield\Models;', $this->getFileContents($filepath)); + $this->assertStringContainsString('class MyUserModel extends UserModel', $this->getFileContents($filepath)); + $this->assertStringContainsString('use CodeIgniter\Shield\Models\UserModel;', $this->getFileContents($filepath)); + $this->assertStringContainsString('protected function initialize(): void', $this->getFileContents($filepath)); + + if (is_file($filepath)) { + unlink($filepath); + } + } + + public function testGenerateUserModelWithForce(): void + { + command('shield:model MyUserModel'); + + command('shield:model MyUserModel --force'); + $this->assertStringContainsString('File overwritten: ', CITestStreamFilter::$buffer); + + $filepath = APPPATH . 'Models/MyUserModel.php'; + $this->assertFileExists($filepath); + } + + public function testGenerateUserModelWithSuffix(): void + { + command('shield:model MyUser --suffix'); + $filepath = APPPATH . 'Models/MyUserModel.php'; + + $this->assertStringContainsString('File created: ', CITestStreamFilter::$buffer); + $this->assertFileExists($filepath); + $this->assertStringContainsString('class MyUserModel extends UserModel', $this->getFileContents($filepath)); + } +}