Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CRUD generator #111

Draft
wants to merge 25 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config/params.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
'class' => Generators\ActiveRecord\Generator::class,
'parameters' => [],
],
[
'class' => Generators\CRUD\Generator::class,
'parameters' => [],
],
],
'parameters' => [
'templates' => [],
Expand Down
159 changes: 159 additions & 0 deletions src/Generator/CRUD/Command.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\Gii\Generator\CRUD;

use Yiisoft\Strings\Inflector;
use Yiisoft\Strings\StringHelper;
use Yiisoft\Validator\Rule\Each;
use Yiisoft\Validator\Rule\Regex;
use Yiisoft\Validator\Rule\Required;
use Yiisoft\Yii\Gii\Generator\AbstractGeneratorCommand;
use Yiisoft\Yii\Gii\Validator\ClassExistsRule;
use Yiisoft\Yii\Gii\Validator\NewClassRule;
use Yiisoft\Yii\Gii\Validator\TemplateRule;

final class Command extends AbstractGeneratorCommand
{
#[Each([
new Regex(
pattern: '/^[a-z][a-z0-9]*$/',
message: 'Only a-z, 0-9, dashes (-), spaces and commas are allowed.'
),
])]
private readonly array $actions;

public function __construct(
#[Required]
#[Regex(
pattern: '/^(?:[a-z][a-z0-9]*)(?:\\\\[a-z][a-z0-9]*)*$/i',
message: 'Invalid namespace'
)]
private readonly string $controllerNamespace = 'App\\Controller',
#[Required]
#[ClassExistsRule]
private readonly string $model = 'App\\Model\\User',
#[Required]
#[Regex(
pattern: '/^[A-Z][a-zA-Z0-9]*Controller$/',
message: 'Only word characters are allowed, and the class name must start with a capital letter and end with "Controller".'
)]
#[NewClassRule]
private readonly string $controllerClass = 'IndexController',
/**
* @var string the controller path
*/
private readonly string $controllerPath = '@src/Controller',
/**
* @var string the controller's views path
*/
private readonly string $viewsPath = '@views/',
#[Regex(
pattern: '/^[a-z\\\\]*$/i',
message: 'Only word characters and backslashes are allowed.',
skipOnEmpty: true,
)]
private readonly string $baseClass = '',
array $actions = ['index', 'view'],
#[Required(message: 'A code template must be selected.')]
#[TemplateRule]
protected string $template = 'default',
) {
parent::__construct($template);
sort($actions);
$this->actions = $actions;
}

/**
* @return string the controller ID
*/
public function getControllerID(): string
{
$name = StringHelper::baseName($this->controllerClass);
return (new Inflector())->pascalCaseToId(substr($name, 0, -10));
}

public function getControllerClass(): string
{
return $this->controllerClass;
}

public function getActions(): array
{
return $this->actions;
}

public function getViewsPath(): string
{
return $this->viewsPath;
}

public function getModel(): string
{
return $this->model;
}

public function getControllerNamespace(): string
{
return $this->controllerNamespace;
}

public function getBaseClass(): string
{
return $this->baseClass;
}

public function getDirectory(): string
{
return $this->controllerPath;
}

public static function getAttributeLabels(): array
{
return [
'controllerNamespace' => 'Controller Namespace',
'controllerClass' => 'Controller Class',
'baseClass' => 'Base Class',
'model' => 'Active record class',
'controllerPath' => 'Controller Path',
'actions' => 'Action IDs',
'viewsPath' => 'Views Path',
'template' => 'Action IDs',
];
}

public static function getHints(): array
{
return [
'controllerClass' => 'This is the name of the controller class to be generated. You should
provide a fully qualified namespaced class (e.g. <code>App\Controller\PostController</code>),
and class name should be in CamelCase ending with the word <code>Controller</code>. Make sure the class
is using the same namespace as specified by your application\'s controllerNamespace property.',
'actions' => 'Provide one or multiple action IDs to generate empty action method(s) in the controller. Separate multiple action IDs with commas or spaces.
Action IDs should be in lower case. For example:
<ul>
<li><code>index</code> generates <code>index()</code></li>
<li><code>create-order</code> generates <code>createOrder()</code></li>
</ul>',
'viewsPath' => 'Specify the directory for storing the view scripts for the controller. You may use path alias here, e.g.,
<code>/var/www/app/controllers/views/order</code>, <code>@app/views/order</code>. If not set, it will default
to <code>@app/views/ControllerID</code>',
'baseClass' => 'This is the class that the new controller class will extend from. Please make sure the class exists and can be autoloaded.',
];
}

public static function getAttributes(): array
{
return [
'controllerNamespace',
'controllerClass',
'baseClass',
'model',
'viewsPath',
'actions',
'template',
'controllerPath',
];
}
}
132 changes: 132 additions & 0 deletions src/Generator/CRUD/Generator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\Gii\Generator\CRUD;

