info('🔄 논리적 관계 문서 업데이트 시작...'); $relationships = $this->extractModelRelationships(); $this->updateLogicalDocument($relationships); $this->info('✅ 논리적 관계 문서 업데이트 완료!'); } private function extractModelRelationships(): array { $relationships = []; $modelPath = app_path('Models'); // 모든 모델 파일 스캔 $modelFiles = File::allFiles($modelPath); foreach ($modelFiles as $file) { if ($file->getExtension() !== 'php') continue; $className = $this->getClassNameFromFile($file); if (!$className || !class_exists($className)) continue; try { $reflection = new ReflectionClass($className); // 모델이 Eloquent Model인지 확인 if (!$reflection->isSubclassOf(\Illuminate\Database\Eloquent\Model::class)) { continue; } // Abstract 클래스 건너뛰기 if ($reflection->isAbstract()) { continue; } // 테이블 이름 직접 추출 $tableName = $this->getTableNameFromModel($className, $reflection); if (!$tableName) continue; $relationships[$tableName] = [ 'model' => $className, 'relationships' => $this->getModelRelationshipsFromFile($file, $className) ]; } catch (\Exception $e) { $this->warn("모델 분석 실패: {$className} - " . $e->getMessage()); continue; } } return $relationships; } private function getTableNameFromModel(string $className, ReflectionClass $reflection): ?string { // 클래스명에서 테이블명 추정 $modelName = class_basename($className); $tableName = strtolower(preg_replace('/(?getRealPath()); $relationships = []; // 관계 메서드 패턴 검출 $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*([^,\)]+)/', ]; foreach ($patterns as $type => $pattern) { if (preg_match_all($pattern, $content, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { $relationships[] = [ 'method' => $match[1], 'type' => $type, 'related_model' => trim($match[2], '"\''), 'foreign_key' => null, 'local_key' => null ]; } } } return $relationships; } private function getModelRelationships(ReflectionClass $reflection, $model): array { $relationships = []; $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC); foreach ($methods as $method) { if ($method->getDeclaringClass()->getName() !== $reflection->getName()) { continue; } try { // 관계 메서드 호출해서 타입 확인 $result = $method->invoke($model); if ($this->isRelationshipMethod($result)) { $relationships[] = [ 'method' => $method->getName(), 'type' => $this->getRelationshipType($result), 'related_model' => get_class($result->getRelated()), 'foreign_key' => $this->getForeignKey($result), 'local_key' => $this->getLocalKey($result) ]; } } catch (\Exception $e) { // 관계 메서드가 아니거나 호출 실패 시 건너뛰기 continue; } } return $relationships; } private function isRelationshipMethod($result): bool { return $result instanceof \Illuminate\Database\Eloquent\Relations\Relation; } private function getRelationshipType($relation): string { $className = get_class($relation); return class_basename($className); } private function getForeignKey($relation): ?string { return method_exists($relation, 'getForeignKeyName') ? $relation->getForeignKeyName() : null; } private function getLocalKey($relation): ?string { return method_exists($relation, 'getLocalKeyName') ? $relation->getLocalKeyName() : null; } private function getClassNameFromFile($file): ?string { $content = File::get($file->getRealPath()); if (!preg_match('/namespace\s+([^;]+);/', $content, $namespaceMatches)) { return null; } if (!preg_match('/class\s+(\w+)/', $content, $classMatches)) { return null; } return $namespaceMatches[1] . '\\' . $classMatches[1]; } private function updateLogicalDocument(array $relationships): void { $documentPath = base_path('LOGICAL_RELATIONSHIPS.md'); $timestamp = now()->format('Y-m-d H:i:s'); $content = "# 논리적 데이터베이스 관계 문서\n\n"; $content .= "> **자동 생성**: {$timestamp}\n"; $content .= "> **소스**: Eloquent 모델 관계 분석\n\n"; $content .= "## 📊 모델별 관계 현황\n\n"; foreach ($relationships as $tableName => $info) { if (empty($info['relationships'])) continue; $content .= "### {$tableName}\n"; $content .= "**모델**: `{$info['model']}`\n\n"; foreach ($info['relationships'] as $rel) { $relatedTable = (new $rel['related_model'])->getTable(); $content .= "- **{$rel['method']}()**: {$rel['type']} → `{$relatedTable}`"; if ($rel['foreign_key']) { $content .= " (FK: `{$rel['foreign_key']}`)"; } $content .= "\n"; } $content .= "\n"; } File::put($documentPath, $content); $this->info("📄 문서 업데이트: {$documentPath}"); } }