-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
951 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
127
src/mine-generator/src/Command/AbstractGeneratorCommand.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
|
||
} |
Oops, something went wrong.