revise late 2018 editorial articles
Build and deploy / deploy (push) Successful in 14s

This commit is contained in:
2026-07-31 10:09:53 +03:00
parent 2c7f9d37d7
commit c72a72b8f3
18 changed files with 2281 additions and 6 deletions
@@ -0,0 +1,64 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 1120" role="img" aria-labelledby="title desc">
<title id="title">Состояния изображения в форме Bitrix</title>
<desc id="desc">Вертикальная схема пути файла: выбор в браузере, multipart POST, запись CFile и обновление PREVIEW_PICTURE элемента инфоблока.</desc>
<defs>
<linearGradient id="bg" x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stop-color="#111827"/>
<stop offset="1" stop-color="#172554"/>
</linearGradient>
<filter id="shadow" x="-15%" y="-10%" width="130%" height="130%">
<feDropShadow dx="0" dy="10" stdDeviation="10" flood-color="#020617" flood-opacity=".28"/>
</filter>
<marker id="arrow" markerWidth="12" markerHeight="12" refX="6" refY="10" orient="auto">
<path d="M0,0 L12,0 L6,12 Z" fill="#7dd3fc"/>
</marker>
</defs>
<rect width="640" height="1120" rx="28" fill="url(#bg)"/>
<g font-family="Inter,Arial,sans-serif">
<text x="48" y="58" fill="#f8fafc" font-size="27" font-weight="700">Форма с изображением:</text>
<text x="48" y="91" fill="#f8fafc" font-size="27" font-weight="700">четыре состояния файла</text>
<text x="48" y="122" fill="#94a3b8" font-size="16">Preview не доказывает, что карточка уже обновлена.</text>
<g filter="url(#shadow)">
<rect x="48" y="160" width="544" height="142" rx="20" fill="#1e293b" stroke="#475569"/>
<rect x="48" y="378" width="544" height="142" rx="20" fill="#1e293b" stroke="#475569"/>
<rect x="48" y="596" width="544" height="142" rx="20" fill="#0f3a53" stroke="#38bdf8"/>
<rect x="48" y="814" width="544" height="142" rx="20" fill="#172554" stroke="#818cf8"/>
</g>
<circle cx="86" cy="202" r="20" fill="#334155"/>
<text x="86" y="209" text-anchor="middle" fill="#e2e8f0" font-size="16" font-weight="700">1</text>
<text x="120" y="207" fill="#f8fafc" font-size="22" font-weight="700">Браузер: File и preview</text>
<text x="80" y="248" fill="#cbd5e1" font-size="17">Пользователь видит выбранный файл.</text>
<text x="80" y="274" fill="#cbd5e1" font-size="17">Постоянного ID Bitrix ещё нет.</text>
<circle cx="86" cy="420" r="20" fill="#334155"/>
<text x="86" y="427" text-anchor="middle" fill="#e2e8f0" font-size="16" font-weight="700">2</text>
<text x="120" y="425" fill="#f8fafc" font-size="22" font-weight="700">Форма: multipart POST</text>
<text x="80" y="466" fill="#cbd5e1" font-size="17">Проверяем имя поля и UPLOAD_ERR.</text>
<text x="80" y="492" fill="#cbd5e1" font-size="17">Доказательство: ожидаемый $_FILES.</text>
<circle cx="86" cy="638" r="20" fill="#0c4a6e"/>
<text x="86" y="645" text-anchor="middle" fill="#e0f2fe" font-size="16" font-weight="700">3</text>
<text x="120" y="643" fill="#f8fafc" font-size="22" font-weight="700">CFile: запись в b_file</text>
<text x="80" y="684" fill="#dbeafe" font-size="17">CFile::SaveFile вернул положительный ID.</text>
<text x="80" y="710" fill="#dbeafe" font-size="17">Файл можно прочитать по этому ID.</text>
<circle cx="86" cy="856" r="20" fill="#3730a3"/>
<text x="86" y="863" text-anchor="middle" fill="#e0e7ff" font-size="16" font-weight="700">4</text>
<text x="120" y="861" fill="#f8fafc" font-size="22" font-weight="700">Элемент: PREVIEW_PICTURE</text>
<text x="80" y="902" fill="#dbeafe" font-size="17">CIBlockElement::Update вернул true.</text>
<text x="80" y="928" fill="#dbeafe" font-size="17">Свежая карточка читает новый ID.</text>
<path d="M320 302 V378" fill="none" stroke="#7dd3fc" stroke-width="4" marker-end="url(#arrow)"/>
<path d="M320 520 V596" fill="none" stroke="#7dd3fc" stroke-width="4" marker-end="url(#arrow)"/>
<path d="M320 738 V814" fill="none" stroke="#7dd3fc" stroke-width="4" marker-end="url(#arrow)"/>
<text x="344" y="348" fill="#bae6fd" font-size="15" font-weight="700">отправить</text>
<text x="344" y="566" fill="#bae6fd" font-size="15" font-weight="700">SaveFile</text>
<text x="344" y="784" fill="#bae6fd" font-size="15" font-weight="700">Update</text>
<rect x="48" y="1000" width="544" height="76" rx="16" fill="#0b1220" stroke="#334155"/>
<text x="76" y="1034" fill="#f8fafc" font-size="18" font-weight="700">Проверка после каждого перехода</text>
<text x="76" y="1060" fill="#cbd5e1" font-size="15">Искать расхождение на стрелке, а не рисовать новый preview.</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

