Skip to content

Commit

Permalink
feat builder template
Browse files Browse the repository at this point in the history
  • Loading branch information
zds-s committed Jan 13, 2024
1 parent f050b75 commit 31440d7
Show file tree
Hide file tree
Showing 17 changed files with 951 additions and 36 deletions.
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,9 @@
"friendsofphp/php-cs-fixer": "^3.0",
"hyperf/testing": "3.1.*",
"mockery/mockery": "^1.0",
"nette/php-generator": "^4.1@dev",
"phpstan/phpstan": "^1.0",
"swoole/ide-helper": "^5.0",
"symfony/var-exporter": "7.1.x-dev"
"swoole/ide-helper": "dev-master"
},
"minimum-stability": "dev",
"config": {
Expand Down
7 changes: 6 additions & 1 deletion src/mine-generator/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@
},
"require": {
"php": ">=8.1",
"phpoffice/phpspreadsheet": "^1.24"
"nette/php-generator": "v4.1.2"
},
"extra": {
"hyperf": {
"config": "Mine\\Generator\\GeneratorConfigProvider"
}
},
"config": {
"optimize-autoloader": true,
Expand Down
28 changes: 28 additions & 0 deletions src/mine-generator/publish/generator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php
return [
'dto' => [
'namespace' => 'App\\Dto',
'path' => BASE_PATH.DIRECTORY_SEPARATOR.'app'.DIRECTORY_SEPARATOR.'Dto',
'type' => [
'mapping' => [
'integer' => 'int',
],
'format' => [
'datetime' => [
'attribute' => [

]
]
]
]
],
'mapper' => [
'namespace' => 'App\\Mapper',
'path' => BASE_PATH.DIRECTORY_SEPARATOR.'app'.DIRECTORY_SEPARATOR.'Mapper',
],
'service' => [
'namespace' => 'App\\Service',
'path' => BASE_PATH.DIRECTORY_SEPARATOR.'app'.DIRECTORY_SEPARATOR.'Service',
'impl' => 'Impl'
]
];
127 changes: 127 additions & 0 deletions src/mine-generator/src/Command/AbstractGeneratorCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php

namespace Mine\Generator\Command;

use Hyperf\Command\Command;
use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\EnumType;
use Nette\PhpGenerator\InterfaceType;
use Nette\PhpGenerator\PhpFile;
use Nette\PhpGenerator\PhpNamespace;
use Nette\PhpGenerator\TraitType;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

abstract class AbstractGeneratorCommand extends Command
{

protected function configure()
{
$this->addArgument('name',InputArgument::REQUIRED,'class name');
$this->addOption('path','path',InputOption::VALUE_NONE,'generator file path');
$this->addOption('force','f',InputOption::VALUE_NONE,'force put file');
$this->addOption('namespace','namespace',InputOption::VALUE_NONE,'class in namespace');
}

protected function getConfig(string $key,mixed $default = null): mixed
{
return config('generator.'.$key,$default);
}


abstract protected function getDefaultPath(): string;

abstract protected function getDefaultNamespace(): string;


public function __invoke(): void
{
$path = $this->input->getOption('path') ?: $this->getDefaultPath();
if (!file_exists($path)){
if (!mkdir($path, 0777, true) && !is_dir($path)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $path));
}
$this->output->success(sprintf('Directory "%s" created',$path));
}
$path = realpath($path);

$name = $this->input->getArgument('name');
if (empty($name)){
throw new \RuntimeException('name argument not found');
}
$force = (boolean)($this->input->getOption('force')??false);
$filepath = $path.DIRECTORY_SEPARATOR.$name.'.php';
if (file_exists($filepath)){
if ($force !== true) {
throw new \RuntimeException(sprintf('file %s exists', $filepath));
}
$this->handleIo(PhpFile::fromCode(file_get_contents($filepath)),$filepath);
return;
}
$namespace = $this->input->getOption('namespace');
if (empty($namespace)){
$namespace = $this->getDefaultNamespace();
$namespace = rtrim($namespace,'\\');
}
$file = new PhpFile();
$file->addNamespace($namespace)
->addClass($name);
$this->handleIo($file,$filepath);
return;
}

protected function defaultUse(): array
{
return [];
}


protected function hasUse(ClassType $type,string $use): bool
{
$uses = $type->getNamespace()?->getUses() ?? [];
foreach ($uses as $val){
if ($val === $use){
return true;
}
}
return false;
}

protected function handleIo(PhpFile $php,string $filepath): void
{
if ($php->hasStrictTypes()){
$php->setStrictTypes();
}
foreach ($php->getNamespaces() as $namespace){
if (method_exists($this,'defaultUse')){
$uses = $this->defaultUse();
$nowUses = $namespace->getUses();
foreach ($uses as $useValue){
$use = $useValue[0];
$as = $useValue[1] ?? null;
if (empty($nowUses[$use])){
$namespace->addUse($use,$as);
}
}
}
foreach ($namespace->getClasses() as $className => $classAst){
$this->handleClass($classAst,$php,$namespace);
break;
}
}
file_put_contents($filepath,(string)$php);
}

