copyStructure(); $this->fixContent(); } /** * Копирует структуру папок и файлов из источника в цель. */ protected function copyStructure(): void { $this->copyFolderSafe( $this->sourceEntity->getPath(), $this->targetEntity->getPath() ); } /** * Рекурсивное копирование папки с фильтрацией и динамическим переименованием. */ protected function copyFolderSafe(string $source, string $destination): void { if ($this->shouldExclude($source)) { echo "Skip: $source" . PHP_EOL; return; } if (is_dir($source)) { $destination = $this->ensureDirectory($destination); if ($destination === null) { return; } $dir = dir($source); while (false !== ($entry = $dir->read())) { if ($entry !== '.' && $entry !== '..') { $srcPath = $source . DIRECTORY_SEPARATOR . $entry; $dstPath = $destination . DIRECTORY_SEPARATOR . $entry; $dstPath = $this->renamePathSegment($dstPath); $this->copyFolderSafe($srcPath, $dstPath); } } $dir->close(); } else { $this->copyFileSafe($source, $destination); } } /** * Копирует файл с сохранением времени модификации. */ protected function copyFileSafe(string $source, string $destination): void { $time = filemtime($source); if (file_exists($destination) && filemtime($destination) >= $time) { return; } if (copy($source, $destination)) { touch($destination, $time); } } /** * Создает директорию, если она отсутствует. */ protected function ensureDirectory(string $path): ?string { if (file_exists($path)) { if (is_dir($path)) { return $path; } unlink($path); } return mkdir($path, 0777, true) ? $path : null; } /** * Выполняет массовую замену строк в PHP файлах целевого модуля. */ protected function fixContent(): void { $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($this->targetEntity->getPath()), RecursiveIteratorIterator::LEAVES_ONLY ); foreach ($iterator as $file) { if ($file->isFile() && $file->getExtension() === 'php') { $path = $file->getPathname(); $content = file_get_contents($path); $original = $content; $replacements = $this->getReplacements(); foreach ($replacements as $search => $replace) { $content = str_replace($search, $replace, $content); } if ($original !== $content) { file_put_contents($path, $content); echo "Fixed: $path" . PHP_EOL; } } } } /** * Формирует карту замен на основе сущностей модулей. */ protected function getReplacements(): array { $s = $this->sourceEntity; $t = $this->targetEntity; $productS = ucfirst($s->getProductCode()); $productT = ucfirst($t->getProductCode()); $companyS = ucfirst($s->getCompanyCode()); $companyT = ucfirst($t->getCompanyCode()); return [ $s->getLangPrefix() => $t->getLangPrefix(), $s->getId() => $t->getId(), $s->getPrefix() => $t->getPrefix(), $s->getNamespace() => $t->getNamespace(), "$productS: $productS" => "$productT: $productT", $productS => $productT, $companyS => $companyT, $s->getProductCode() => $t->getProductCode(), $s->getCompanyCode() => $t->getCompanyCode(), ]; } /** * Переименовывает сегменты пути (папки) в соответствии с именами модулей. */ protected function renamePathSegment(string $path): string { $sourceLast = collect(explode('.', $this->sourceEntity->getId()))->last(); $targetLast = collect(explode('.', $this->targetEntity->getId()))->last(); $path = str_replace(ucfirst($sourceLast), ucfirst($targetLast), $path); $path = str_replace(strtolower($sourceLast), strtolower($targetLast), $path); return $path; } /** * Проверяет, должен ли файл/папка быть исключен из копирования. */ protected function shouldExclude(string $path): bool { foreach ($this->excludePatterns as $pattern) { if (preg_match($pattern, $path)) { return true; } } return false; } }