null]); } // Шаг 2: парсим HTML, показываем обнаруженные поля и черновик Blade public function analyze(Request $request): View { $request->validate([ 'layout_name' => 'required|string|max:100|regex:/^[a-z0-9_]+$/', 'layout_title' => 'required|string|max:255', 'html' => 'required|string', ]); $parsed = (new BlockLayoutParser)->parse($request->input('html')); return view('admin.blocks.wizard', [ 'parsed' => $parsed, 'layout_name' => $request->input('layout_name'), 'layout_title' => $request->input('layout_title'), 'original_html' => $request->input('html'), ]); } // Шаг 3: сохраняем файлы макета и Blade-шаблона public function generate(Request $request): RedirectResponse { $request->validate([ 'layout_name' => 'required|string|max:100|regex:/^[a-z0-9_]+$/', 'layout_title' => 'required|string|max:255', 'blade' => 'required|string', ]); $name = $request->input('layout_name'); $title = $request->input('layout_title'); // Защита: не перезаписываем встроенные макеты if (isset(BlockLayoutRegistry::all()[$name]) && !file_exists(app_path("Support/layouts/{$name}.php"))) { return back()->withInput()->withErrors(['layout_name' => 'Такое имя уже занято встроенным макетом.']); } // Собираем определение полей из submitted-данных формы $fields = $this->buildFieldsFromRequest($request); // Сохраняем app/Support/layouts/{name}.php $layoutPhp = $this->renderLayoutPhp($title, $fields); file_put_contents(app_path("Support/layouts/{$name}.php"), $layoutPhp); // Сохраняем resources/views/blocks/{name}.blade.php $bladeContent = "{{-- Blade-шаблон блока «{$title}». Сгенерирован мастером макетов. --}}\n" . trim($request->input('blade')) . "\n"; file_put_contents(resource_path("views/blocks/{$name}.blade.php"), $bladeContent); // Сбрасываем кеш скомпилированных вьюх array_map('unlink', glob(storage_path('framework/views/*.php'))); return redirect()->route('admin.blocks.create') ->with('success', "Макет «{$title}» создан. Теперь создайте блок на его основе."); } // Собирает поля из multipart-данных формы шага 2 private function buildFieldsFromRequest(Request $request): array { $isRepeater = (bool) $request->input('is_repeater', false); $rawFlatFields = $request->input('flat_fields', []); $flatFields = array_values(array_filter( array_map(fn($f) => [ 'name' => trim($f['name'] ?? ''), 'label' => trim($f['label'] ?? ''), 'type' => $f['type'] ?? 'text', ], $rawFlatFields), fn($f) => $f['name'] !== '' )); if (!$isRepeater) { return $flatFields; } // Repeater: плоские поля перед/после + repeater-поле $subRaw = $request->input('sub_fields', []); $subFields = array_values(array_filter( array_map(fn($f) => [ 'name' => trim($f['name'] ?? ''), 'label' => trim($f['label'] ?? ''), 'type' => $f['type'] ?? 'text', ], $subRaw), fn($f) => $f['name'] !== '' )); $repeaterField = [ 'name' => trim($request->input('repeater_name', 'items')), 'label' => trim($request->input('repeater_label', 'Элементы')), 'type' => 'repeater', 'sub_fields' => $subFields, ]; // Плоские поля (если есть) идут перед repeater return array_merge($flatFields, [$repeaterField]); } // Рендерит PHP-файл определения макета private function renderLayoutPhp(string $title, array $fields): string { $fieldsExport = $this->varExportPretty($fields, 1); return << {$this->exportStr($title)}, 'fields' => {$fieldsExport}, ]; PHP; } // Аккуратный var_export с отступами вместо стандартного private function varExportPretty(mixed $value, int $depth = 0): string { $pad = str_repeat(' ', $depth); $pad1 = str_repeat(' ', $depth + 1); if (is_string($value)) { return $this->exportStr($value); } if (!is_array($value)) { return var_export($value, true); } if (empty($value)) { return '[]'; } $isList = array_keys($value) === range(0, count($value) - 1); $lines = []; foreach ($value as $k => $v) { $key = $isList ? '' : $this->exportStr((string) $k) . ' => '; $lines[] = $pad1 . $key . $this->varExportPretty($v, $depth + 1); } return "[\n" . implode(",\n", $lines) . ",\n{$pad}]"; } private function exportStr(string $s): string { return "'" . addcslashes($s, "'\\") . "'"; } }