mirror of
https://devops.lemonos.cn/lawson/FendxPHP.git
synced 2026-06-15 23:12:49 +08:00
553 lines
16 KiB
PHP
553 lines
16 KiB
PHP
|
|
<?php
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace Fendx\CLI\Generator;
|
||
|
|
|
||
|
|
class ModelGenerator extends CodeGenerator
|
||
|
|
{
|
||
|
|
protected function loadTemplates(): void
|
||
|
|
{
|
||
|
|
$this->templates = [
|
||
|
|
'model' => $this->getModelTemplate(),
|
||
|
|
'property' => $this->getPropertyTemplate(),
|
||
|
|
'method' => $this->getMethodTemplate(),
|
||
|
|
'migration' => $this->getMigrationTemplate(),
|
||
|
|
'factory' => $this->getFactoryTemplate(),
|
||
|
|
'seeder' => $this->getSeederTemplate()
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
public function generate(string $name, array $options = []): bool
|
||
|
|
{
|
||
|
|
if (!$this->validateName($name)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
$className = $this->getClassName($name);
|
||
|
|
$tableName = $options['table'] ?? $this->getTableName($name);
|
||
|
|
$fields = $options['fields'] ?? [];
|
||
|
|
$timestamps = $options['timestamps'] ?? true;
|
||
|
|
$softDeletes = $options['soft_deletes'] ?? false;
|
||
|
|
$relationships = $options['relationships'] ?? [];
|
||
|
|
$generateMigration = $options['migration'] ?? true;
|
||
|
|
$generateFactory = $options['factory'] ?? false;
|
||
|
|
$generateSeeder = $options['seeder'] ?? false;
|
||
|
|
|
||
|
|
if (!empty($fields)) {
|
||
|
|
if (is_string($fields)) {
|
||
|
|
$fields = $this->parseFields($fields);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 生成模型
|
||
|
|
$modelContent = $this->generateModel($className, $tableName, $fields, $timestamps, $softDeletes, $relationships);
|
||
|
|
$modelPath = $this->getNamespacePath('Model');
|
||
|
|
$modelFilePath = $modelPath . '/' . $className . '.php';
|
||
|
|
|
||
|
|
if (!$this->createFile($modelFilePath, $modelContent)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 生成迁移文件
|
||
|
|
if ($generateMigration) {
|
||
|
|
$this->generateMigration($className, $tableName, $fields, $timestamps, $softDeletes);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 生成工厂文件
|
||
|
|
if ($generateFactory) {
|
||
|
|
$this->generateFactory($className, $fields);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 生成种子文件
|
||
|
|
if ($generateSeeder) {
|
||
|
|
$this->generateSeeder($className);
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->showMessage('success', "Model '{$className}' generated successfully!");
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function generateModel(string $className, string $tableName, array $fields, bool $timestamps, bool $softDeletes, array $relationships): string
|
||
|
|
{
|
||
|
|
$useStatements = [];
|
||
|
|
$properties = '';
|
||
|
|
$methods = '';
|
||
|
|
|
||
|
|
// 基础use语句
|
||
|
|
$useStatements[] = 'use Fendx\\ORM\\Model;';
|
||
|
|
|
||
|
|
if ($softDeletes) {
|
||
|
|
$useStatements[] = 'use Fendx\\ORM\\Traits\\SoftDeletes;';
|
||
|
|
}
|
||
|
|
|
||
|
|
// 生成属性
|
||
|
|
foreach ($fields as $field) {
|
||
|
|
$properties .= $this->generateProperty($field);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 生成关系方法
|
||
|
|
foreach ($relationships as $relationship) {
|
||
|
|
$methods .= $this->generateRelationshipMethod($relationship);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 生成访问器和修改器
|
||
|
|
foreach ($fields as $field) {
|
||
|
|
$methods .= $this->generateAccessorMutator($field);
|
||
|
|
}
|
||
|
|
|
||
|
|
$useBlock = implode("\n", array_unique($useStatements)) . "\n\n";
|
||
|
|
|
||
|
|
return $this->renderTemplate('model', [
|
||
|
|
'namespace' => $this->getFullNamespace('Model'),
|
||
|
|
'use_block' => $useBlock,
|
||
|
|
'class_name' => $className,
|
||
|
|
'table_name' => $tableName,
|
||
|
|
'timestamps' => $timestamps ? 'true' : 'false',
|
||
|
|
'soft_deletes' => $softDeletes ? 'use SoftDeletes;' : '',
|
||
|
|
'properties' => $properties,
|
||
|
|
'methods' => $methods
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function generateProperty(array $field): string
|
||
|
|
{
|
||
|
|
$name = $field['name'];
|
||
|
|
$type = $this->getPhpType($field['type']);
|
||
|
|
$nullable = isset($field['options']['nullable']);
|
||
|
|
|
||
|
|
$docBlock = $this->generatePropertyDocBlock($field);
|
||
|
|
|
||
|
|
return $this->renderTemplate('property', [
|
||
|
|
'doc_block' => $docBlock,
|
||
|
|
'property_name' => $name,
|
||
|
|
'property_type' => $type,
|
||
|
|
'nullable' => $nullable ? '?' : ''
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function generatePropertyDocBlock(array $field): string
|
||
|
|
{
|
||
|
|
$type = $this->getPhpType($field['type']);
|
||
|
|
$comment = '';
|
||
|
|
|
||
|
|
if (isset($field['options']['unique'])) {
|
||
|
|
$comment .= ' (unique)';
|
||
|
|
}
|
||
|
|
if (isset($field['options']['default'])) {
|
||
|
|
$comment .= ' (default: ' . $field['options']['default'] . ')';
|
||
|
|
}
|
||
|
|
if (isset($field['options']['nullable'])) {
|
||
|
|
$comment .= ' (nullable)';
|
||
|
|
}
|
||
|
|
|
||
|
|
return "/**\n * @var {$type}{$comment}\n */";
|
||
|
|
}
|
||
|
|
|
||
|
|
private function generateRelationshipMethod(array $relationship): string
|
||
|
|
{
|
||
|
|
$type = $relationship['type'];
|
||
|
|
$relatedModel = $relationship['model'];
|
||
|
|
$foreignKey = $relationship['foreign_key'] ?? null;
|
||
|
|
$localKey = $relationship['local_key'] ?? null;
|
||
|
|
|
||
|
|
switch ($type) {
|
||
|
|
case 'hasOne':
|
||
|
|
return $this->generateHasOneMethod($relatedModel, $foreignKey, $localKey);
|
||
|
|
case 'hasMany':
|
||
|
|
return $this->generateHasManyMethod($relatedModel, $foreignKey, $localKey);
|
||
|
|
case 'belongsTo':
|
||
|
|
return $this->generateBelongsToMethod($relatedModel, $foreignKey, $localKey);
|
||
|
|
case 'belongsToMany':
|
||
|
|
return $this->generateBelongsToManyMethod($relatedModel, $foreignKey, $localKey);
|
||
|
|
default:
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private function generateHasOneMethod(string $relatedModel, ?string $foreignKey, ?string $localKey): string
|
||
|
|
{
|
||
|
|
$methodName = $this->getVariableName($relatedModel);
|
||
|
|
$params = '';
|
||
|
|
|
||
|
|
if ($foreignKey) {
|
||
|
|
$params .= ", '{$foreignKey}'";
|
||
|
|
}
|
||
|
|
if ($localKey) {
|
||
|
|
$params .= ", '{$localKey}'";
|
||
|
|
}
|
||
|
|
|
||
|
|
return <<<PHP
|
||
|
|
/**
|
||
|
|
* Get the associated {$relatedModel}.
|
||
|
|
*/
|
||
|
|
public function {$methodName}(): HasOne
|
||
|
|
{
|
||
|
|
return \$this->hasOne({$relatedModel}::class{$params});
|
||
|
|
}
|
||
|
|
|
||
|
|
PHP;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function generateHasManyMethod(string $relatedModel, ?string $foreignKey, ?string $localKey): string
|
||
|
|
{
|
||
|
|
$methodName = $this->getPluralName($this->getVariableName($relatedModel));
|
||
|
|
$params = '';
|
||
|
|
|
||
|
|
if ($foreignKey) {
|
||
|
|
$params .= ", '{$foreignKey}'";
|
||
|
|
}
|
||
|
|
if ($localKey) {
|
||
|
|
$params .= ", '{$localKey}'";
|
||
|
|
}
|
||
|
|
|
||
|
|
return <<<PHP
|
||
|
|
/**
|
||
|
|
* Get the associated {$relatedModel} records.
|
||
|
|
*/
|
||
|
|
public function {$methodName}(): HasMany
|
||
|
|
{
|
||
|
|
return \$this->hasMany({$relatedModel}::class{$params});
|
||
|
|
}
|
||
|
|
|
||
|
|
PHP;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function generateBelongsToMethod(string $relatedModel, ?string $foreignKey, ?string $localKey): string
|
||
|
|
{
|
||
|
|
$methodName = $this->getVariableName($relatedModel);
|
||
|
|
$params = '';
|
||
|
|
|
||
|
|
if ($foreignKey) {
|
||
|
|
$params .= ", '{$foreignKey}'";
|
||
|
|
}
|
||
|
|
if ($localKey) {
|
||
|
|
$params .= ", '{$localKey}'";
|
||
|
|
}
|
||
|
|
|
||
|
|
return <<<PHP
|
||
|
|
/**
|
||
|
|
* Get the associated {$relatedModel}.
|
||
|
|
*/
|
||
|
|
public function {$methodName}(): BelongsTo
|
||
|
|
{
|
||
|
|
return \$this->belongsTo({$relatedModel}::class{$params});
|
||
|
|
}
|
||
|
|
|
||
|
|
PHP;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function generateBelongsToManyMethod(string $relatedModel, ?string $foreignKey, ?string $localKey): string
|
||
|
|
{
|
||
|
|
$methodName = $this->getPluralName($this->getVariableName($relatedModel));
|
||
|
|
$params = '';
|
||
|
|
|
||
|
|
if ($foreignKey) {
|
||
|
|
$params .= ", '{$foreignKey}'";
|
||
|
|
}
|
||
|
|
if ($localKey) {
|
||
|
|
$params .= ", '{$localKey}'";
|
||
|
|
}
|
||
|
|
|
||
|
|
return <<<PHP
|
||
|
|
/**
|
||
|
|
* Get the associated {$relatedModel} records.
|
||
|
|
*/
|
||
|
|
public function {$methodName}(): BelongsToMany
|
||
|
|
{
|
||
|
|
return \$this->belongsToMany({$relatedModel}::class{$params});
|
||
|
|
}
|
||
|
|
|
||
|
|
PHP;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function generateAccessorMutator(array $field): string
|
||
|
|
{
|
||
|
|
$name = $field['name'];
|
||
|
|
$type = $this->getPhpType($field['type']);
|
||
|
|
$methods = '';
|
||
|
|
|
||
|
|
// 生成访问器
|
||
|
|
$accessorName = 'get' . $this->getClassName($name) . 'Attribute';
|
||
|
|
$methods .= <<<PHP
|
||
|
|
/**
|
||
|
|
* Get the {$name} attribute.
|
||
|
|
*/
|
||
|
|
public function {$accessorName}(): {$type}
|
||
|
|
{
|
||
|
|
return \$this->attributes['{$name}'];
|
||
|
|
}
|
||
|
|
|
||
|
|
PHP;
|
||
|
|
|
||
|
|
// 生成修改器
|
||
|
|
$mutatorName = 'set' . $this->getClassName($name) . 'Attribute';
|
||
|
|
$methods .= <<<PHP
|
||
|
|
/**
|
||
|
|
* Set the {$name} attribute.
|
||
|
|
*/
|
||
|
|
public function {$mutatorName}({$type} \$value): void
|
||
|
|
{
|
||
|
|
\$this->attributes['{$name}'] = \$value;
|
||
|
|
}
|
||
|
|
|
||
|
|
PHP;
|
||
|
|
|
||
|
|
return $methods;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function generateMigration(string $className, string $tableName, array $fields, bool $timestamps, bool $softDeletes): void
|
||
|
|
{
|
||
|
|
$migrationName = 'create_' . $tableName . '_table';
|
||
|
|
$migrationClassName = $this->getClassName($migrationName);
|
||
|
|
|
||
|
|
$content = $this->generateMigrationContent($migrationClassName, $tableName, $fields, $timestamps, $softDeletes);
|
||
|
|
|
||
|
|
$migrationPath = $this->getNamespacePath('../database/migrations');
|
||
|
|
$timestamp = date('Y_m_d_His');
|
||
|
|
$filename = $timestamp . '_' . $migrationName . '.php';
|
||
|
|
$filePath = $migrationPath . '/' . $filename;
|
||
|
|
|
||
|
|
$this->createFile($filePath, $content);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function generateMigrationContent(string $className, string $tableName, array $fields, bool $timestamps, bool $softDeletes): string
|
||
|
|
{
|
||
|
|
$upMethods = '';
|
||
|
|
$downMethods = '';
|
||
|
|
|
||
|
|
// 生成字段定义
|
||
|
|
foreach ($fields as $field) {
|
||
|
|
$upMethods .= $this->generateMigrationField($field);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 生成时间戳
|
||
|
|
if ($timestamps) {
|
||
|
|
$upMethods .= " \$table->timestamps();\n";
|
||
|
|
$downMethods .= " \$table->dropTimestamps();\n";
|
||
|
|
}
|
||
|
|
|
||
|
|
// 生成软删除
|
||
|
|
if ($softDeletes) {
|
||
|
|
$upMethods .= " \$table->softDeletes();\n";
|
||
|
|
$downMethods .= " \$table->dropSoftDeletes();\n";
|
||
|
|
}
|
||
|
|
|
||
|
|
return $this->renderTemplate('migration', [
|
||
|
|
'class_name' => $className,
|
||
|
|
'table_name' => $tableName,
|
||
|
|
'up_methods' => $upMethods,
|
||
|
|
'down_methods' => $downMethods
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function generateMigrationField(array $field): string
|
||
|
|
{
|
||
|
|
$name = $field['name'];
|
||
|
|
$type = $field['type'];
|
||
|
|
$options = $field['options'] ?? [];
|
||
|
|
|
||
|
|
$method = "\$table->{$type}('{$name}')";
|
||
|
|
|
||
|
|
if (isset($options['nullable'])) {
|
||
|
|
$method .= '->nullable()';
|
||
|
|
}
|
||
|
|
|
||
|
|
if (isset($options['default'])) {
|
||
|
|
$default = $options['default'];
|
||
|
|
if (is_string($default)) {
|
||
|
|
$default = "'{$default}'";
|
||
|
|
}
|
||
|
|
$method .= "->default({$default})";
|
||
|
|
}
|
||
|
|
|
||
|
|
if (isset($options['unique'])) {
|
||
|
|
$method .= '->unique()';
|
||
|
|
}
|
||
|
|
|
||
|
|
return " {$method};\n";
|
||
|
|
}
|
||
|
|
|
||
|
|
private function generateFactory(string $className, array $fields): void
|
||
|
|
{
|
||
|
|
$factoryClassName = $className . 'Factory';
|
||
|
|
$definition = $this->generateFactoryDefinition($className, $fields);
|
||
|
|
|
||
|
|
$content = $this->renderTemplate('factory', [
|
||
|
|
'namespace' => $this->getFullNamespace('Database\\Factories'),
|
||
|
|
'class_name' => $factoryClassName,
|
||
|
|
'model_class' => $className,
|
||
|
|
'definition' => $definition
|
||
|
|
]);
|
||
|
|
|
||
|
|
$factoryPath = $this->getNamespacePath('../database/factories');
|
||
|
|
$filePath = $factoryPath . '/' . $factoryClassName . '.php';
|
||
|
|
|
||
|
|
$this->createFile($filePath, $content);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function generateFactoryDefinition(string $className, array $fields): string
|
||
|
|
{
|
||
|
|
$definition = '';
|
||
|
|
|
||
|
|
foreach ($fields as $field) {
|
||
|
|
$name = $field['name'];
|
||
|
|
$type = $field['type'];
|
||
|
|
$faker = $this->getFakerMethod($type, $field['options'] ?? []);
|
||
|
|
|
||
|
|
$definition .= " '{$name}' => {$faker},\n";
|
||
|
|
}
|
||
|
|
|
||
|
|
return $definition;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function getFakerMethod(string $type, array $options): string
|
||
|
|
{
|
||
|
|
$fakerMap = [
|
||
|
|
'string' => '$this->faker->sentence()',
|
||
|
|
'varchar' => '$this->faker->word()',
|
||
|
|
'text' => '$this->faker->paragraph()',
|
||
|
|
'int' => '$this->faker->numberBetween(1, 1000)',
|
||
|
|
'integer' => '$this->faker->numberBetween(1, 1000)',
|
||
|
|
'bigint' => '$this->faker->numberBetween(1, 1000000)',
|
||
|
|
'float' => '$this->faker->randomFloat(2, 0, 1000)',
|
||
|
|
'double' => '$this->faker->randomFloat(2, 0, 1000)',
|
||
|
|
'bool' => '$this->faker->boolean()',
|
||
|
|
'boolean' => '$this->faker->boolean()',
|
||
|
|
'date' => '$this->faker->date()',
|
||
|
|
'datetime' => '$this->faker->datetime()',
|
||
|
|
'timestamp' => '$this->faker->datetime()',
|
||
|
|
'email' => '$this->faker->email()',
|
||
|
|
'url' => '$this->faker->url()'
|
||
|
|
];
|
||
|
|
|
||
|
|
return $fakerMap[$type] ?? '$this->faker->word()';
|
||
|
|
}
|
||
|
|
|
||
|
|
private function generateSeeder(string $className): void
|
||
|
|
{
|
||
|
|
$seederClassName = $className . 'Seeder';
|
||
|
|
$modelVariable = $this->getVariableName($className);
|
||
|
|
|
||
|
|
$content = $this->renderTemplate('seeder', [
|
||
|
|
'namespace' => $this->getFullNamespace('Database\\Seeders'),
|
||
|
|
'class_name' => $seederClassName,
|
||
|
|
'model_class' => $className,
|
||
|
|
'model_variable' => $modelVariable,
|
||
|
|
'model_namespace' => $this->getFullNamespace('Model')
|
||
|
|
]);
|
||
|
|
|
||
|
|
$seederPath = $this->getNamespacePath('../database/seeders');
|
||
|
|
$filePath = $seederPath . '/' . $seederClassName . '.php';
|
||
|
|
|
||
|
|
$this->createFile($filePath, $content);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function getModelTemplate(): string
|
||
|
|
{
|
||
|
|
return <<<PHP
|
||
|
|
<?php
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace {{namespace}};
|
||
|
|
|
||
|
|
{{use_block}}class {{class_name}} extends Model
|
||
|
|
{
|
||
|
|
protected string \$table = '{{table_name}}';
|
||
|
|
protected bool \$timestamps = {{timestamps}};
|
||
|
|
{{soft_deletes}}
|
||
|
|
|
||
|
|
{{properties}}
|
||
|
|
{{methods}}
|
||
|
|
}
|
||
|
|
PHP;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function getPropertyTemplate(): string
|
||
|
|
{
|
||
|
|
return <<<PHP
|
||
|
|
{{doc_block}}
|
||
|
|
private {{nullable}}{{property_type}} \${{property_name}};
|
||
|
|
|
||
|
|
PHP;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function getMethodTemplate(): string
|
||
|
|
{
|
||
|
|
return <<<PHP
|
||
|
|
{{method_content}}
|
||
|
|
|
||
|
|
PHP;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function getMigrationTemplate(): string
|
||
|
|
{
|
||
|
|
return <<<PHP
|
||
|
|
<?php
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
use Fendx\Database\Migration;
|
||
|
|
use Fendx\Database\Schema\Blueprint;
|
||
|
|
|
||
|
|
class {{class_name}} extends Migration
|
||
|
|
{
|
||
|
|
public function up(): void
|
||
|
|
{
|
||
|
|
\$this->schema->create('{{table_name}}', function (Blueprint \$table) {
|
||
|
|
{{up_methods}});
|
||
|
|
}
|
||
|
|
|
||
|
|
public function down(): void
|
||
|
|
{
|
||
|
|
\$this->schema->dropIfExists('{{table_name}}');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
PHP;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function getFactoryTemplate(): string
|
||
|
|
{
|
||
|
|
return <<<PHP
|
||
|
|
<?php
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace {{namespace}};
|
||
|
|
|
||
|
|
use Fendx\Database\Factories\Factory;
|
||
|
|
use {{model_namespace}}\{{model_class}};
|
||
|
|
|
||
|
|
class {{class_name}} extends Factory
|
||
|
|
{
|
||
|
|
protected string \$model = {{model_class}}::class;
|
||
|
|
|
||
|
|
public function definition(): array
|
||
|
|
{
|
||
|
|
return [
|
||
|
|
{{definition}};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
PHP;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function getSeederTemplate(): string
|
||
|
|
{
|
||
|
|
return <<<PHP
|
||
|
|
<?php
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace {{namespace}};
|
||
|
|
|
||
|
|
use Fendx\Database\Seeder;
|
||
|
|
use {{model_namespace}}\{{model_class}};
|
||
|
|
|
||
|
|
class {{class_name}} extends Seeder
|
||
|
|
{
|
||
|
|
public function run(): void
|
||
|
|
{
|
||
|
|
{{model_class}}::factory()->count(10)->create();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
PHP;
|
||
|
|
}
|
||
|
|
}
|