@@ -0,0 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 960" role="img" aria-labelledby="title desc">
<title id="title">Замена legacy обработчика с проверкой и откатом</title>
<desc id="desc">Последовательность безопасной замены: снимок элемента до изменения, выбор старого или нового writer для одного ID, повторная выборка. При расхождении сначала выключается новый маршрут, а поле восстанавливается только после проверки снимка.</desc>
<defs>
<linearGradient id="bg" x1="0" x2="1" y1="0" y2="1"><stop stop-color="#0f172a"/><stop offset="1" stop-color="#172554"/></linearGradient>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%"><feDropShadow dx="0" dy="8" stdDeviation="9" flood-color="#020617" flood-opacity=".36"/></filter>
<marker id="arrow" markerWidth="12" markerHeight="12" refX="6" refY="10" orient="auto"><path d="M0,0 L12,0 L6,12 Z" fill="#7dd3fc"/></marker>
<marker id="warn" markerWidth="12" markerHeight="12" refX="6" refY="10" orient="auto"><path d="M0,0 L12,0 L6,12 Z" fill="#fbbf24"/></marker>
</defs>
<rect width="760" height="960" rx="28" fill="url(#bg)"/>
<g font-family="Inter,Arial,sans-serif">
<text x="52" y="62" fill="#f8fafc" font-size="29" font-weight="700">Один элемент, проверка, откат</text>
<text x="52" y="93" fill="#94a3b8" font-size="20">Откат маршрута и поля — разные действия.</text>
<g filter="url(#shadow)">
<rect x="116" y="142" width="528" height="114" rx="20" fill="#1e293b" stroke="#475569"/>
<rect x="116" y="332" width="528" height="136" rx="20" fill="#0f3a53" stroke="#38bdf8"/>
<rect x="116" y="544" width="528" height="104" rx="20" fill="#172554" stroke="#818cf8"/>
<rect x="116" y="724" width="528" height="160" rx="20" fill="#3f3012" stroke="#fbbf24"/>
</g>
<text x="148" y="184" fill="#f8fafc" font-size="22" font-weight="700">1. Снимок до изменения</text>
<text x="148" y="215" fill="#cbd5e1" font-size="20">ID · IBLOCK_ID · старый CODE · ожидаемый CODE</text>
<text x="148" y="377" fill="#f8fafc" font-size="22" font-weight="700">2. Маршрут одного вызова</text>
<text x="148" y="408" fill="#dbeafe" font-size="20">старый / новый writer · только выбранный ID</text>
<text x="148" y="438" fill="#dbeafe" font-size="20">Update(CODE) без соседних полей</text>
<text x="148" y="588" fill="#f8fafc" font-size="22" font-weight="700">3. Повторная выборка</text>
<text x="148" y="619" fill="#e0e7ff" font-size="20">CODE сопоставлен с ожиданием</text>
<text x="148" y="769" fill="#fef3c7" font-size="22" font-weight="700">4. При расхождении</text>
<text x="148" y="800" fill="#fde68a" font-size="20">сначала выключить новый маршрут</text>
<text x="148" y="830" fill="#fde68a" font-size="20">сверить снимок и текущее значение</text>
<text x="148" y="860" fill="#fde68a" font-size="20">после этого решать вопрос данных</text>
<path d="M380 256 V332" fill="none" stroke="#7dd3fc" stroke-width="4" marker-end="url(#arrow)"/>
<path d="M380 468 V544" fill="none" stroke="#7dd3fc" stroke-width="4" marker-end="url(#arrow)"/>
<path d="M380 648 V724" fill="none" stroke="#fbbf24" stroke-width="4" marker-end="url(#warn)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