/**
* 处理类
* @param ClassType|InterfaceType|TraitType|EnumType $class
* @param PhpFile $phpFile
* @param PhpNamespace $namespace
* @return mixed
*/
abstract protected function handleClass(
ClassType|InterfaceType|TraitType|EnumType $class,
PhpFile $phpFile,
PhpNamespace $namespace,
);
}
172 changes: 172 additions & 0 deletions src/mine-generator/src/Command/GeneratorDtoCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
<?php

namespace Mine\Generator\Command;

use Hyperf\ApiDocs\Annotation\ApiModel;
use Hyperf\ApiDocs\Annotation\ApiModelProperty;
use Hyperf\Collection\Arr;
use Hyperf\Command\Annotation\Command;
use Hyperf\Database\Model\Model;
use Hyperf\Database\Schema\Builder as SchemaBuilder;
use Hyperf\Database\Schema\Column;
use Hyperf\Stringable\Str;
use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\EnumType;
use Nette\PhpGenerator\InterfaceType;
use Nette\PhpGenerator\PhpFile;
use Nette\PhpGenerator\PhpNamespace;
use Nette\PhpGenerator\TraitType;
use Symfony\Component\Console\Input\InputOption;

#[Command]
class GeneratorDtoCommand extends AbstractGeneratorCommand
{
protected ?string $name = 'gen:mine-dto';

protected string $description = 'generator Dto';
public function configure()
{
$this->addOption('model','mo',InputOption::VALUE_OPTIONAL,'Generate DTO classes based on the model');
$this->addOption('property','p',InputOption::VALUE_OPTIONAL,'Generate DTO classes based on parameters');
$this->addOption('table-hidden','m-h',InputOption::VALUE_OPTIONAL,'Do not generate the following fields');
$this->addOption('property-case','property-case',InputOption::VALUE_OPTIONAL,'Field type 0 Snake 1 Hump');
parent::configure();
}

protected function getDefaultPath(): string
{
return $this->getConfig('dto.path');
}

protected function getDefaultNamespace(): string
{
return $this->getConfig('dto.namespace');
}

protected function defaultUse(): array
{
return [
[
ApiModel::class,'ApiDto'
],
[
ApiModelProperty::class,'ApiProperty'
]
];
}

protected function handlePropery(ClassType $class,array $property)
{
foreach ($property as $option){
if ($class->hasProperty($option['name'])){
// $this->output->warning(sprintf('property %s is already exists,now skip build',$option['name']));
continue;
}
$io = $class->addProperty($option['name'],$option['value']);
$io->setType('?'. $this->convertType($option['type']));
foreach ($option['attribute'] as $attribute => $args){
$io->addAttribute($attribute,$args);
}
}
}

protected function handleClass(
TraitType|InterfaceType|ClassType|EnumType $class,
PhpFile $phpFile,
PhpNamespace $namespace,
)
{
if (!($class instanceof ClassType)){
throw new \RuntimeException('dto Errors');
}
$isApiModel = false;
$attributes = $class->getAttributes();
foreach ($attributes as $attr){
if ($attr->getName() === ApiModel::class){
$isApiModel = true;
}
}

if (!$isApiModel){
$class->addAttribute(ApiModel::class);
}

$model = $this->input->getOption('model');
$propertyOption = $this->input->getOption('property');
$builderProperty = [];
$hidden = $this->input->getOption('table-hidden') ?? [];
$propertyCase = (int)($this->input->getOption('property-case') ?? 0);
if (!empty($model)){
/**
* @var Model $model
* @var SchemaBuilder $builder
*/
$builder = $model::getModel()->getConnection()->getSchemaBuilder();
$columns = $builder->getColumns();
$table = $model::getModel()->getTable();
$tableColumns = $builder->getColumnListing($table);
$keyName = $model::getModel()->getKeyName();
$columns = Arr::where($columns,function (Column $column)use ($tableColumns,$keyName){
return in_array($column->getName(),$tableColumns,true) && $keyName !== $column->getName();
});
$casts = $model::getModel()->getCasts();
/**
* @var Column[] $columns
*/
foreach ($columns as $colum){
if (in_array($colum->getName(),$hidden,true)){
continue;
}
$propertyName = $this->covertCase($colum->getName(),$propertyCase);
$builderProperty[] = [
'name' => $propertyName,
'value' => null,
'type' => ($casts[$colum->getName()] ?? 'string'),
'attribute' => [
ApiModelProperty::class=>[
'value' => $colum->getComment(),
'example' => $colum->getDefault(),
'required' => true,
]
]
];
}
}
if (!empty($propertyOption)){
$propertys = explode('|',$propertyOption);
foreach ($propertys as $property){
$property = explode(',',$property);
$propertyName = $this->covertCase($property[0] ?? '',$propertyCase);
if (in_array($colum->getName(),$hidden,true)){
continue;
}
$builderProperty[] = [
'name' => $propertyName,
'value' => $property[2] ?? null,
'type' => ($property[3]??'string'),
'attribute' => [
ApiModelProperty::class=>[
'value' => $property[1] ?? null,
'example' => $property[2] ?? null,
'required' => true,
]
]
];
}
}
if (count($builderProperty) > 0){
$this->handlePropery($class,$builderProperty);
}
}

protected function covertCase(string $property,int $case)
{
return $case === 0 ? Str::snake($property) : Str::camel($property);
}

protected function convertType(string $type): string
{
return $this->getConfig('dto.type.mapping.'.$type,$type);
}

}
Loading

0 comments on commit 31440d7

Please sign in to comment.