use InvalidArgumentException;
use Yiisoft\ActiveRecord\ActiveRecordFactory;
use Yiisoft\Aliases\Aliases;
use Yiisoft\Strings\StringHelper;
use Yiisoft\Validator\ValidatorInterface;
use Yiisoft\Yii\Gii\Component\CodeFile\CodeFile;
use Yiisoft\Yii\Gii\Generator\AbstractGenerator;
use Yiisoft\Yii\Gii\GeneratorCommandInterface;
use Yiisoft\Yii\Gii\ParametersProvider;

/**
* This generator will generate a controller and one or a few action view files.
*/
final class Generator extends AbstractGenerator
{
public function __construct(
Aliases $aliases,
ValidatorInterface $validator,
ParametersProvider $parametersProvider,
private readonly ActiveRecordFactory $activeRecordFactory,
) {
parent::__construct($aliases, $validator, $parametersProvider);
}

public static function getId(): string
{
return 'crud';
}

public static function getName(): string
{
return 'CRUD';
}

public static function getDescription(): string
{
return 'This generator helps you to quickly generate a new controller class with
one or several controller actions and their corresponding views.';
}

public function getRequiredTemplates(): array
{
return [
'controller.php',
'view.php',
];
}

public function doGenerate(GeneratorCommandInterface $command): array
{
if (!$command instanceof Command) {
throw new InvalidArgumentException();
}

$files = [];

$rootPath = $this->aliases->get('@root');

$codeFile = (new CodeFile(
$this->getControllerFile($command),
$this->render($command, 'controller.php')
))->withBasePath($rootPath);
$files[$codeFile->getId()] = $codeFile;

//$actions = $command->getActions();
$actions = ['index', 'view'];
$model = $this->activeRecordFactory->createAR($command->getModel());
foreach ($actions as $action) {
$codeFile = (new CodeFile(
$this->getViewFile($command, $action),
$this->render($command, $action . '.php', ['action' => $action, 'model' => $model])
))->withBasePath($rootPath);
$files[$codeFile->getId()] = $codeFile;
}

return $files;
}

/**
* @return string the controller class file path
*/
private function getControllerFile(Command $command): string
{
$directory = empty($command->getDirectory()) ? '@src/Controller/' : $command->getDirectory();

return $this->aliases->get(
str_replace(
['\\', '//'],
'/',
sprintf(
'%s/%s.php',
$directory,
StringHelper::baseName($command->getModel()) . 'Controller',
),
),
);
}

/**
* @param string $action the action ID
*
* @return string the action view file path
*/
public function getViewFile(Command $command, string $action): string
{
$directory = empty($command->getViewsPath()) ? '@views/' : $command->getViewsPath();

return $this->aliases->get(
str_replace(
['\\', '//'],
'/',
sprintf(
'%s/%s/%s.php',
$directory,
StringHelper::lowercase(StringHelper::baseName($command->getModel())),
$action,
),
),
);
}

public static function getCommandClass(): string
{
return Command::class;
}
}
74 changes: 74 additions & 0 deletions src/Generator/CRUD/default/controller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
/**
* This is the template for generating a controller class file.
*/

use Yiisoft\ActiveRecord\ActiveRecordFactory;
use Yiisoft\Strings\StringHelper;

/* @var $command Yiisoft\Yii\Gii\Generator\CRUD\Command */

$classDefinitionParts = [];
$classDefinitionParts[] = StringHelper::baseName($command->getModel()) . 'Controller';
if (!empty($command->getBaseClass())) {
//$classDefinitionParts[] = 'extends \\' . trim($command->getBaseClass(), '\\');
}
$classDefinition = implode(' ', $classDefinitionParts) . PHP_EOL;

/**
* @var $arF ActiveRecordFactory
*/

echo "<?php\n";
?>

declare(strict_types=1);

namespace <?= $command->getControllerNamespace() ?>;

use Yiisoft\ActiveRecord\ActiveRecordFactory;
use Yiisoft\Data\Paginator\OffsetPaginator;
use Yiisoft\Data\Reader\Iterable\IterableDataReader;
use Yiisoft\Yii\View\ViewRenderer;

final class <?= $classDefinition; ?>
{
public function __construct(private ViewRenderer $viewRenderer)
{
$this->viewRenderer = $viewRenderer->withController($this);
}
<?php
foreach ($command->getActions() as $action) {
echo PHP_EOL;
switch ($action) {
case 'index':
echo <<<PHP
public function index(ActiveRecordFactory \$activeRecordFactory)
{
\$query = \$activeRecordFactory->createQueryTo(\\{$command->getModel()}::class);
\$paginator = (new OffsetPaginator(new IterableDataReader(\$query->all())))
->withPageSize(10);
return \$this->viewRenderer->render('index', [
'paginator' => \$paginator,
]);
}
PHP;
break;
case 'view':
echo <<<PHP
public function view(ActiveRecordFactory \$activeRecordFactory, string \$id)
{
\$model = \$activeRecordFactory->createQueryTo(\\{$command->getModel()}::class)->findOne(\$id);
return \$this->viewRenderer->render('view', [
'model' => \$model,
]);
}
PHP;
break;
}

}
echo PHP_EOL;
?>
}
Loading
Loading