@@ -0,0 +1,37 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 880" role="img" aria-labelledby="title desc">
<title id="title">Защищённый шов вокруг обновления CODE в Bitrix</title>
<desc id="desc">Вертикальная карта legacy-модуля: форма передаёт ID и CODE в выделенный CodeWriter. Он проверяет инфоблок, обновляет одно поле и читает элемент обратно. Шаблон и интеграция остаются за границей шва.</desc>
<defs>
<linearGradient id="bg" x1="0" x2="1" y1="0" y2="1"><stop stop-color="#111827"/><stop offset="1" stop-color="#172554"/></linearGradient>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%"><feDropShadow dx="0" dy="8" stdDeviation="9" flood-color="#020617" flood-opacity=".36"/></filter>
<marker id="arrow" markerWidth="12" markerHeight="12" refX="6" refY="10" orient="auto"><path d="M0,0 L12,0 L6,12 Z" fill="#7dd3fc"/></marker>
</defs>
<rect width="760" height="880" rx="28" fill="url(#bg)"/>
<g font-family="Inter,Arial,sans-serif">
<text x="52" y="62" fill="#f8fafc" font-size="29" font-weight="700">Маленький шов: только CODE</text>
<text x="52" y="93" fill="#94a3b8" font-size="20">Форма и интеграция не входят в одну функцию.</text>
<rect x="52" y="138" width="656" height="524" rx="26" fill="#0b1220" stroke="#38bdf8" stroke-width="2" stroke-dasharray="9 9"/>
<text x="78" y="174" fill="#7dd3fc" font-size="20" font-weight="700">ЗАЩИЩЁННЫЙ ШОВ</text>
<g filter="url(#shadow)">
<rect x="198" y="206" width="364" height="100" rx="18" fill="#1e293b" stroke="#475569"/>
<rect x="138" y="374" width="484" height="122" rx="18" fill="#0f3a53" stroke="#38bdf8"/>
<rect x="198" y="560" width="364" height="70" rx="18" fill="#172554" stroke="#818cf8"/>
</g>
<text x="380" y="245" text-anchor="middle" fill="#f8fafc" font-size="22" font-weight="700">Вход формы</text>
<text x="380" y="277" text-anchor="middle" fill="#cbd5e1" font-size="22">ID + новый CODE</text>
<text x="380" y="416" text-anchor="middle" fill="#f8fafc" font-size="23" font-weight="700">ProductCodeWriter</text>
<text x="380" y="447" text-anchor="middle" fill="#dbeafe" font-size="20">проверка инфоблока · Update(CODE)</text>
<text x="380" y="473" text-anchor="middle" fill="#dbeafe" font-size="20">повторная выборка</text>
<text x="380" y="603" text-anchor="middle" fill="#f8fafc" font-size="20" font-weight="700">Отчёт: changed / ошибка</text>
<path d="M380 306 V374" fill="none" stroke="#7dd3fc" stroke-width="4" marker-end="url(#arrow)"/>
<path d="M380 496 V560" fill="none" stroke="#7dd3fc" stroke-width="4" marker-end="url(#arrow)"/>
<rect x="52" y="714" width="294" height="108" rx="18" fill="#1e293b" stroke="#475569"/>
<rect x="414" y="714" width="294" height="108" rx="18" fill="#1e293b" stroke="#475569"/>
<text x="80" y="756" fill="#f8fafc" font-size="20" font-weight="700">Шаблон</text>
<text x="80" y="787" fill="#cbd5e1" font-size="20">вывод результата</text>
<text x="442" y="756" fill="#f8fafc" font-size="20" font-weight="700">Интеграция</text>
<text x="442" y="787" fill="#cbd5e1" font-size="20">следующий шаг</text>
<path d="M286 662 C250 679, 214 685, 200 714" fill="none" stroke="#64748b" stroke-width="3" marker-end="url(#arrow)"/>
<path d="M474 662 C510 679, 546 685, 560 714" fill="none" stroke="#64748b" stroke-width="3" marker-end="url(#arrow)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

