fix: UpdateLogicalRelationships 명령 개선

- use 문 파싱 추가하여 짧은 클래스명을 FQCN으로 변환
- self/static 자기 참조 관계 정상 처리
- Polymorphic 관계 지원 (morphTo, morphMany, morphOne)
- 클래스 존재 확인 및 안전한 에러 처리
- ::class 문자열 오류 수정

마이그레이션 실행 시 'Class BoardSetting::class not found' 에러 해결
This commit is contained in:
2025-10-14 22:26:15 +09:00
parent 14023e8160
commit 447710def8
3 changed files with 691 additions and 147 deletions

View File

@@ -85,21 +85,48 @@ private function getModelRelationshipsFromFile($file, string $className): array
$content = File::get($file->getRealPath());
$relationships = [];
// use 문 추출
$useStatements = $this->extractUseStatements($content);
// 현재 파일의 namespace 추출
$currentNamespace = $this->extractNamespace($content);
// 관계 메서드 패턴 검출
$patterns = [
'belongsTo' => '/public\s+function\s+(\w+)\s*\([^)]*\)\s*[^{]*{\s*return\s+\$this->belongsTo\s*\(\s*([^,\)]+)/',
'hasMany' => '/public\s+function\s+(\w+)\s*\([^)]*\)\s*[^{]*{\s*return\s+\$this->hasMany\s*\(\s*([^,\)]+)/',
'hasOne' => '/public\s+function\s+(\w+)\s*\([^)]*\)\s*[^{]*{\s*return\s+\$this->hasOne\s*\(\s*([^,\)]+)/',
'belongsToMany' => '/public\s+function\s+(\w+)\s*\([^)]*\)\s*[^{]*{\s*return\s+\$this->belongsToMany\s*\(\s*([^,\)]+)/',
'morphTo' => '/public\s+function\s+(\w+)\s*\([^)]*\)\s*[^{]*{\s*return\s+\$this->morphTo\s*\(/',
'morphMany' => '/public\s+function\s+(\w+)\s*\([^)]*\)\s*[^{]*{\s*return\s+\$this->morphMany\s*\(\s*([^,\)]+)/',
'morphOne' => '/public\s+function\s+(\w+)\s*\([^)]*\)\s*[^{]*{\s*return\s+\$this->morphOne\s*\(\s*([^,\)]+)/',
];
foreach ($patterns as $type => $pattern) {
if (preg_match_all($pattern, $content, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
// morphTo는 관련 모델이 없으므로 특별 처리
if ($type === 'morphTo') {
$relationships[] = [
'method' => $match[1],
'type' => $type,
'related_model' => '(Polymorphic)',
'foreign_key' => null,
'local_key' => null
];
continue;
}
// ::class 제거 및 따옴표 제거
$relatedModel = str_replace('::class', '', trim($match[2], '"\''));
// 완전한 클래스명으로 변환
$fullyQualifiedClass = $this->resolveClassName($relatedModel, $useStatements, $currentNamespace, $className);
$relationships[] = [
'method' => $match[1],
'type' => $type,
'related_model' => trim($match[2], '"\''),
'related_model' => $fullyQualifiedClass,
'foreign_key' => null,
'local_key' => null
];
@@ -110,6 +137,72 @@ private function getModelRelationshipsFromFile($file, string $className): array
return $relationships;
}
/**
* 파일 내용에서 use 문들을 추출
*/
private function extractUseStatements(string $content): array
{
$useStatements = [];
// use 문 패턴: use Full\Namespace\ClassName; 또는 use Full\Namespace\ClassName as Alias;
if (preg_match_all('/use\s+([^;]+);/', $content, $matches)) {
foreach ($matches[1] as $useStatement) {
// as 별칭 처리
if (strpos($useStatement, ' as ') !== false) {
[$fullClass, $alias] = array_map('trim', explode(' as ', $useStatement));
$useStatements[$alias] = $fullClass;
} else {
$fullClass = trim($useStatement);
$shortName = class_basename($fullClass);
$useStatements[$shortName] = $fullClass;
}
}
}
return $useStatements;
}
/**
* 파일 내용에서 namespace 추출
*/
private function extractNamespace(string $content): ?string
{
if (preg_match('/namespace\s+([^;]+);/', $content, $matches)) {
return trim($matches[1]);
}
return null;
}
/**
* 짧은 클래스명을 완전한 클래스명으로 변환
*/
private function resolveClassName(string $className, array $useStatements, ?string $currentNamespace, string $currentClassName): string
{
// 이미 완전한 클래스명인 경우 (백슬래시 포함)
if (strpos($className, '\\') !== false) {
return ltrim($className, '\\');
}
// self/static 처리 - 현재 클래스로 대체
if ($className === 'self' || $className === 'static') {
return $currentClassName;
}
// use 문에서 찾기
if (isset($useStatements[$className])) {
return $useStatements[$className];
}
// 같은 namespace에 있다고 가정
if ($currentNamespace) {
return $currentNamespace . '\\' . $className;
}
// 그 외의 경우 그대로 반환
return $className;
}
private function getModelRelationships(ReflectionClass $reflection, $model): array
{
$relationships = [];
@@ -200,14 +293,31 @@ private function updateLogicalDocument(array $relationships): void
$content .= "**모델**: `{$info['model']}`\n\n";
foreach ($info['relationships'] as $rel) {
$relatedTable = (new $rel['related_model'])->getTable();
$content .= "- **{$rel['method']}()**: {$rel['type']} → `{$relatedTable}`";
try {
// Polymorphic 관계는 특별 표시
if ($rel['related_model'] === '(Polymorphic)') {
$content .= "- **{$rel['method']}()**: {$rel['type']} → `(Polymorphic)`\n";
continue;
}
if ($rel['foreign_key']) {
$content .= " (FK: `{$rel['foreign_key']}`)";
// 관련 모델 클래스가 존재하는지 확인
if (!class_exists($rel['related_model'])) {
$this->warn("모델 클래스가 존재하지 않음: {$rel['related_model']}");
continue;
}
$relatedTable = (new $rel['related_model'])->getTable();
$content .= "- **{$rel['method']}()**: {$rel['type']} → `{$relatedTable}`";
if ($rel['foreign_key']) {
$content .= " (FK: `{$rel['foreign_key']}`)";
}
$content .= "\n";
} catch (\Exception $e) {
$this->warn("관계 처리 실패: {$rel['method']} - " . $e->getMessage());
continue;
}
$content .= "\n";
}
$content .= "\n";