fix
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Wt\Core\App;
|
||||
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* Class ModuleCloner
|
||||
*
|
||||
* Обеспечивает функционал клонирования Bitrix модулей с автоматической
|
||||
* заменой идентификаторов и пространств имен.
|
||||
*/
|
||||
class ModuleCloner
|
||||
{
|
||||
protected array $excludePatterns = [
|
||||
'/\.git$/',
|
||||
'/\.module\.json/',
|
||||
'/clone\.php/',
|
||||
'/ModuleCloner\.php/',
|
||||
'/readme\.md/',
|
||||
'/src\/\.env$/',
|
||||
'/src\/config\.php$/',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly ModuleEntity $sourceEntity,
|
||||
private readonly ModuleEntity $targetEntity
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Основной метод клонирования модуля.
|
||||
*
|
||||
* @throws UnexpectedValueException
|
||||
*/
|
||||
public function clone(): void
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user