@@ -0,0 +1,38 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 900" role="img" aria-labelledby="title desc">
<title id="title">Смешанная ответственность старого save.php</title>
<desc id="desc">Диаграмма показывает, что один save.php читает POST, меняет инфоблок, выводит HTML и запускает интеграцию. Поздняя ошибка не отменяет раннюю запись автоматически.</desc>
<defs>
<linearGradient id="bg" x1="0" x2="1" y1="0" y2="1"><stop stop-color="#18181b"/><stop offset="1" stop-color="#1e1b4b"/></linearGradient>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%"><feDropShadow dx="0" dy="8" stdDeviation="9" flood-color="#020617" flood-opacity=".4"/></filter>
<marker id="arrowBlue" markerWidth="12" markerHeight="12" refX="6" refY="10" orient="auto"><path d="M0,0 L12,0 L6,12 Z" fill="#7dd3fc"/></marker>
<marker id="arrowRed" markerWidth="12" markerHeight="12" refX="6" refY="10" orient="auto"><path d="M0,0 L12,0 L6,12 Z" fill="#fb7185"/></marker>
</defs>
<rect width="760" height="900" rx="28" fill="url(#bg)"/>
<g font-family="Inter,Arial,sans-serif">
<text x="52" y="62" fill="#f8fafc" font-size="29" font-weight="700">Когда save.php делает всё сразу</text>
<text x="52" y="93" fill="#a1a1aa" font-size="20">Ошибка формы не равна состоянию инфоблока.</text>
<g filter="url(#shadow)">
<rect x="180" y="146" width="400" height="138" rx="22" fill="#312e81" stroke="#818cf8" stroke-width="2"/>
<rect x="68" y="418" width="274" height="120" rx="18" fill="#1e293b" stroke="#475569"/>
<rect x="418" y="418" width="274" height="120" rx="18" fill="#0f3a53" stroke="#38bdf8"/>
<rect x="68" y="682" width="274" height="120" rx="18" fill="#3f1d2e" stroke="#fb7185"/>
<rect x="418" y="682" width="274" height="120" rx="18" fill="#3f1d2e" stroke="#fb7185"/>
</g>
<text x="380" y="197" text-anchor="middle" fill="#f8fafc" font-size="25" font-weight="700">save.php</text>
<text x="380" y="228" text-anchor="middle" fill="#ddd6fe" font-size="20">POST · API · HTML</text>
<text x="380" y="256" text-anchor="middle" fill="#ddd6fe" font-size="20">один файл, несколько состояний</text>
<text x="205" y="462" text-anchor="middle" fill="#f8fafc" font-size="21" font-weight="700">Вход формы</text>
<text x="205" y="493" text-anchor="middle" fill="#cbd5e1" font-size="22">ID · NAME · CODE</text>
<text x="555" y="462" text-anchor="middle" fill="#f8fafc" font-size="21" font-weight="700">Инфоблок</text>
<text x="555" y="493" text-anchor="middle" fill="#dbeafe" font-size="22">Update(CODE)</text>
<text x="205" y="726" text-anchor="middle" fill="#f8fafc" font-size="21" font-weight="700">HTML-ответ</text>
<text x="205" y="757" text-anchor="middle" fill="#fecdd3" font-size="20">сообщение формы</text>
<text x="555" y="726" text-anchor="middle" fill="#f8fafc" font-size="21" font-weight="700">Интеграция</text>
<text x="555" y="757" text-anchor="middle" fill="#fecdd3" font-size="20">отдельный эффект</text>
<path d="M300 284 L224 418" fill="none" stroke="#7dd3fc" stroke-width="4" marker-end="url(#arrowBlue)"/>
<path d="M460 284 L536 418" fill="none" stroke="#7dd3fc" stroke-width="4" marker-end="url(#arrowBlue)"/>
<path d="M205 538 V682" fill="none" stroke="#fb7185" stroke-width="4" marker-end="url(#arrowRed)"/>
<path d="M555 538 V682" fill="none" stroke="#fb7185" stroke-width="4" marker-end="url(#arrowRed)"/>
<text x="54" y="846" fill="#fda4af" font-size="20" font-weight="700">Поздний сбой не отменяет раннюю запись автоматически.</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

@@ -0,0 +1,56 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 1040" role="img" aria-labelledby="title desc">
<title id="title">Контракт legacy-формы с изображением</title>
<desc id="desc">Вертикальная диаграмма разделяет браузерный preview, передачу multipart POST, серверный выбор действия и новое чтение карточки Bitrix.</desc>
<defs>
<linearGradient id="bg" x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stop-color="#18181b"/>
<stop offset="1" stop-color="#1f2937"/>
</linearGradient>
<filter id="shadow" x="-15%" y="-10%" width="130%" height="130%">
<feDropShadow dx="0" dy="10" stdDeviation="10" flood-color="#030712" flood-opacity=".3"/>
</filter>
<marker id="yellow" markerWidth="12" markerHeight="12" refX="6" refY="10" orient="auto">
<path d="M0,0 L12,0 L6,12 Z" fill="#fbbf24"/>
</marker>
<marker id="blue" markerWidth="12" markerHeight="12" refX="6" refY="10" orient="auto">
<path d="M0,0 L12,0 L6,12 Z" fill="#60a5fa"/>
</marker>
</defs>
<rect width="640" height="1040" rx="28" fill="url(#bg)"/>
<g font-family="Inter,Arial,sans-serif">
<text x="48" y="58" fill="#f9fafb" font-size="27" font-weight="700">Legacy-форма:</text>
<text x="48" y="91" fill="#f9fafb" font-size="27" font-weight="700">preview не равен результату</text>
<text x="48" y="122" fill="#9ca3af" font-size="16">Каждый сигнал имеет одну роль и одного владельца.</text>
<g filter="url(#shadow)">
<rect x="48" y="166" width="544" height="190" rx="20" fill="#111827" stroke="#4b5563"/>
<rect x="48" y="460" width="544" height="210" rx="20" fill="#172554" stroke="#60a5fa"/>
<rect x="48" y="776" width="544" height="150" rx="20" fill="#0f172a" stroke="#4f46e5"/>
</g>
<text x="80" y="211" fill="#f9fafb" font-size="22" font-weight="700">1. Браузер и jQuery</text>
<text x="80" y="251" fill="#fef3c7" font-size="18" font-weight="700">CATALOG_PREVIEW</text>
<text x="80" y="277" fill="#d1d5db" font-size="16">Бинарный файл для multipart POST.</text>
<text x="80" y="310" fill="#93c5fd" font-size="18" font-weight="700">.js-photo-state и preview</text>
<text x="80" y="336" fill="#d1d5db" font-size="16">Только обратная связь для человека.</text>
<path d="M320 356 V460" fill="none" stroke="#fbbf24" stroke-width="4" marker-end="url(#yellow)"/>
<text x="344" y="420" fill="#fde68a" font-size="15" font-weight="700">отправить FormData</text>
<text x="80" y="505" fill="#f9fafb" font-size="22" font-weight="700">2. PHP и Bitrix</text>
<text x="80" y="547" fill="#bfdbfe" font-size="18" font-weight="700">Проверить multipart POST</text>
<text x="80" y="573" fill="#dbeafe" font-size="16">Имя поля, upload error, размер файла.</text>
<text x="80" y="610" fill="#bfdbfe" font-size="18" font-weight="700">Выбрать одну ветку</text>
<text x="80" y="636" fill="#dbeafe" font-size="16">Новый файл, оставить или удалить.</text>
<path d="M320 670 V776" fill="none" stroke="#60a5fa" stroke-width="4" marker-end="url(#blue)"/>
<text x="344" y="734" fill="#bfdbfe" font-size="15" font-weight="700">вернуть результат</text>
<text x="80" y="821" fill="#f9fafb" font-size="22" font-weight="700">3. Свежая карточка</text>
<text x="80" y="861" fill="#c7d2fe" font-size="17">CIBlockElement::Update вернул true.</text>
<text x="80" y="887" fill="#c7d2fe" font-size="17">Новое чтение подтверждает ID изображения.</text>
<rect x="48" y="956" width="544" height="46" rx="14" fill="#0b1220" stroke="#374151"/>
<text x="76" y="986" fill="#e5e7eb" font-size="15">Критерий: один POST, одна ветка, затем новая страница.</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

@@ -0,0 +1,34 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 900" role="img" aria-labelledby="title desc">
<title id="title">Трасса фальшиво-зелёного сценария PHP</title>
<desc id="desc">Unit-тест подменяет базу и HTTP-клиент, поэтому не видит ошибки конфигурации. Интеграционный маршрут проходит через PDO, тестовую базу, локальный callback и связывает шаги request ID.</desc>
<defs>
<linearGradient id="bg" x1="0" x2="1" y1="0" y2="1"><stop stop-color="#172942"/><stop offset="1" stop-color="#322f55"/></linearGradient>
<marker id="arrowBlue" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto"><path d="M0,0 L10,5 L0,10z" fill="#8ed7ff"/></marker>
<marker id="arrowGreen" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto"><path d="M0,0 L10,5 L0,10z" fill="#7ce3b9"/></marker>
</defs>
<rect width="720" height="900" rx="30" fill="url(#bg)"/>
<text x="52" y="65" fill="#fff" font-family="Arial, sans-serif" font-size="34" font-weight="700">Где появляется ложная зелень</text>
<text x="52" y="102" fill="#c3d2e8" font-family="Arial, sans-serif" font-size="22">Сначала видим границу, потом её проверяем</text>
<rect x="55" y="150" width="610" height="165" rx="20" fill="#414366" stroke="#d2c4ff" stroke-width="3"/>
<text x="86" y="199" fill="#fff" font-family="Arial, sans-serif" font-size="27" font-weight="700">UNIT: заранее заданный успех</text>
<text x="86" y="239" fill="#e5e0ff" font-family="Arial, sans-serif" font-size="21">Подмены не читают DSN, не делают SQL</text>
<text x="86" y="273" fill="#e5e0ff" font-family="Arial, sans-serif" font-size="21">и не видят cURL или HTTP status.</text>
<path d="M360 315 V378" stroke="#8ed7ff" stroke-width="5" marker-end="url(#arrowBlue)"/>
<rect x="55" y="398" width="610" height="210" rx="20" fill="#1e4260" stroke="#8ed7ff" stroke-width="3"/>
<text x="86" y="445" fill="#ddf3ff" font-family="Arial, sans-serif" font-size="27" font-weight="700">Короткая трасса</text>
<rect x="92" y="477" width="110" height="64" rx="12" fill="#305d7d"/><text x="147" y="517" text-anchor="middle" fill="#fff" font-family="Arial, sans-serif" font-size="21" font-weight="700">config</text>
<path d="M202 509 H255" stroke="#8ed7ff" stroke-width="4" marker-end="url(#arrowBlue)"/>
<rect x="267" y="477" width="90" height="64" rx="12" fill="#28604f"/><text x="312" y="517" text-anchor="middle" fill="#fff" font-family="Arial, sans-serif" font-size="21" font-weight="700">PDO</text>
<path d="M357 509 H410" stroke="#8ed7ff" stroke-width="4" marker-end="url(#arrowBlue)"/>
<rect x="422" y="477" width="90" height="64" rx="12" fill="#5b4d87"/><text x="467" y="517" text-anchor="middle" fill="#fff" font-family="Arial, sans-serif" font-size="21" font-weight="700">cURL</text>
<path d="M512 509 H555" stroke="#8ed7ff" stroke-width="4" marker-end="url(#arrowBlue)"/>
<rect x="567" y="477" width="66" height="64" rx="12" fill="#2a6175"/><text x="600" y="517" text-anchor="middle" fill="#fff" font-family="Arial, sans-serif" font-size="19" font-weight="700">202</text>
<text x="92" y="576" fill="#c6e3f4" font-family="Arial, sans-serif" font-size="19">TEST_ vars</text>
<text x="267" y="576" fill="#c6e3f4" font-family="Arial, sans-serif" font-size="19">test DB</text>
<text x="422" y="576" fill="#c6e3f4" font-family="Arial, sans-serif" font-size="19">local callback</text>
<rect x="55" y="667" width="610" height="170" rx="20" fill="#14372f" stroke="#7ce3b9" stroke-width="3"/>
<text x="86" y="716" fill="#e5fff3" font-family="Arial, sans-serif" font-size="27" font-weight="700">INTEGRATION: реальный маршрут</text>
<text x="86" y="757" fill="#c6efdc" font-family="Arial, sans-serif" font-size="22">registration-test-42 связывает шаги</text>
<text x="86" y="792" fill="#c6efdc" font-family="Arial, sans-serif" font-size="22">и оставляет контролируемые следы.</text>
<path d="M360 608 V660" stroke="#7ce3b9" stroke-width="4" stroke-dasharray="9 8" marker-end="url(#arrowGreen)"/>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

@@ -0,0 +1,27 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 900" role="img" aria-labelledby="title desc">
<title id="title">Контракт интеграционного теста PHP с базой данных</title>
<desc id="desc">PHPUnit передаёт данные в PDO-репозиторий, тот записывает и читает отдельную тестовую базу данных, после чего транзакция откатывается.</desc>
<defs>
<linearGradient id="bg" x1="0" x2="1" y1="0" y2="1"><stop stop-color="#11213a"/><stop offset="1" stop-color="#1d3655"/></linearGradient>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto"><path d="M0,0 L10,5 L0,10z" fill="#84d5ff"/></marker>
</defs>
<rect width="720" height="900" rx="30" fill="url(#bg)"/>
<text x="52" y="65" fill="#f3f8ff" font-family="Arial, sans-serif" font-size="34" font-weight="700">Контракт: запись и чтение</text>
<text x="52" y="102" fill="#bfd0e4" font-family="Arial, sans-serif" font-size="22">Один путь через PDO и отдельную test DB</text>
<rect x="62" y="160" width="596" height="126" rx="18" fill="#203d60" stroke="#84d5ff" stroke-width="3"/>
<text x="360" y="211" text-anchor="middle" fill="#fff" font-family="Arial, sans-serif" font-size="30" font-weight="700">PHPUnit</text>
<text x="360" y="250" text-anchor="middle" fill="#d3efff" font-family="Arial, sans-serif" font-size="23">вход, ожидание, фикстура</text>
<path d="M360 286 V350" stroke="#84d5ff" stroke-width="5" marker-end="url(#arrow)"/>
<rect x="62" y="370" width="596" height="142" rx="18" fill="#183952" stroke="#72e0bb" stroke-width="3"/>
<text x="360" y="425" text-anchor="middle" fill="#fff" font-family="Arial, sans-serif" font-size="30" font-weight="700">PDO-репозиторий</text>
<text x="360" y="464" text-anchor="middle" fill="#cbf5e6" font-family="Arial, sans-serif" font-size="23">SQL, типы, преобразование</text>
<path d="M360 512 V575" stroke="#84d5ff" stroke-width="5" marker-end="url(#arrow)"/>
<rect x="62" y="595" width="596" height="142" rx="18" fill="#3c365d" stroke="#bdadff" stroke-width="3"/>
<ellipse cx="186" cy="638" rx="57" ry="19" fill="#887ec2"/>
<path d="M129 638 V690 C129 716 243 716 243 690 V638" fill="#645997"/>
<ellipse cx="186" cy="690" rx="57" ry="19" fill="#645997"/>
<text x="421" y="654" text-anchor="middle" fill="#fff" font-family="Arial, sans-serif" font-size="29" font-weight="700">test DB</text>
<text x="421" y="695" text-anchor="middle" fill="#eeeaff" font-family="Arial, sans-serif" font-size="22">отдельная схема</text>
<rect x="62" y="778" width="596" height="72" rx="16" fill="#10243a" stroke="#456e95" stroke-width="2"/>
<text x="92" y="823" fill="#d8efff" font-family="Arial, sans-serif" font-size="23">config → INSERT → SELECT → rollback</text>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@@ -0,0 +1,38 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 900" role="img" aria-labelledby="title desc">
<title id="title">Граница unit и integration тестов PHP</title>
<desc id="desc">Unit-тест заменяет репозиторий подставным объектом и проверяет правило сервиса. Integration-тест проходит через настоящий PDO или HTTP-адаптер к тестовому ресурсу.</desc>
<defs>
<linearGradient id="bg" x1="0" x2="1" y1="0" y2="1"><stop stop-color="#152943"/><stop offset="1" stop-color="#253350"/></linearGradient>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto"><path d="M0,0 L10,5 L0,10z" fill="#8fd8ff"/></marker>
</defs>
<rect width="720" height="900" rx="30" fill="url(#bg)"/>
<text x="52" y="65" fill="#f8fbff" font-family="Arial, sans-serif" font-size="34" font-weight="700">Граница: правило и адаптер</text>
<text x="52" y="102" fill="#bfd1e7" font-family="Arial, sans-serif" font-size="22">Подмена полезна, но не проверяет ресурс</text>
<rect x="55" y="150" width="610" height="250" rx="20" fill="#1d3a59" stroke="#8fd8ff" stroke-width="3"/>
<text x="86" y="197" fill="#d5efff" font-family="Arial, sans-serif" font-size="27" font-weight="700">UNIT</text>
<rect x="88" y="228" width="158" height="86" rx="14" fill="#2e587b"/>
<text x="167" y="279" text-anchor="middle" fill="#fff" font-family="Arial, sans-serif" font-size="23" font-weight="700">TestCase</text>
<path d="M246 271 H321" stroke="#8fd8ff" stroke-width="4" marker-end="url(#arrow)"/>
<rect x="335" y="228" width="145" height="86" rx="14" fill="#27634f"/>
<text x="407" y="279" text-anchor="middle" fill="#fff" font-family="Arial, sans-serif" font-size="22" font-weight="700">сервис</text>
<path d="M480 271 H540" stroke="#d7c8ff" stroke-width="4" stroke-dasharray="9 8" marker-end="url(#arrow)"/>
<rect x="550" y="228" width="85" height="86" rx="14" fill="#564b86"/>
<text x="592" y="279" text-anchor="middle" fill="#fff" font-family="Arial, sans-serif" font-size="22" font-weight="700">fake</text>
<text x="86" y="361" fill="#b9d8f2" font-family="Arial, sans-serif" font-size="21">Проверяет локальное решение. SQL и HTTP остаются снаружи.</text>
<rect x="55" y="455" width="610" height="328" rx="20" fill="#193f36" stroke="#78e1ba" stroke-width="3"/>
<text x="86" y="503" fill="#cff9e6" font-family="Arial, sans-serif" font-size="27" font-weight="700">INTEGRATION</text>
<rect x="88" y="548" width="158" height="86" rx="14" fill="#2e587b"/>
<text x="167" y="599" text-anchor="middle" fill="#fff" font-family="Arial, sans-serif" font-size="23" font-weight="700">TestCase</text>
<path d="M246 591 H320" stroke="#8fd8ff" stroke-width="4" marker-end="url(#arrow)"/>
<rect x="335" y="548" width="145" height="86" rx="14" fill="#27634f"/>
<text x="407" y="585" text-anchor="middle" fill="#fff" font-family="Arial, sans-serif" font-size="22" font-weight="700">PDO / cURL</text>
<text x="407" y="612" text-anchor="middle" fill="#d5f7e8" font-family="Arial, sans-serif" font-size="18">адаптер</text>
<path d="M480 591 H540" stroke="#78e1ba" stroke-width="4" marker-end="url(#arrow)"/>
<rect x="550" y="548" width="85" height="86" rx="14" fill="#265b63"/>
<text x="592" y="584" text-anchor="middle" fill="#fff" font-family="Arial, sans-serif" font-size="18" font-weight="700">test</text>
<text x="592" y="611" text-anchor="middle" fill="#d6f4ff" font-family="Arial, sans-serif" font-size="18">DB/HTTP</text>
<text x="86" y="688" fill="#c9f0df" font-family="Arial, sans-serif" font-size="21">Проверяет договор с контролируемым ресурсом.</text>
<text x="86" y="726" fill="#a9d8c5" font-family="Arial, sans-serif" font-size="19">Один настоящий переход за тест — понятная причина падения.</text>
<line x1="360" y1="400" x2="360" y2="455" stroke="#7890ad" stroke-width="3" stroke-dasharray="8 8"/>
<text x="387" y="436" fill="#b8c9dc" font-family="Arial, sans-serif" font-size="18">граница подмены</text>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

@@ -0,0 +1,58 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 1130" role="img" aria-labelledby="title desc">
<title id="title">Порядок jQuery и legacy-плагина в Webpack production</title>
<desc id="desc">Вертикальная схема показывает правильный порядок через runtime, bootstrap с window.jQuery и runtime require плагина, а ниже плохой путь со статическим импортом.</desc>
<defs>
<linearGradient id="bg" x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stop-color="#0f172a"/>
<stop offset="1" stop-color="#312e81"/>
</linearGradient>
<filter id="shadow" x="-15%" y="-10%" width="130%" height="130%">
<feDropShadow dx="0" dy="10" stdDeviation="10" flood-color="#020617" flood-opacity=".32"/>
</filter>
<marker id="good" markerWidth="12" markerHeight="12" refX="6" refY="10" orient="auto">
<path d="M0,0 L12,0 L6,12 Z" fill="#34d399"/>
</marker>
<marker id="bad" markerWidth="12" markerHeight="12" refX="6" refY="10" orient="auto">
<path d="M0,0 L12,0 L6,12 Z" fill="#fb7185"/>
</marker>
</defs>
<rect width="640" height="1130" rx="28" fill="url(#bg)"/>
<g font-family="Inter,Arial,sans-serif">
<text x="48" y="58" fill="#f8fafc" font-size="27" font-weight="700">Webpack production:</text>
<text x="48" y="91" fill="#f8fafc" font-size="27" font-weight="700">порядок до legacy-плагина</text>
<text x="48" y="122" fill="#c4b5fd" font-size="16">ProvidePlugin не заменяет явный момент window.jQuery.</text>
<text x="48" y="170" fill="#a7f3d0" font-size="21" font-weight="700">Рабочий путь</text>
<g filter="url(#shadow)">
<rect x="48" y="198" width="544" height="116" rx="20" fill="#064e3b" stroke="#34d399"/>
<rect x="48" y="374" width="544" height="116" rx="20" fill="#065f46" stroke="#34d399"/>
<rect x="48" y="550" width="544" height="116" rx="20" fill="#047857" stroke="#34d399"/>
<rect x="48" y="726" width="544" height="116" rx="20" fill="#064e3b" stroke="#34d399"/>
</g>
<text x="80" y="242" fill="#d1fae5" font-size="21" font-weight="700">1. runtime и vendors chunk</text>
<text x="80" y="278" fill="#a7f3d0" font-size="16">В фактическом графе есть один jquery.</text>
<path d="M320 314 V374" fill="none" stroke="#34d399" stroke-width="4" marker-end="url(#good)"/>
<text x="80" y="418" fill="#d1fae5" font-size="21" font-weight="700">2. bootstrap</text>
<text x="80" y="454" fill="#a7f3d0" font-size="16">window.jQuery = $ и проверка экземпляра.</text>
<path d="M320 490 V550" fill="none" stroke="#34d399" stroke-width="4" marker-end="url(#good)"/>
<text x="80" y="594" fill="#ecfdf5" font-size="21" font-weight="700">3. runtime require плагина</text>
<text x="80" y="630" fill="#d1fae5" font-size="16">Плагин читает уже существующий глобал.</text>
<path d="M320 666 V726" fill="none" stroke="#34d399" stroke-width="4" marker-end="url(#good)"/>
<text x="80" y="770" fill="#d1fae5" font-size="21" font-weight="700">4. ready handler формы</text>
<text x="80" y="806" fill="#a7f3d0" font-size="16">DOM есть, метод плагина доступен один раз.</text>
<text x="48" y="900" fill="#fecdd3" font-size="21" font-weight="700">Плохой путь</text>
<g filter="url(#shadow)">
<rect x="48" y="928" width="544" height="120" rx="20" fill="#4c0519" stroke="#fb7185"/>
</g>
<text x="80" y="970" fill="#ffe4e6" font-size="21" font-weight="700">static import legacy-плагина</text>
<text x="80" y="1006" fill="#fecdd3" font-size="16">Плагин исполняется раньше тела bootstrap.</text>
<text x="80" y="1032" fill="#fecdd3" font-size="16">window.jQuery ещё пуст, форма падает в production.</text>
<rect x="48" y="1074" width="544" height="34" rx="12" fill="#111827" stroke="#4f46e5"/>
<text x="70" y="1097" fill="#c7d2fe" font-size="13">Проверка: stats.json, чистый dist, глобал до require.</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB