<?xml version="1.0"?>
<rss version="2.0"><channel><title/><link>https://opencartforum.com/en/blogs/245-tutorial/</link><description/><language>en</language><item><title>&#x420;&#x43E;&#x431;&#x438;&#x43C;&#x43E; &#x443;&#x43B;&#x44C;&#x442;&#x438;&#x43C;&#x430;&#x442;&#x438;&#x432;&#x43D;&#x438;&#x439; &#x43F;&#x43E;&#x448;&#x443;&#x43A; &#x434;&#x43B;&#x44F; Opencart</title><link>https://opencartforum.com/blogs/entry/554-robimo-ultimativniy-poshuk-dlya-opencart/</link><description><![CDATA[<p>
	Думаю, всім зрозуміло, що коли асортимент магазину сягає десятків тисяч і більше, важливо мати зручний пошук на сайті. Стандартний LIKE-подібний пошук на великих обсягах не справляється ні зі швидкістю, ні зі зручністю відповіді. Я розповім, як зробити пошуковий механізм на основі систем, створених спеціально для пошуку.
</p>

<p>
	 
</p>

<p>
	<strong>Почну відразу з можливостей пошуку</strong>.
</p>

<p>
	 
</p>

<p>
	1. Пошук з урахуванням помилок
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">чихол -&gt; чохол</span></pre>

<p>
	 
</p>

<p>
	Пошук артикулів у різних варіаціях:
</p>

<pre class="ipsCode">SKU.1234 -&gt; SKU-1234 -&gt; SKU1234</pre>

<p>
	 
</p>

<p>
	2. Неправильна розкладка клавіатури:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">xj[jk -&gt; чохол</span></pre>

<p>
	 
</p>

<p>
	3. Пошук з урахуванням морфології мови: рід, число, відмінок:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">сумка ноутбук -&gt; сумки для ноутбуків</span></pre>

<p>
	 
</p>

<p>
	4. Семантичний пошук.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">мікрохвильовка Samsung -&gt; мікрохвильова піч Samsung

браслет мед макс -&gt; браслет Mad Max </span></pre>

<p>
	 
</p>

<p>
	До цього були показані приклади лексичного пошуку, тобто пошуку за конкретними символами. Семантичний (векторний) пошук шукає за смисловою схожістю. Даний підхід пов'язаний з великими мовними моделями. Тренуючись на купі інформації, LLM навчилися класифікувати поняття за тисячами факторів. Коли ми індексуємо наші товари, ми не зберігаємо їх у вигляді ключових слів, ми створюємо векторне (математичне) представлення слів, фраз. Сам вектор являє собою масив float значень. Якими саме будуть значення і їх розмірність безпосередньо залежить від моделі. Пошуковий запит також перетворюється на вектор і за допомогою математичних виразів ми перевіряємо близькість наших векторів, тобто наскільки семантично близькі поняття.
</p>

<p>
	<span></span>
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" href="https://opencartforum.com/storage/attachment/monthly_2025_09/vektor.png.cdbcb8f0f7eea50bd69a45d9c9cbb507.png" data-fileid="829370" data-fileext="png" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="829370" data-ratio="47.90" width="1000" alt="vektor.thumb.png.7af9a5cc6276e73d6cd50ff9a69ebbff.png" data-src="https://opencartforum.com/storage/attachment/monthly_2025_09/vektor.thumb.png.7af9a5cc6276e73d6cd50ff9a69ebbff.png" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	 
</p>

<p>
	Для використання семантичного пошуку вам не потрібно завантажувати мовні моделі на десятки гігабайт і мати топову відеокарту, все працює на звичайному VPS.
</p>

<p>
	 
</p>

<p>
	<strong>Які функції реалізовані</strong>:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="829194" href="https://opencartforum.com/storage/attachment/monthly_2025_09/feature.png.f364aa6420192aaa2821bdb6b9712532.png" rel=""><img alt="feature.thumb.png.af7843a22cc9701b19a5c3181601cbf7.png" class="ipsImage ipsImage_thumbnailed" data-fileid="829194" data-ratio="89.61" style="height:auto;" width="837" data-src="https://opencartforum.com/storage/attachment/monthly_2025_09/feature.thumb.png.af7843a22cc9701b19a5c3181601cbf7.png" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	 
</p>

<p>
	 
</p>

<ul>
	<li>
		Спливаюче вікно з автодоповненням
	</li>
	<li>
		Пошукові підказки:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">чохол:
чохол для телефону
чохол для ноутбука </span></pre>

<p>
	 
</p>

<ul>
	<li>
		категорії із зазначенням кількості знайдених товарів
	</li>
</ul>

<p>
	Тут відображаються не категорії, які збіглися із запитом пошуку, а категорії товарів, які відповідають умовам пошуку.
</p>

<p>
	 
</p>

<ul>
	<li>
		товари
	</li>
	<li>
		Ви мали на увазі
	</li>
</ul>

<p>
	Коли користувач зробив помилку в пошуковому запиті, він бачить виправлене слово: можливо, ви мали на увазі. Ця функція реалізована технічно, але не виведена, щоб не перевантажувати пошуковий інтерфейс.
</p>

<p>
	<br />
	<strong>З чого складається дане рішення:</strong>
</p>

<ol>
	<li>
		Пошуковий движок на вашому хостингу: Opensearch (форк ElasticSearch), Typesense або Meilisearch.
	</li>
	<li>
		API додаток на піддомені на основі Laravel.
	</li>
	<li>
		Embedding API
	</li>
	<li>
		HTTP-клієнт для <abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом"><abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом">Opencart</abbr></abbr> з модулем пошуку.
	</li>
</ol>

<p>
	 
</p>

<p>
	Наведені вище можливості пошуку вказані для Opensearch. Далі також буде описуватися саме цей движок. Готові приклади для інших движків ви зможете знайти в <a href="https://github.com/ozzzi/api-opencart" rel="external nofollow">репозиторії проекту</a>.
</p>

<p>
	 
</p>

<p>
	Порівняння можливостей движків:
</p>

<p>
	<strong>OpenSearch</strong>
</p>

<ul>
	<li>
		Простота встановлення: <span class="ipsEmoji">❌</span>
	</li>
	<li>
		Пошук з помилками: <span class="ipsEmoji">✅</span>
	</li>
	<li>
		Морфологія: <span class="ipsEmoji">✅</span>
	</li>
	<li>
		Виправлення розкладки клавіатури: <span class="ipsEmoji">✅</span>
	</li>
	<li>
		Семантичний пошук: <span class="ipsEmoji">✅</span>
	</li>
</ul>

<p>
	 
</p>

<p>
	<strong>Typesense</strong>
</p>

<ul>
	<li>
		Простота встановлення: <span class="ipsEmoji">✅</span>
	</li>
	<li>
		Пошук з помилками: <span class="ipsEmoji">✅</span>
	</li>
	<li>
		Морфологія: <span class="ipsEmoji">❌</span>
	</li>
	<li>
		Виправлення розкладки клавіатури: <span class="ipsEmoji">❌</span>
	</li>
	<li>
		Семантичний пошук: <span class="ipsEmoji">✅</span>
	</li>
</ul>

<p>
	 
</p>

<p>
	<strong>Meilisearch</strong>
</p>

<ul>
	<li>
		Простота встановлення: <span class="ipsEmoji">✅</span>
	</li>
	<li>
		Пошук з помилками: <span class="ipsEmoji">✅</span>
	</li>
	<li>
		Морфологія: <span class="ipsEmoji">❌</span>
	</li>
	<li>
		Виправлення розкладки клавіатури: <span class="ipsEmoji">❌</span>
	</li>
	<li>
		Семантичний пошук: <span class="ipsEmoji">❌</span>
	</li>
</ul>

<p>
	          
</p>

<p>
	Чому окреме API? Я робив універсальне рішення для різних движків, а зробити таке в рамках опенкарта - нагородити купу "лапшекода"" без можливості виконання cli-скриптів, черг та інших принадностей цивілізації. У будь-якому випадку, установка API буде не складнішою за установку <abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом"><abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом">Opencart</abbr></abbr>, знати Laravel зовсім не обов'язково.
</p>

<p>
	<br />
	Embedding API використовується тільки в Opensearch і тільки для семантичного пошуку. Це однофайлове API на основі FastAPI і Python, яке приймає рядок і повертає його векторне представлення. Запускається парою команд, ніяких знань Python від вас не потрібно.
</p>

<p>
	 
</p>

<p>
	<strong>Встановлення</strong> 
</p>

<p>
	Для тих, хто не проти спробувати Docker, в репозиторії знаходиться готова конфігурація для запуску всієї інфраструктури однією командою.
</p>

<p>
	 
</p>

<p>
	<strong>1. Пошуковий движок.</strong><br />
	Вибираєте пошуковий движок, який вам сподобався, і на офіційному сайті або в ChatGPT отримуєте відповідь про встановлення під вашу платформу. Від себе зазначу, що найпростіші в установці Typesense і Meilisearch. 
</p>

<p>
	 
</p>

<p>
	Для Opensearch необхідно встановити плагін для векторного пошуку:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">/usr/share/opensearch/bin/opensearch-plugin install opensearch-knn</span></pre>

<p>
	 
</p>

<p>
	А також морфологію для української та російської мов:
</p>

<p>
	/usr/share/opensearch/config/hunspell — тут створюємо папки: ru_RU, uk_UA, в яких будуть лежати файли *.dic, *.aff. Самі файли завантажуєте звідси:
</p>

<p>
	<a href="https://raw.githubusercontent.com/LibreOffice/dictionaries/master/ru_RU/ru_RU.dic" rel="external nofollow">https://raw.githubusercontent.com/LibreOffice/dictionaries/master/ru_RU/ru_RU.dic</a><br />
	<a href="https://raw.githubusercontent.com/LibreOffice/dictionaries/master/ru_RU/ru_RU.aff" rel="external nofollow">https://raw.githubusercontent.com/LibreOffice/dictionaries/master/ru_RU/ru_RU.aff</a><br />
	<a href="https://raw.githubusercontent.com/LibreOffice/dictionaries/master/uk_UA/uk_UA.dic" rel="external nofollow">https://raw.githubusercontent.com/LibreOffice/dictionaries/master/uk_UA/uk_UA.dic</a><br />
	<a href="https://raw.githubusercontent.com/LibreOffice/dictionaries/master/uk_UA/uk_UA.aff" rel="external nofollow">https://raw.githubusercontent.com/LibreOffice/dictionaries/master/uk_UA/uk_UA.aff</a>
</p>

<p>
	 
</p>

<p>
	Щодо семантичного пошуку. У кожному з движків можна використовувати OpenAI embedding (перетворювач рядків у вектори), але ви повинні розуміти, що він платний, тому я його не реалізовував. 
</p>

<p>
	 
</p>

<p>
	Якщо хочете використовувати тільки безкоштовні рішення:
</p>

<ul>
	<li>
		OpenSearch - потрібен окремий Embedding API з безкоштовною моделлю. 
	</li>
	<li>
		Typsense - є вбудований механізм embedding-а і невеликий список доступних моделей
	</li>
	<li>
		Meilisearch - вбудованих механізмів embedding-а не знайшов, а окремий API для цього просто не став робити. Якщо вже заморочуватися, то краще отримати всі можливості Opensearch.
	</li>
</ul>

<p>
	 
</p>

<p>
	<strong>2. Embedding API</strong><br />
	Повторю, що цей крок потрібен тільки для Opensearch при використанні семантичного пошуку.
</p>

<p>
	Вибираємо директорію для проекту і копіюємо туди файли: 
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">docker/embedder/app/main.py - сам файл додатка
docker/embedder/requirements.txt - залежності, щось на зразок composer.json</span></pre>

<p>
	 
</p>

<p>
	Створюємо також .env файл з параметрами:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">API_TOKEN=дивіться наступний пункт
SEARCH_EMBEDDING_MODEL=дивіться наступний пункт</span></pre>

<p>
	 
</p>

<p>
	Встановлюємо залежності (на сервері повинен бути Python)
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">pip install --no-cache-dir -r requirements.txt</span></pre>

<p>
	 
</p>

<p>
	Запускаємо сервер
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">uvicorn main:app --host 0.0.0.0 --port 8000</span></pre>

<p>
	 
</p>

<p>
	При першому запуску після установки знадобиться деякий час на ініціалізацію.
</p>

<p>
	Щоб все це працювало після збоїв і перезапусків системи, створіть systemd unit (ChatGPT легко напише вам команди і конфігурацію)
</p>

<p>
	 
</p>

<p>
	<strong>3. Пошукове API</strong>
</p>

<p>
	Копіюємо файли з github репозиторію в поточну директорію:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">git clone https://github.com/ozzzi/api-opencart.git .</span></pre>

<p>
	 
</p>

<p>
	Створюємо файл середовища .env
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">cp .env.example .env

Заповнюємо його:

APP_NAME=API
APP_ENV=production
APP_DEBUG=false
APP_URL=http://ваш_урл </span></pre>

<p>
	 
</p>

<p>
	налаштовуємо підключення до бази <abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом"><abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом">опенкарт</abbr></abbr>
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=
DB_USERNAME=
DB_PASSWORD=</span></pre>

<p>
	 
</p>

<p>
	доступ до API
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">API_TOKEN - токен аутентифікації для API
API_IP_ADDRESS - IP-адреса, з якої дозволені звернення</span></pre>

<p>
	 
</p>

<p>
	налаштування пошуку
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">SEARCH_API_KEY - токен доступу до opensearch, який вказали під час встановлення
SEARCH_USER - користувач opensearch
SEARCH_HOST
SEARCH_PORT
SEARCH_SSL - true/false</span></pre>

<p>
	 
</p>

<p>
	Модель для перетворення текстових даних у вектори
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">SEARCH_EMBEDDING_MODEL=sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2</span></pre>

<p>
	 
</p>

<p>
	для Typsense є список доступних моделей: <a href="https://huggingface.co/typesense/models-moved/tree/main" rel="external nofollow">https://huggingface.co/typesense/models-moved/tree/main.</a> Можна вибрати, наприклад, ts/multilingual-e5-large, але зверніть увагу на розмір моделі, він може бути як кілька сотень мегабайт, так і кілька гігабайт і буде завантажений автоматично на ваш диск при першому використанні.
</p>

<p>
	 
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">EMBEDDED_URL - адреса Embedding API, за замовчуванням адреса http://localhost:8000/vectorize</span></pre>

<p>
	 
</p>

<p>
	SEARCH_DISTANCE_THRESHOLD - порогова оцінка документа, якщо у документа вона буде нижчою, то він не потрапить у видачу. Даний параметр пов'язаний із семантичним пошуком. Коли ми порівнюємо вектори, ми обчислюємо відстань або кут між ними. У нашому випадку, чим ближче оцінка до 1, тим семантично наші вектори близькі. Якщо ми не будемо обмежувати допустиму оцінку документа, ми просто отримаємо купу нерелевантних для нас результатів. Цей параметр підбирається індивідуально, можна почати зі значення 0.8. А щоб взагалі розуміти, яку оцінку отримав документ і чому якийсь документ взагалі потрапив у видачу, увімкніть дебаг-режим (див. параметр) і через dump()/dd() подивіться відповідь opensearch.
</p>

<p>
	 
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">SEARCH_DEBUG - вмикає дебаг інформацію (true/false)</span></pre>

<p>
	 
</p>

<p>
	Встановлення залежностей (повинен бути встановлений composer)
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">composer install --no-dev --prefer-dist --optimize-autoloader</span></pre>

<p>
	 
</p>

<p>
	Генеруємо ключ додатка:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">php artisan key:generate</span></pre>

<p>
	 
</p>

<p>
	Коли всі сервіси працюють, можна приступати до налаштування пошуку. Ось перелік консольних команд, як потрібно запустити в корневій директорії API
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">php artisan search:setup - налаштування семантичного пошуку (тільки для Opensearch)

php artisan search:create-index - створюємо пошуковий індекс

php artisan search:reindex - наповнюємо індекс даними</span></pre>

<p>
	<br />
	Опис ключових компонентів:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">app/Providers/OpensearchProvider.php - тут конфігурується з'єднання з пошуковою системою і задається схема індексу: поля, їх типи, ваги, опції.

app/Services/Search/Engines/Opensearch/OpensearchIndexer.php - тут конфігурується індекс: маппінг полів, аналізатори, фільтри тощо.

app/Services/Search/Engines/Opensearch/OpensearchSearcher.php - всі налаштування пошуку

app/Console/Commands/Search/Reindex.php - консольна команда, в якій відбувається наповнення індексу даними з бази</span></pre>

<p>
	 
</p>

<p>
	<strong>4. Модуль <abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом"><abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом">opencart</abbr></abbr></strong>
</p>

<p>
	Модуль додає autocomplete у форму пошуку і замінює стандартний пошук. Встановлюється модуль як зазвичай. Потім додаємо в config.php налаштування підключення до пошукового API:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted"><span class="pln">define(“API_URL”, “https://ваша_адреса/api/”);
define(“API_TOKEN”, “токен”);</span></pre>

<p>
	 
</p>

<p>
	<a class="ipsAttachLink" data-fileext="zip" data-fileid="829192" href="https://opencartforum.com/applications/core/interface/file/attachment.php?id=829192&amp;key=b39c0abc26149ceed299c62c7ae6e432" rel="">search_oc23.ocmod.zip</a> - для <abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом"><abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом">opencart</abbr></abbr> 2.3
</p>

<p>
	<a class="ipsAttachLink" data-fileext="zip" data-fileid="829193" href="https://opencartforum.com/applications/core/interface/file/attachment.php?id=829193&amp;key=9b893d8fd9826c9262b4d95687890218" rel="">search_oc3x.ocmod.zip</a> - для <abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом"><abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом">opencart</abbr></abbr> 3.x
</p>

<p>
	<br />
	Все. Пошук готовий до роботи.
</p>

<p>
	 
</p>

<p>
	При бажанні сюди можна додати ще фасетний пошук і тим самим фільтрувати результати за категоріями, брендами тощо.
</p>
]]></description><guid isPermaLink="false">554</guid><pubDate>Tue, 16 Sep 2025 13:27:27 +0000</pubDate></item><item><title>&#x410;&#x432;&#x442;&#x43E;&#x43C;&#x430;&#x442;&#x438;&#x447;&#x435;&#x441;&#x43A;&#x438;&#x439; &#x434;&#x435;&#x43F;&#x43B;&#x43E;&#x439; Opencart &#x441; &#x43F;&#x43E;&#x43C;&#x43E;&#x449;&#x44C;&#x44E; GitHub Actions</title><link>https://opencartforum.com/blogs/entry/501-avtomaticheskiy-deploy-opencart-s-pomoschyu-github-actions/</link><description><![CDATA[<p>
	Вы когда-нибудь забывали внести изменения в базу данных после публикации нового функционала? А еще эти кеши модификаторов, которые нужно не забыть обновить. В этой статье я опишу, как автоматизировать процесс деплоя проекта и избавить себя от рутины.
</p>

<p>
	<br />
	Данная статья предполагает, что вы используете Git как систему контроля версий, GitHub как хранилище вашего репозитория и также у вас есть SSH доступ к серверу, на котором можно запустить composer. В качестве основы для нашей автоматизации мы будем использовать GitHub Actions, который позволяет запускать различные процессы внутри контейнеров. 
</p>

<p>
	<br /><strong>Что нам понадобится сделать:</strong>
</p>

<ul><li>
		консольные скрипты для очистки модификаторов и кеша системы
	</li>
	<li>
		систему миграций для БД
	</li>
</ul><p>
	 
</p>

<p>
	<strong>Консольные команды</strong><br />
	Конечно, можно использовать исходный код опенкарта и на основе него написать скрипты, но это не наш метод. Мы напишем собственные велосипеды и за основу возьмем Symfony Console. 
</p>

<p>
	 
</p>

<p>
	Устанавливаем нужные зависимости:
</p>

<pre class="ipsCode">composer require symfony/console</pre>

<p>
	 
</p>

<p>
	Создаем директорию src для нашего приложения (лучше всего размещать такой код на уровень выше вашего index.php)
</p>

<p>
	 
</p>

<p>
	В composer.json, в разделе автозагрузки добавим код для задания пространства имен нашего приложения:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted"><span class="str">"autoload"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">"psr-4"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="str">"App\\"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"путь к папке/src/"</span><span class="pun">,</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
</span><span class="str">"require"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">...список</span><span class="pln"> </span><span class="pun">ваших</span><span class="pln"> </span><span class="pun">зависимостей...,</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	 
</p>

<p>
	Создадим папки в директории src для будущего кода: <em>Commands, Models, Services, Traits</em>
</p>

<p>
	 
</p>

<p>
	Структура папки src
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="753700" href="https://opencartforum.com/storage/attachment/monthly_2023_05/directories.png.34ce5b7bf4098bfae99afcf862d39335.png" rel=""><img alt="directories.thumb.png.7a01f4e49bc93ff562e0a7e47fd58968.png" class="ipsImage ipsImage_thumbnailed" data-fileid="753700" data-ratio="47.70" style="height:auto;" width="1000" data-src="https://opencartforum.com/storage/attachment/monthly_2023_05/directories.thumb.png.7a01f4e49bc93ff562e0a7e47fd58968.png" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	 
</p>

<p>
	 
</p>

<p>
	<strong>Создадим сервисы для очистки директорий кеша</strong>
</p>

<p>
	 
</p>

<p>
	Базовый класс<br />
	 
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted"><span class="pun">&lt;?</span><span class="pln">php

declare</span><span class="pun">(</span><span class="pln">strict_types</span><span class="pun">=</span><span class="lit">1</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">namespace</span><span class="pln"> </span><span class="typ">App</span><span class="pln">\Services\Clear</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">FilesystemIterator</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">RecursiveDirectoryIterator</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">RecursiveIteratorIterator</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">BaseClearService</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">protected</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> SKIP_FILE </span><span class="pun">=</span><span class="pln"> </span><span class="str">'index.html'</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> clear</span><span class="pun">(</span><span class="kwd">string</span><span class="pln"> $dir</span><span class="pun">):</span><span class="pln"> </span><span class="kwd">void</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        $directories </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">RecursiveDirectoryIterator</span><span class="pun">(</span><span class="pln">$dir</span><span class="pun">,</span><span class="pln"> </span><span class="typ">FilesystemIterator</span><span class="pun">::</span><span class="pln">SKIP_DOTS</span><span class="pun">);</span><span class="pln">
        $files </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">RecursiveIteratorIterator</span><span class="pun">(</span><span class="pln">$directories</span><span class="pun">,</span><span class="pln"> </span><span class="typ">RecursiveIteratorIterator</span><span class="pun">::</span><span class="pln">CHILD_FIRST</span><span class="pun">);</span><span class="pln">

        </span><span class="kwd">foreach</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$files </span><span class="kwd">as</span><span class="pln"> $file</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$file</span><span class="pun">-&gt;</span><span class="pln">isFile</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> $file</span><span class="pun">-&gt;</span><span class="pln">getFilename</span><span class="pun">()</span><span class="pln"> </span><span class="pun">===</span><span class="pln"> </span><span class="kwd">self</span><span class="pun">::</span><span class="pln">SKIP_FILE</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="kwd">continue</span><span class="pun">;</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">

            $file</span><span class="pun">-&gt;</span><span class="pln">isDir</span><span class="pun">()</span><span class="pln"> </span><span class="pun">?</span><span class="pln">  rmdir</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">-&gt;</span><span class="pln">getRealPath</span><span class="pun">())</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> unlink</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">-&gt;</span><span class="pln">getRealPath</span><span class="pun">());</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	 
</p>

<p>
	 
</p>

<p>
	Сервис очистка системного кеша 
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted"><span class="pun">&lt;?</span><span class="pln">php

declare</span><span class="pun">(</span><span class="pln">strict_types</span><span class="pun">=</span><span class="lit">1</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">namespace</span><span class="pln"> </span><span class="typ">App</span><span class="pln">\Services\Clear</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">CacheClearService</span><span class="pln"> </span><span class="kwd">extends</span><span class="pln"> </span><span class="typ">BaseClearService</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> process</span><span class="pun">():</span><span class="pln"> </span><span class="kwd">void</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        $this</span><span class="pun">-&gt;</span><span class="pln">clear</span><span class="pun">(</span><span class="pln">DIR_CACHE</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	 
</p>

<p>
	Сервис очистки старых модификаторов<br />
	 
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted"><span class="pun">&lt;?</span><span class="pln">php

declare</span><span class="pun">(</span><span class="pln">strict_types</span><span class="pun">=</span><span class="lit">1</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">namespace</span><span class="pln"> </span><span class="typ">App</span><span class="pln">\Services\Clear</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">ModificationClearService</span><span class="pln"> </span><span class="kwd">extends</span><span class="pln"> </span><span class="typ">BaseClearService</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> process</span><span class="pun">():</span><span class="pln"> </span><span class="kwd">void</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        $this</span><span class="pun">-&gt;</span><span class="pln">clear</span><span class="pun">(</span><span class="pln">DIR_MODIFICATION</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	 
</p>

<p>
	<em>ModificationService</em> - мне было лень переписывать код, взял из опенкарта как есть.
</p>

<p>
	 
</p>

<div class="ipsSpoiler" data-ipsspoiler="">
	<div class="ipsSpoiler_header">
		<span>Скрытый текст</span>
	</div>

	<div class="ipsSpoiler_contents">
		<pre class="ipsCode prettyprint lang-php prettyprinted"><span class="pun">&lt;?</span><span class="pln">php

</span><span class="kwd">namespace</span><span class="pln"> </span><span class="typ">App</span><span class="pln">\Services</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">App</span><span class="pln">\Model\Modification</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">DOMDocument</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">ModificationService</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> process</span><span class="pun">():</span><span class="pln"> </span><span class="kwd">void</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        $xml </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[];</span><span class="pln">

        $xml</span><span class="pun">[]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> file_get_contents</span><span class="pun">(</span><span class="pln">DIR_SYSTEM </span><span class="pun">.</span><span class="pln"> </span><span class="str">'modification.xml'</span><span class="pun">);</span><span class="pln">

        $files </span><span class="pun">=</span><span class="pln"> glob</span><span class="pun">(</span><span class="pln">DIR_SYSTEM </span><span class="pun">.</span><span class="pln"> </span><span class="str">'*.ocmod.xml'</span><span class="pun">);</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$files</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">foreach</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$files </span><span class="kwd">as</span><span class="pln"> $file</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                $xml</span><span class="pun">[]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> file_get_contents</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">);</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        $results </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Modification</span><span class="pun">::</span><span class="pln">query</span><span class="pun">()-&gt;</span><span class="kwd">where</span><span class="pun">(</span><span class="str">'status'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)-&gt;</span><span class="kwd">get</span><span class="pun">();</span><span class="pln">

        </span><span class="kwd">foreach</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$results </span><span class="kwd">as</span><span class="pln"> $result</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            $xml</span><span class="pun">[]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> $result</span><span class="pun">-&gt;</span><span class="pln">xml</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        $modification </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[];</span><span class="pln">

        </span><span class="kwd">foreach</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$xml </span><span class="kwd">as</span><span class="pln"> $xml</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">empty</span><span class="pun">(</span><span class="pln">$xml</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="kwd">continue</span><span class="pun">;</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">

            $dom </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">DOMDocument</span><span class="pun">(</span><span class="str">'1.0'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'UTF-8'</span><span class="pun">);</span><span class="pln">
            $dom</span><span class="pun">-&gt;</span><span class="pln">preserveWhiteSpace </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">
            $dom</span><span class="pun">-&gt;</span><span class="pln">loadXml</span><span class="pun">(</span><span class="pln">$xml</span><span class="pun">);</span><span class="pln">

            $recovery </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[];</span><span class="pln">

            </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">isset</span><span class="pun">(</span><span class="pln">$modification</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                $recovery </span><span class="pun">=</span><span class="pln"> $modification</span><span class="pun">;</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">

            $files </span><span class="pun">=</span><span class="pln"> $dom</span><span class="pun">-&gt;</span><span class="pln">getElementsByTagName</span><span class="pun">(</span><span class="str">'modification'</span><span class="pun">)</span><span class="pln">
                </span><span class="pun">-&gt;</span><span class="pln">item</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)</span><span class="pln">
                </span><span class="pun">-&gt;</span><span class="pln">getElementsByTagName</span><span class="pun">(</span><span class="str">'file'</span><span class="pun">);</span><span class="pln">

            </span><span class="kwd">foreach</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$files </span><span class="kwd">as</span><span class="pln"> $file</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                $operations </span><span class="pun">=</span><span class="pln"> $file</span><span class="pun">-&gt;</span><span class="pln">getElementsByTagName</span><span class="pun">(</span><span class="str">'operation'</span><span class="pun">);</span><span class="pln">

                $files </span><span class="pun">=</span><span class="pln"> explode</span><span class="pun">(</span><span class="str">'|'</span><span class="pun">,</span><span class="pln"> $file</span><span class="pun">-&gt;</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="str">'path'</span><span class="pun">));</span><span class="pln">

                </span><span class="kwd">foreach</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$files </span><span class="kwd">as</span><span class="pln"> $file</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                    $path </span><span class="pun">=</span><span class="pln"> </span><span class="str">''</span><span class="pun">;</span><span class="pln">

                    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">((</span><span class="pln">substr</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">7</span><span class="pun">)</span><span class="pln"> </span><span class="pun">===</span><span class="pln"> </span><span class="str">'catalog'</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                        $path </span><span class="pun">=</span><span class="pln"> DIR_CATALOG </span><span class="pun">.</span><span class="pln"> substr</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">);</span><span class="pln">
                    </span><span class="pun">}</span><span class="pln">

                    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">((</span><span class="pln">substr</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">)</span><span class="pln"> </span><span class="pun">===</span><span class="pln"> </span><span class="str">'admin'</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                        $path </span><span class="pun">=</span><span class="pln"> DIR_APPLICATION </span><span class="pun">.</span><span class="pln"> substr</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">,</span><span class="pln"> </span><span class="lit">6</span><span class="pun">);</span><span class="pln">
                    </span><span class="pun">}</span><span class="pln">

                    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">((</span><span class="pln">substr</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">6</span><span class="pun">)</span><span class="pln"> </span><span class="pun">===</span><span class="pln"> </span><span class="str">'system'</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                        $path </span><span class="pun">=</span><span class="pln"> DIR_SYSTEM </span><span class="pun">.</span><span class="pln"> substr</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">,</span><span class="pln"> </span><span class="lit">7</span><span class="pun">);</span><span class="pln">
                    </span><span class="pun">}</span><span class="pln">

                    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$path</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                        $files </span><span class="pun">=</span><span class="pln"> glob</span><span class="pun">(</span><span class="pln">$path</span><span class="pun">,</span><span class="pln"> GLOB_BRACE</span><span class="pun">);</span><span class="pln">

                        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$files</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                            </span><span class="kwd">foreach</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$files </span><span class="kwd">as</span><span class="pln"> $file</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">substr</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> strlen</span><span class="pun">(</span><span class="pln">DIR_CATALOG</span><span class="pun">))</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> DIR_CATALOG</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                    $key </span><span class="pun">=</span><span class="pln"> </span><span class="str">'catalog/'</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> substr</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">,</span><span class="pln"> strlen</span><span class="pun">(</span><span class="pln">DIR_CATALOG</span><span class="pun">));</span><span class="pln">
                                </span><span class="pun">}</span><span class="pln">

                                </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">substr</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> strlen</span><span class="pun">(</span><span class="pln">DIR_APPLICATION</span><span class="pun">))</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> DIR_APPLICATION</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                    $key </span><span class="pun">=</span><span class="pln"> </span><span class="str">'admin/'</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> substr</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">,</span><span class="pln"> strlen</span><span class="pun">(</span><span class="pln">DIR_APPLICATION</span><span class="pun">));</span><span class="pln">
                                </span><span class="pun">}</span><span class="pln">

                                </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">substr</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> strlen</span><span class="pun">(</span><span class="pln">DIR_SYSTEM</span><span class="pun">))</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> DIR_SYSTEM</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                    $key </span><span class="pun">=</span><span class="pln"> </span><span class="str">'system/'</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> substr</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">,</span><span class="pln"> strlen</span><span class="pun">(</span><span class="pln">DIR_SYSTEM</span><span class="pun">));</span><span class="pln">
                                </span><span class="pun">}</span><span class="pln">

                                </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">isset</span><span class="pun">(</span><span class="pln">$modification</span><span class="pun">[</span><span class="pln">$key</span><span class="pun">]))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                    $content </span><span class="pun">=</span><span class="pln"> file_get_contents</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">);</span><span class="pln">

                                    $modification</span><span class="pun">[</span><span class="pln">$key</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> preg_replace</span><span class="pun">(</span><span class="str">'~\r?\n~'</span><span class="pun">,</span><span class="pln"> </span><span class="str">"\n"</span><span class="pun">,</span><span class="pln"> $content</span><span class="pun">);</span><span class="pln">
                                    $original</span><span class="pun">[</span><span class="pln">$key</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> preg_replace</span><span class="pun">(</span><span class="str">'~\r?\n~'</span><span class="pun">,</span><span class="pln"> </span><span class="str">"\n"</span><span class="pun">,</span><span class="pln"> $content</span><span class="pun">);</span><span class="pln">
                                </span><span class="pun">}</span><span class="pln">

                                </span><span class="kwd">foreach</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$operations </span><span class="kwd">as</span><span class="pln"> $operation</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                    $error </span><span class="pun">=</span><span class="pln"> $operation</span><span class="pun">-&gt;</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="str">'error'</span><span class="pun">);</span><span class="pln">

                                    $ignoreif </span><span class="pun">=</span><span class="pln"> $operation</span><span class="pun">-&gt;</span><span class="pln">getElementsByTagName</span><span class="pun">(</span><span class="str">'ignoreif'</span><span class="pun">)-&gt;</span><span class="pln">item</span><span class="pun">(</span><span class="lit">0</span><span class="pun">);</span><span class="pln">

                                    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$ignoreif</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$ignoreif</span><span class="pun">-&gt;</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="str">'regex'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">!==</span><span class="pln"> </span><span class="str">'true'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                            </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">strpos</span><span class="pun">(</span><span class="pln">$modification</span><span class="pun">[</span><span class="pln">$key</span><span class="pun">],</span><span class="pln"> $ignoreif</span><span class="pun">-&gt;</span><span class="pln">textContent</span><span class="pun">)</span><span class="pln"> </span><span class="pun">!==</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                                </span><span class="kwd">continue</span><span class="pun">;</span><span class="pln">
                                            </span><span class="pun">}</span><span class="pln">
                                        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                            </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">preg_match</span><span class="pun">(</span><span class="pln">$ignoreif</span><span class="pun">-&gt;</span><span class="pln">textContent</span><span class="pun">,</span><span class="pln"> $modification</span><span class="pun">[</span><span class="pln">$key</span><span class="pun">]))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                                </span><span class="kwd">continue</span><span class="pun">;</span><span class="pln">
                                            </span><span class="pun">}</span><span class="pln">
                                        </span><span class="pun">}</span><span class="pln">
                                    </span><span class="pun">}</span><span class="pln">

                                    $status </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">

                                    </span><span class="com">// Search and replace</span><span class="pln">
                                    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$operation</span><span class="pun">-&gt;</span><span class="pln">getElementsByTagName</span><span class="pun">(</span><span class="str">'search'</span><span class="pun">)-&gt;</span><span class="pln">item</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)-&gt;</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="pln">
                                            </span><span class="str">'regex'</span><span class="pln">
                                        </span><span class="pun">)</span><span class="pln"> </span><span class="pun">!==</span><span class="pln"> </span><span class="str">'true'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                        $search </span><span class="pun">=</span><span class="pln"> $operation</span><span class="pun">-&gt;</span><span class="pln">getElementsByTagName</span><span class="pun">(</span><span class="str">'search'</span><span class="pun">)-&gt;</span><span class="pln">item</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)-&gt;</span><span class="pln">textContent</span><span class="pun">;</span><span class="pln">
                                        $trim </span><span class="pun">=</span><span class="pln"> $operation</span><span class="pun">-&gt;</span><span class="pln">getElementsByTagName</span><span class="pun">(</span><span class="str">'search'</span><span class="pun">)-&gt;</span><span class="pln">item</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)-&gt;</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="pln">
                                            </span><span class="str">'trim'</span><span class="pln">
                                        </span><span class="pun">);</span><span class="pln">
                                        $index </span><span class="pun">=</span><span class="pln"> $operation</span><span class="pun">-&gt;</span><span class="pln">getElementsByTagName</span><span class="pun">(</span><span class="str">'search'</span><span class="pun">)-&gt;</span><span class="pln">item</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)-&gt;</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="pln">
                                            </span><span class="str">'index'</span><span class="pln">
                                        </span><span class="pun">);</span><span class="pln">

                                        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">$trim </span><span class="pun">||</span><span class="pln"> $trim </span><span class="pun">===</span><span class="pln"> </span><span class="str">'true'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                            $search </span><span class="pun">=</span><span class="pln"> trim</span><span class="pun">(</span><span class="pln">$search</span><span class="pun">);</span><span class="pln">
                                        </span><span class="pun">}</span><span class="pln">

                                        </span><span class="com">// Add</span><span class="pln">
                                        $add </span><span class="pun">=</span><span class="pln"> $operation</span><span class="pun">-&gt;</span><span class="pln">getElementsByTagName</span><span class="pun">(</span><span class="str">'add'</span><span class="pun">)-&gt;</span><span class="pln">item</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)-&gt;</span><span class="pln">textContent</span><span class="pun">;</span><span class="pln">
                                        $trim </span><span class="pun">=</span><span class="pln"> $operation</span><span class="pun">-&gt;</span><span class="pln">getElementsByTagName</span><span class="pun">(</span><span class="str">'add'</span><span class="pun">)-&gt;</span><span class="pln">item</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)-&gt;</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="str">'trim'</span><span class="pun">);</span><span class="pln">
                                        $position </span><span class="pun">=</span><span class="pln"> $operation</span><span class="pun">-&gt;</span><span class="pln">getElementsByTagName</span><span class="pun">(</span><span class="str">'add'</span><span class="pun">)-&gt;</span><span class="pln">item</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)-&gt;</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="pln">
                                            </span><span class="str">'position'</span><span class="pln">
                                        </span><span class="pun">);</span><span class="pln">
                                        $offset </span><span class="pun">=</span><span class="pln"> $operation</span><span class="pun">-&gt;</span><span class="pln">getElementsByTagName</span><span class="pun">(</span><span class="str">'add'</span><span class="pun">)-&gt;</span><span class="pln">item</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)-&gt;</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="pln">
                                            </span><span class="str">'offset'</span><span class="pln">
                                        </span><span class="pun">);</span><span class="pln">

                                        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$offset </span><span class="pun">==</span><span class="pln"> </span><span class="str">''</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                            $offset </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
                                        </span><span class="pun">}</span><span class="pln">

                                        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$trim </span><span class="pun">===</span><span class="pln"> </span><span class="str">'true'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                            $add </span><span class="pun">=</span><span class="pln"> trim</span><span class="pun">(</span><span class="pln">$add</span><span class="pun">);</span><span class="pln">
                                        </span><span class="pun">}</span><span class="pln">

                                        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$index </span><span class="pun">!==</span><span class="pln"> </span><span class="str">''</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                            $indexes </span><span class="pun">=</span><span class="pln"> explode</span><span class="pun">(</span><span class="str">','</span><span class="pun">,</span><span class="pln"> $index</span><span class="pun">);</span><span class="pln">
                                        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                            $indexes </span><span class="pun">=</span><span class="pln"> array</span><span class="pun">();</span><span class="pln">
                                        </span><span class="pun">}</span><span class="pln">

                                        $i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">

                                        $lines </span><span class="pun">=</span><span class="pln"> explode</span><span class="pun">(</span><span class="str">"\n"</span><span class="pun">,</span><span class="pln"> $modification</span><span class="pun">[</span><span class="pln">$key</span><span class="pun">]);</span><span class="pln">

                                        </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$line_id </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> $line_id </span><span class="pun">&lt;</span><span class="pln"> count</span><span class="pun">(</span><span class="pln">$lines</span><span class="pun">);</span><span class="pln"> $line_id</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                            $line </span><span class="pun">=</span><span class="pln"> $lines</span><span class="pun">[</span><span class="pln">$line_id</span><span class="pun">];</span><span class="pln">

                                            $match </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">

                                            </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">stripos</span><span class="pun">(</span><span class="pln">$line</span><span class="pun">,</span><span class="pln"> $search</span><span class="pun">)</span><span class="pln"> </span><span class="pun">!==</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                                </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">$indexes</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                                    $match </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
                                                </span><span class="pun">}</span><span class="pln"> elseif </span><span class="pun">(</span><span class="pln">in_array</span><span class="pun">(</span><span class="pln">$i</span><span class="pun">,</span><span class="pln"> $indexes</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                                    $match </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
                                                </span><span class="pun">}</span><span class="pln">

                                                $i</span><span class="pun">++;</span><span class="pln">
                                            </span><span class="pun">}</span><span class="pln">

                                            </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$match</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                                </span><span class="kwd">switch</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$position</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                                    </span><span class="kwd">default</span><span class="pun">:</span><span class="pln">
                                                    </span><span class="kwd">case</span><span class="pln"> </span><span class="str">'replace'</span><span class="pun">:</span><span class="pln">
                                                        $new_lines </span><span class="pun">=</span><span class="pln"> explode</span><span class="pun">(</span><span class="str">"\n"</span><span class="pun">,</span><span class="pln"> $add</span><span class="pun">);</span><span class="pln">

                                                        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$offset </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                                            array_splice</span><span class="pun">(</span><span class="pln">
                                                                $lines</span><span class="pun">,</span><span class="pln">
                                                                $line_id </span><span class="pun">+</span><span class="pln"> $offset</span><span class="pun">,</span><span class="pln">
                                                                abs</span><span class="pun">(</span><span class="pln">$offset</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln">
                                                                array</span><span class="pun">(</span><span class="pln">str_replace</span><span class="pun">(</span><span class="pln">$search</span><span class="pun">,</span><span class="pln"> $add</span><span class="pun">,</span><span class="pln"> $line</span><span class="pun">))</span><span class="pln">
                                                            </span><span class="pun">);</span><span class="pln">

                                                            $line_id </span><span class="pun">-=</span><span class="pln"> $offset</span><span class="pun">;</span><span class="pln">
                                                        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                                            array_splice</span><span class="pun">(</span><span class="pln">
                                                                $lines</span><span class="pun">,</span><span class="pln">
                                                                $line_id</span><span class="pun">,</span><span class="pln">
                                                                $offset </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln">
                                                                array</span><span class="pun">(</span><span class="pln">str_replace</span><span class="pun">(</span><span class="pln">$search</span><span class="pun">,</span><span class="pln"> $add</span><span class="pun">,</span><span class="pln"> $line</span><span class="pun">))</span><span class="pln">
                                                            </span><span class="pun">);</span><span class="pln">
                                                        </span><span class="pun">}</span><span class="pln">
                                                        </span><span class="kwd">break</span><span class="pun">;</span><span class="pln">
                                                    </span><span class="kwd">case</span><span class="pln"> </span><span class="str">'before'</span><span class="pun">:</span><span class="pln">
                                                        $new_lines </span><span class="pun">=</span><span class="pln"> explode</span><span class="pun">(</span><span class="str">"\n"</span><span class="pun">,</span><span class="pln"> $add</span><span class="pun">);</span><span class="pln">

                                                        array_splice</span><span class="pun">(</span><span class="pln">$lines</span><span class="pun">,</span><span class="pln"> $line_id </span><span class="pun">-</span><span class="pln"> $offset</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> $new_lines</span><span class="pun">);</span><span class="pln">

                                                        $line_id </span><span class="pun">+=</span><span class="pln"> count</span><span class="pun">(</span><span class="pln">$new_lines</span><span class="pun">);</span><span class="pln">
                                                        </span><span class="kwd">break</span><span class="pun">;</span><span class="pln">
                                                    </span><span class="kwd">case</span><span class="pln"> </span><span class="str">'after'</span><span class="pun">:</span><span class="pln">
                                                        $new_lines </span><span class="pun">=</span><span class="pln"> explode</span><span class="pun">(</span><span class="str">"\n"</span><span class="pun">,</span><span class="pln"> $add</span><span class="pun">);</span><span class="pln">

                                                        array_splice</span><span class="pun">(</span><span class="pln">$lines</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$line_id </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> $offset</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> $new_lines</span><span class="pun">);</span><span class="pln">

                                                        $line_id </span><span class="pun">+=</span><span class="pln"> count</span><span class="pun">(</span><span class="pln">$new_lines</span><span class="pun">);</span><span class="pln">
                                                        </span><span class="kwd">break</span><span class="pun">;</span><span class="pln">
                                                </span><span class="pun">}</span><span class="pln">

                                                $status </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
                                            </span><span class="pun">}</span><span class="pln">
                                        </span><span class="pun">}</span><span class="pln">

                                        $modification</span><span class="pun">[</span><span class="pln">$key</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> implode</span><span class="pun">(</span><span class="str">"\n"</span><span class="pun">,</span><span class="pln"> $lines</span><span class="pun">);</span><span class="pln">
                                    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                        $search </span><span class="pun">=</span><span class="pln"> trim</span><span class="pun">(</span><span class="pln">
                                            $operation</span><span class="pun">-&gt;</span><span class="pln">getElementsByTagName</span><span class="pun">(</span><span class="str">'search'</span><span class="pun">)-&gt;</span><span class="pln">item</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)-&gt;</span><span class="pln">textContent
                                        </span><span class="pun">);</span><span class="pln">
                                        $limit </span><span class="pun">=</span><span class="pln"> $operation</span><span class="pun">-&gt;</span><span class="pln">getElementsByTagName</span><span class="pun">(</span><span class="str">'search'</span><span class="pun">)-&gt;</span><span class="pln">item</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)-&gt;</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="pln">
                                            </span><span class="str">'limit'</span><span class="pln">
                                        </span><span class="pun">);</span><span class="pln">
                                        $replace </span><span class="pun">=</span><span class="pln"> trim</span><span class="pun">(</span><span class="pln">$operation</span><span class="pun">-&gt;</span><span class="pln">getElementsByTagName</span><span class="pun">(</span><span class="str">'add'</span><span class="pun">)-&gt;</span><span class="pln">item</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)-&gt;</span><span class="pln">textContent</span><span class="pun">);</span><span class="pln">

                                        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">$limit</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                            $limit </span><span class="pun">=</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">;</span><span class="pln">
                                        </span><span class="pun">}</span><span class="pln">

                                        $match </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[];</span><span class="pln">

                                        preg_match_all</span><span class="pun">(</span><span class="pln">$search</span><span class="pun">,</span><span class="pln"> $modification</span><span class="pun">[</span><span class="pln">$key</span><span class="pun">],</span><span class="pln"> $match</span><span class="pun">,</span><span class="pln"> PREG_OFFSET_CAPTURE</span><span class="pun">);</span><span class="pln">

                                        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$limit </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                            $match</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> array_slice</span><span class="pun">(</span><span class="pln">$match</span><span class="pun">[</span><span class="lit">0</span><span class="pun">],</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> $limit</span><span class="pun">);</span><span class="pln">
                                        </span><span class="pun">}</span><span class="pln">

                                        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$match</span><span class="pun">[</span><span class="lit">0</span><span class="pun">])</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                            $status </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
                                        </span><span class="pun">}</span><span class="pln">

                                        $modification</span><span class="pun">[</span><span class="pln">$key</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> preg_replace</span><span class="pun">(</span><span class="pln">
                                            $search</span><span class="pun">,</span><span class="pln">
                                            $replace</span><span class="pun">,</span><span class="pln">
                                            $modification</span><span class="pun">[</span><span class="pln">$key</span><span class="pun">],</span><span class="pln">
                                            $limit
                                        </span><span class="pun">);</span><span class="pln">
                                    </span><span class="pun">}</span><span class="pln">

                                    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">$status</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                        </span><span class="com">// Abort applying this modification completely.</span><span class="pln">
                                        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$error </span><span class="pun">==</span><span class="pln"> </span><span class="str">'abort'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                            $modification </span><span class="pun">=</span><span class="pln"> $recovery</span><span class="pun">;</span><span class="pln">
                                            </span><span class="kwd">break</span><span class="pln"> </span><span class="lit">5</span><span class="pun">;</span><span class="pln">
                                        </span><span class="pun">}</span><span class="pln"> </span><span class="com">// Skip current operation or break</span><span class="pln">
                                        elseif </span><span class="pun">(</span><span class="pln">$error </span><span class="pun">==</span><span class="pln"> </span><span class="str">'skip'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                            </span><span class="kwd">continue</span><span class="pun">;</span><span class="pln">
                                        </span><span class="pun">}</span><span class="pln"> </span><span class="com">// Break current operations</span><span class="pln">
                                        </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                                            </span><span class="kwd">break</span><span class="pun">;</span><span class="pln">
                                        </span><span class="pun">}</span><span class="pln">
                                    </span><span class="pun">}</span><span class="pln">
                                </span><span class="pun">}</span><span class="pln">
                            </span><span class="pun">}</span><span class="pln">
                        </span><span class="pun">}</span><span class="pln">
                    </span><span class="pun">}</span><span class="pln">
                </span><span class="pun">}</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="kwd">foreach</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$modification </span><span class="kwd">as</span><span class="pln"> $key </span><span class="pun">=&gt;</span><span class="pln"> $value</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="com">// Only create a file if there are changes</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$original</span><span class="pun">[</span><span class="pln">$key</span><span class="pun">]</span><span class="pln"> </span><span class="pun">!=</span><span class="pln"> $value</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                $path </span><span class="pun">=</span><span class="pln"> </span><span class="str">''</span><span class="pun">;</span><span class="pln">

                $directories </span><span class="pun">=</span><span class="pln"> explode</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> dirname</span><span class="pun">(</span><span class="pln">$key</span><span class="pun">));</span><span class="pln">

                </span><span class="kwd">foreach</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$directories </span><span class="kwd">as</span><span class="pln"> $directory</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                    $path </span><span class="pun">=</span><span class="pln"> $path </span><span class="pun">.</span><span class="pln"> </span><span class="str">'/'</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> $directory</span><span class="pun">;</span><span class="pln">

                    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">is_dir</span><span class="pun">(</span><span class="pln">DIR_MODIFICATION </span><span class="pun">.</span><span class="pln"> $path</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">mkdir</span><span class="pun">(</span><span class="pln">$concurrentDirectory </span><span class="pun">=</span><span class="pln"> DIR_MODIFICATION </span><span class="pun">.</span><span class="pln"> $path</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0777</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> </span><span class="pun">!</span><span class="pln">is_dir</span><span class="pun">(</span><span class="pln">
                                $concurrentDirectory
                            </span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                            </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> \RuntimeException</span><span class="pun">(</span><span class="pln">
                                sprintf</span><span class="pun">(</span><span class="str">'Directory "%s" was not created'</span><span class="pun">,</span><span class="pln"> $concurrentDirectory</span><span class="pun">)</span><span class="pln">
                            </span><span class="pun">);</span><span class="pln">
                        </span><span class="pun">}</span><span class="pln">
                    </span><span class="pun">}</span><span class="pln">
                </span><span class="pun">}</span><span class="pln">

                $handle </span><span class="pun">=</span><span class="pln"> fopen</span><span class="pun">(</span><span class="pln">DIR_MODIFICATION </span><span class="pun">.</span><span class="pln"> $key</span><span class="pun">,</span><span class="pln"> </span><span class="str">'w'</span><span class="pun">);</span><span class="pln">

                fwrite</span><span class="pun">(</span><span class="pln">$handle</span><span class="pun">,</span><span class="pln"> $value</span><span class="pun">);</span><span class="pln">

                fclose</span><span class="pun">(</span><span class="pln">$handle</span><span class="pun">);</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

		<p>
			 
		</p>
	</div>
</div>

<p>
	 
</p>

<p>
	Первый сервис очищает директорию, а второй создает модифицируемые файлы. Для создания файлов нам нужно из базы получить список модификаторов. Для этой цели можно использовать разные подходы, но т.к. я использую в других проектах Laravel, я задействую для работы с базой Eloquent  <img alt=":)" data-emoticon="" height="20" src="https://opencartforum.com/storage/smiles/emoticons/test_smile.png" srcset="https://opencartforum.com/storage/smiles/emoticons/smile@2x.png 2x" title=":)" width="20" />.
</p>

<p>
	 
</p>

<p>
	Установим пакет:
</p>

<pre class="ipsCode">composer require illuminate/database </pre>

<p>
	 
</p>

<p>
	Создаем файл модели модификаций:<br />
	 
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted"><span class="pun">&lt;?</span><span class="pln">php

declare</span><span class="pun">(</span><span class="pln">strict_types</span><span class="pun">=</span><span class="lit">1</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">namespace</span><span class="pln"> </span><span class="typ">App</span><span class="pln">\Models</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">Illuminate</span><span class="pln">\Database\Eloquent\Model</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Modification</span><span class="pln"> </span><span class="kwd">extends</span><span class="pln"> </span><span class="typ">Model</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">protected</span><span class="pln"> $table </span><span class="pun">=</span><span class="pln"> </span><span class="str">'modification'</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">protected</span><span class="pln"> $primaryKey </span><span class="pun">=</span><span class="pln"> </span><span class="str">'modification_id'</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">public</span><span class="pln"> $timestamps </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	 
</p>

<p>
	Файлы для работы с базой данных
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted"><span class="pun">&lt;?</span><span class="pln">php

</span><span class="kwd">namespace</span><span class="pln"> </span><span class="typ">App</span><span class="pln">\Traits</span><span class="pun">;</span><span class="pln">

trait </span><span class="typ">Singleton</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> $instance</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> getInstance</span><span class="pun">():</span><span class="pln"> </span><span class="kwd">self</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">empty</span><span class="pun">(</span><span class="kwd">self</span><span class="pun">::</span><span class="pln">$instance</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">self</span><span class="pun">::</span><span class="pln">$instance </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="kwd">static</span><span class="pun">();</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">self</span><span class="pun">::</span><span class="pln">$instance</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> __clone</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">private</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> __wakeup</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	<br />
	 
</p>

<div class="ipsSpoiler" data-ipsspoiler="">
	<div class="ipsSpoiler_header">
		<span>Скрытый текст</span>
	</div>

	<div class="ipsSpoiler_contents">
		<pre class="ipsCode prettyprint lang-php prettyprinted"><span class="pun">&lt;?</span><span class="pln">php

</span><span class="kwd">namespace</span><span class="pln"> </span><span class="typ">App</span><span class="pln">\Models</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">App</span><span class="pln">\Traits\Singleton</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">Illuminate</span><span class="pln">\Database\Capsule\Manager </span><span class="kwd">as</span><span class="pln"> </span><span class="typ">Capsule</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Database</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">use</span><span class="pln"> </span><span class="typ">Singleton</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> __construct</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        $capsule </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Capsule</span><span class="pun">;</span><span class="pln">
        $capsule</span><span class="pun">-&gt;</span><span class="pln">addConnection</span><span class="pun">(</span><span class="pln">
            </span><span class="pun">[</span><span class="pln">
                </span><span class="str">'driver'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'mysql'</span><span class="pun">,</span><span class="pln">
                </span><span class="str">'host'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> DB_HOSTNAME</span><span class="pun">,</span><span class="pln">
                </span><span class="str">'database'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> DB_DATABASE</span><span class="pun">,</span><span class="pln">
                </span><span class="str">'username'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> DB_USERNAME</span><span class="pun">,</span><span class="pln">
                </span><span class="str">'password'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> DB_PASSWORD</span><span class="pun">,</span><span class="pln">
                </span><span class="str">'charset'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'utf8'</span><span class="pun">,</span><span class="pln">
                </span><span class="str">'collation'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'utf8_unicode_ci'</span><span class="pun">,</span><span class="pln">
                </span><span class="str">'prefix'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'oc_'</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">]</span><span class="pln">
        </span><span class="pun">);</span><span class="pln">

        $capsule</span><span class="pun">-&gt;</span><span class="pln">setAsGlobal</span><span class="pun">();</span><span class="pln">
        $capsule</span><span class="pun">-&gt;</span><span class="pln">bootEloquent</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

		<p>
			 
		</p>
	</div>
</div>

<p>
	<br />
	Теперь непосредственно создадим классы консольных команд для очистки системного кеша<br />
	 
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted"><span class="pun">&lt;?</span><span class="pln">php

declare</span><span class="pun">(</span><span class="pln">strict_types</span><span class="pun">=</span><span class="lit">1</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">namespace</span><span class="pln"> </span><span class="typ">App</span><span class="pln">\Commands</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">App</span><span class="pln">\Services\CacheClearService</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">Symfony</span><span class="pln">\Component\Console\Command\Command</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">Symfony</span><span class="pln">\Component\Console\Input\InputInterface</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">Symfony</span><span class="pln">\Component\Console\Output\OutputInterface</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">CacheClearCommand</span><span class="pln"> </span><span class="kwd">extends</span><span class="pln"> </span><span class="typ">Command</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">protected</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> configure</span><span class="pun">():</span><span class="pln"> </span><span class="kwd">void</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        $this</span><span class="pun">-&gt;</span><span class="pln">setName</span><span class="pun">(</span><span class="str">'cache:clear'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">-&gt;</span><span class="pln">setDescription</span><span class="pun">(</span><span class="str">'Clear system cache'</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">protected</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> execute</span><span class="pun">(</span><span class="typ">InputInterface</span><span class="pln"> $input</span><span class="pun">,</span><span class="pln"> </span><span class="typ">OutputInterface</span><span class="pln"> $output</span><span class="pun">):</span><span class="pln"> </span><span class="kwd">int</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">CacheClearService</span><span class="pun">())-&gt;</span><span class="pln">process</span><span class="pun">();</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="pln">\Throwable $e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            $output</span><span class="pun">-&gt;</span><span class="pln">writeln</span><span class="pun">(</span><span class="str">'&lt;error&gt;Directory error&lt;/error&gt;'</span><span class="pun">);</span><span class="pln">

            </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">Command</span><span class="pun">::</span><span class="pln">FAILURE</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        $output</span><span class="pun">-&gt;</span><span class="pln">writeln</span><span class="pun">(</span><span class="str">'&lt;info&gt;Modification cache cleared successfully&lt;/info&gt;'</span><span class="pun">);</span><span class="pln">

        </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">Command</span><span class="pun">::</span><span class="pln">SUCCESS</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	 
</p>

<p>
	И для очистки кеша модификаторов<br />
	 
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted"><span class="pun">&lt;?</span><span class="pln">php

declare</span><span class="pun">(</span><span class="pln">strict_types</span><span class="pun">=</span><span class="lit">1</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">namespace</span><span class="pln"> </span><span class="typ">App</span><span class="pln">\Commands\Opencart</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">App</span><span class="pln">\Models\Database</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">App</span><span class="pln">\Services\ModificationClearService</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">App</span><span class="pln">\Services\ModificationService</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">Symfony</span><span class="pln">\Component\Console\Command\Command</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">Symfony</span><span class="pln">\Component\Console\Input\InputInterface</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">Symfony</span><span class="pln">\Component\Console\Output\OutputInterface</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">ModificationClearCommand</span><span class="pln"> </span><span class="kwd">extends</span><span class="pln"> </span><span class="typ">Command</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">protected</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> configure</span><span class="pun">():</span><span class="pln"> </span><span class="kwd">void</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        $this</span><span class="pun">-&gt;</span><span class="pln">setName</span><span class="pun">(</span><span class="str">'cache:modification'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">-&gt;</span><span class="pln">setDescription</span><span class="pun">(</span><span class="str">'Clear modification cache'</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">protected</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> execute</span><span class="pun">(</span><span class="typ">InputInterface</span><span class="pln"> $input</span><span class="pun">,</span><span class="pln"> </span><span class="typ">OutputInterface</span><span class="pln"> $output</span><span class="pun">):</span><span class="pln"> </span><span class="kwd">int</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">Database</span><span class="pun">::</span><span class="pln">getInstance</span><span class="pun">();</span><span class="pln">
            </span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ModificationClearService</span><span class="pun">())-&gt;</span><span class="pln">process</span><span class="pun">();</span><span class="pln">
            </span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ModificationService</span><span class="pun">())-&gt;</span><span class="pln">process</span><span class="pun">();</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="pln">\Throwable $e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            $output</span><span class="pun">-&gt;</span><span class="pln">writeln</span><span class="pun">(</span><span class="str">'&lt;error&gt;Directory deleting error&lt;/error&gt;'</span><span class="pun">);</span><span class="pln">

            </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">Command</span><span class="pun">::</span><span class="pln">FAILURE</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        $output</span><span class="pun">-&gt;</span><span class="pln">writeln</span><span class="pun">(</span><span class="str">'&lt;info&gt;Modification cache cleared successfully&lt;/info&gt;'</span><span class="pun">);</span><span class="pln">

        </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">Command</span><span class="pun">::</span><span class="pln">SUCCESS</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	 
</p>

<p>
	 
</p>

<p>
	Соберем все в общее консольное приложение<br />
	 
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted"><span class="pun">&lt;?</span><span class="pln">php

declare</span><span class="pun">(</span><span class="pln">strict_types</span><span class="pun">=</span><span class="lit">1</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">namespace</span><span class="pln"> </span><span class="typ">App</span><span class="pln">\Commands</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">App</span><span class="pln">\Commands\CacheClearCommand</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">App</span><span class="pln">\Commands\ModificationClearCommand</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">Exception</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">Symfony</span><span class="pln">\Component\Console\Application</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">AppConsole</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="com">/**
     * @throws Exception
     */</span><span class="pln">
    </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> run</span><span class="pun">():</span><span class="pln"> </span><span class="kwd">void</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        $app </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Application</span><span class="pun">();</span><span class="pln">

        $app</span><span class="pun">-&gt;</span><span class="kwd">add</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ModificationClearCommand</span><span class="pun">());</span><span class="pln">
        $app</span><span class="pun">-&gt;</span><span class="kwd">add</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">CacheClearCommand</span><span class="pun">());</span><span class="pln">

        $app</span><span class="pun">-&gt;</span><span class="pln">run</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	 
</p>

<p>
	И создадим файл console в корне, который будет запускать саму консоль<br />
	 
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted"><span class="com">#!/usr/bin/env php</span><span class="pln">
</span><span class="pun">&lt;?</span><span class="pln">php

</span><span class="kwd">require</span><span class="pln"> dirname</span><span class="pun">(</span><span class="pln">__DIR__</span><span class="pun">)</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="str">'/путь к/vendor/autoload.php'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">require</span><span class="pln"> __DIR__ </span><span class="pun">.</span><span class="pln"> </span><span class="str">'/путь к/admin/config.php'</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">App</span><span class="pln">\Commands\AppConsole</span><span class="pun">;</span><span class="pln">

</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">AppConsole</span><span class="pun">())-&gt;</span><span class="pln">run</span><span class="pun">();</span></pre>

<p>
	 
</p>

<p>
	Делаем его исполняемым: <em>chmod +x console</em>
</p>

<p>
	 
</p>

<p>
	Теперь при запуске команды php console мы получим список доступных команд:
</p>

<p>
	<br /><em>php console cache:clear</em> - очистка системного кеша<br /><em>php console cache:modification</em> - очистка модификаторов
</p>

<p>
	<br />
	Таким образом можно создать другие команды: для сжатия картинок, для бэкапов базы и т.д.
</p>

<p>
	 
</p>

<p>
	<strong>Миграции баз данных</strong>
</p>

<p>
	 
</p>

<p>
	Данный подход позволяет фиксировать все изменения базы в PHP-коде. Следовательно, мы можем в Git хранить всю историю изменений базы данных.
</p>

<p>
	 
</p>

<p>
	Мы будем использовать библиотеку phinx. Установим ее:
</p>

<pre class="ipsCode">composer require robmorgan/phinx</pre>

<p>
	<br />
	Далее запускаем команду для создания конфигурационного файла phinx.php
</p>

<pre class="ipsCode">./vendor/bin/phinx init</pre>

<p>
	 
</p>

<p>
	Создадим папку <em>db/migrations</em> - тут будут храниться файлы миграций.
</p>

<p>
	В конфиге phinx.php прописываем путь для папки, где будут лежать файлы миграций и параметры подключения к БД
</p>

<p>
	 
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted"><span class="pun">&lt;?</span><span class="pln">php

</span><span class="kwd">return</span><span class="pln">
</span><span class="pun">[</span><span class="pln">
    </span><span class="str">'paths'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
        </span><span class="str">'migrations'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'%%PHINX_CONFIG_DIR%%/путь до папки db/migrations'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'seeds'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'%%PHINX_CONFIG_DIR%%/db/seeds'</span><span class="pln">
    </span><span class="pun">],</span><span class="pln">
    </span><span class="str">'environments'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
        </span><span class="str">'default_migration_table'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'phinxlog'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'default_environment'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'development'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'development'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
            </span><span class="str">'adapter'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'mysql'</span><span class="pun">,</span><span class="pln">
            </span><span class="str">'host'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'host db'</span><span class="pun">,</span><span class="pln">
            </span><span class="str">'name'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'opencart'</span><span class="pun">,</span><span class="pln">
            </span><span class="str">'user'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'admin'</span><span class="pun">,</span><span class="pln">
            </span><span class="str">'pass'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'1234567'</span><span class="pun">,</span><span class="pln">
            </span><span class="str">'port'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'3306'</span><span class="pun">,</span><span class="pln">
            </span><span class="str">'table_prefix'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'oc_'</span><span class="pun">,</span><span class="pln">
            </span><span class="str">'charset'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'utf8mb4'</span><span class="pun">,</span><span class="pln">
        </span><span class="pun">],</span><span class="pln">
    </span><span class="pun">],</span><span class="pln">
    </span><span class="str">'version_order'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'creation'</span><span class="pln">
</span><span class="pun">];</span></pre>

<p>
	 
</p>

<p>
	Ключ environments позволяет прописывать различные подключения к базе на проде или тестовом сервере и потом при запуске указывать, для какого окружения нужно запускать миграции.
</p>

<p>
	<br />
	Чтобы создать миграцию, запустите команду <em>create</em> и укажите название класса миграции, в названии которого будет отображена суть (для какой таблицы мы создаем миграцию). 
</p>

<p>
	 
</p>

<p>
	Например, создадим миграцию для таблицы с постами:
</p>

<pre class="ipsCode">./vendor/bin/phinx create PostsTableMigration</pre>

<p>
	 
</p>

<p>
	После чего в папке db/migrations появится файл с временной меткой в названии файла и именем миграции с snake case. Заполним файл:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted"><span class="pun">&lt;?</span><span class="pln">php

declare</span><span class="pun">(</span><span class="pln">strict_types</span><span class="pun">=</span><span class="lit">1</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">use</span><span class="pln"> </span><span class="typ">Phinx</span><span class="pln">\Migration\AbstractMigration</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">final</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">DocsMigration</span><span class="pln"> </span><span class="kwd">extends</span><span class="pln"> </span><span class="typ">AbstractMigration</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
   </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> change</span><span class="pun">():</span><span class="pln"> </span><span class="kwd">void</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        $table </span><span class="pun">=</span><span class="pln"> $this</span><span class="pun">-&gt;</span><span class="pln">table</span><span class="pun">(</span><span class="str">'docs'</span><span class="pun">);</span><span class="pln">
        $table</span><span class="pun">-&gt;</span><span class="pln">addColumn</span><span class="pun">(</span><span class="str">'route'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'string'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">-&gt;</span><span class="pln">addColumn</span><span class="pun">(</span><span class="str">'description'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'text'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">-&gt;</span><span class="pln">addColumn</span><span class="pun">(</span><span class="str">'created'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'datetime'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">[</span><span class="str">'default'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'CURRENT_TIMESTAMP'</span><span class="pun">])</span><span class="pln">
            </span><span class="pun">-&gt;</span><span class="pln">addIndex</span><span class="pun">([</span><span class="str">'route'</span><span class="pun">],</span><span class="pln"> </span><span class="pun">[</span><span class="str">'unique'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">])</span><span class="pln">
            </span><span class="pun">-&gt;</span><span class="pln">create</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	 
</p>

<p>
	Тут все просто, указываем название таблицы, колонки и их типы, задаем индексы. Для каждой таблицы автоматически создается автоинкрементное поле id. Полную инструкцию по написанию миграций можно найти на оф. сайте: <a href="https://book.cakephp.org/phinx/0/en/migrations.html" rel="external nofollow">https://book.cakephp.org/phinx/0/en/migrations.html</a>
</p>

<p>
	 
</p>

<p>
	Для запуска миграций воспользуемся командой:
</p>

<pre class="ipsCode">./vendor/bin/phinx migrate</pre>

<p>
	 
</p>

<p>
	После запуска данного скрипта в таблице phinxlog (настраивается в параметре default_migration_table из файла phinx.php) появится запись о примененной миграции. 
</p>

<p>
	<br /><strong>GitHub Actions</strong>
</p>

<p>
	 
</p>

<p>
	Остался последний и самый главный этап - настройка на стороне GitHub. 
</p>

<p>
	 
</p>

<p>
	Создаем в корне проекта файл (указываю полный путь) <em>.github/workflows/deploy.yml</em>. Здесь будут указаны все задачи, которые должны будут запускаться внутри контейнеров на стороне GitHub.
</p>

<pre class="ipsCode">name: Deploy
on:
  push:
    branches: [ main ]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Deploy to server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.KEY }}
          port: ${{ secrets.PORT }}
          script: |
            cd /путь к корню проекта
            ./.scripts/deploy.sh
            ./vendor/bin/phinx migrate</pre>

<p>
	 
</p>

<p>
	 
</p>

<p>
	Тут мы указываем на что будет реагировать наш Action. В данном случае на пуш в ветку main
</p>

<pre class="ipsCode">on:
  push:
    branches: [ main ]</pre>

<p>
	  
</p>

<p>
	<em>jobs</em> - непосредственно описываем задачи<br /><em>deploy</em> - имя задачи<br /><em>appleboy/ssh-action@master</em> - инструмент для запуска удаленных команд по SSH. Ниже указаны параметры для аутентификации и список выполняемых команд:<br />
	    - cd - переходим в корень проекта<br />
	    - ./.scripts/deploy.sh - запускаем баш-скрипт с нашими командами (опишем ниже)<br />
	    - ./vendor/bin/phinx migrate - запускаем миграции
</p>

<p>
	<br /><em>appleboy/ssh-action@master</em> - для аутентификации можно использовать ssh-юзера и пароль, но я указал настройки для входа по ключу. 
</p>

<p>
	 
</p>

<p>
	Для этого зайдем по SSH на сервер, куда мы будем деплоить проект и создадим связку ключей:
</p>

<pre class="ipsCode">ssh-keygen -t rsa -b 4096 -C "your_email@example.com"</pre>

<p>
	 
</p>

<pre class="ipsCode">cat  ~/.ssh/id_rsa.pub &gt;&gt; ~/.ssh/authorized_keys</pre>

<p>
	 
</p>

<p>
	Скопируем приватный ключ
</p>

<pre class="ipsCode">cat ~/.ssh/id_rsa</pre>

<p>
	 
</p>

<p>
	Теперь нужно заполнить секретные ключи в GitHub.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="753703" href="https://opencartforum.com/storage/attachment/monthly_2023_05/secret-key.png.afab2591b926a016d29ce2be2dfe6a3d.png" rel=""><img alt="secret-key.thumb.png.6ef272e838939b58dff61e2e4b5a9b9a.png" class="ipsImage ipsImage_thumbnailed" data-fileid="753703" data-ratio="61.30" style="height:auto;" width="1000" data-src="https://opencartforum.com/storage/attachment/monthly_2023_05/secret-key.thumb.png.6ef272e838939b58dff61e2e4b5a9b9a.png" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	 
</p>

<p>
	Переходим в наш репозиторий: Settings -&gt; Secrets and variables -&gt; Actions и добавляем наши секреты: <br /><em>HOST</em> - хост сервера<br /><em>PORT</em> - порт сервера<br /><em>KEY</em> - скопированный ранее приватный ключ<br /><em>USERNAME</em> - имя пользователя на сервере
</p>

<p>
	<br />
	Создаем баш-скрипт <em>.scripts/deploy.sh</em> для запуска консольных команд, которые мы создавали в начале статьи.
</p>

<pre class="ipsCode">#!/bin/bash
set -e

echo "Deployment started ..."

git pull

php console cache:modification
php console cache:clear

echo "Deployment finished!"</pre>

<p>
	<br />
	Тут мы забираем все изменения из git-репозитория и чистим кеши нашими консольными скриптами. Сюда можно добавить команду для включения режима обслуживания на время деплоя, но это оставлю вам для самостоятельного выполнения.
</p>

<p>
	 
</p>

<p>
	Разрешаем запуск данного скрипта
</p>

<pre class="ipsCode">chmod +x deploy.sh</pre>

<p>
	 
</p>

<p>
	Окончательная структура директорий
</p>

<p>
	 
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="753704" href="https://opencartforum.com/storage/attachment/monthly_2023_05/all-directories.png.e8373c00406237cb4f00a15141b885aa.png" rel=""><img alt="all-directories.thumb.png.02c4b75ed903714b5ca3677bd2b898ae.png" class="ipsImage ipsImage_thumbnailed" data-fileid="753704" data-ratio="82.24" style="height:auto;" width="912" data-src="https://opencartforum.com/storage/attachment/monthly_2023_05/all-directories.thumb.png.02c4b75ed903714b5ca3677bd2b898ae.png" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	<br />
	Теперь мы можем запушить новые изменения в репозиторий и увидим, как во вкладке Actions на GitHub появится новая задача. Мы можем нажать на нее и посмотреть детально на все происходящие процессы в контейнерах.
</p>

<p>
	 
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="753705" href="https://opencartforum.com/storage/attachment/monthly_2023_05/actions.png.8408816fb7639e11504b6e9826fac61f.png" rel=""><img alt="actions.thumb.png.b5341646b450e0daa8950ce6f276c586.png" class="ipsImage ipsImage_thumbnailed" data-fileid="753705" data-ratio="43.50" style="height:auto;" width="1000" data-src="https://opencartforum.com/storage/attachment/monthly_2023_05/actions.thumb.png.b5341646b450e0daa8950ce6f276c586.png" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	 
</p>

<p>
	Если произойдет ошибка, то вам на почту придет письмо, что Action завершился неудачно.
</p>

<p>
	 
</p>

<p>
	Сюда также можно добавить проверки на codestyle, запуск тестов и построить полноценное CI/CD, но это пока не про <abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом"><abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом">Opencart</abbr></abbr>...
</p>
]]></description><guid isPermaLink="false">501</guid><pubDate>Tue, 02 May 2023 06:50:08 +0000</pubDate></item><item><title>&#x421;&#x438;&#x441;&#x442;&#x435;&#x43C;&#x430; &#x43E;&#x447;&#x435;&#x440;&#x435;&#x434;&#x435;&#x439; &#x43D;&#x430; Opencart (RabbitMQ)</title><link>https://opencartforum.com/blogs/entry/419-sistema-ocheredey-na-opencart-rabbitmq/</link><description><![CDATA[<p>
	Я хочу затронуть тему очередей, которые используются повсеместно в интернет-проектах, но в <abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом"><abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом">Opencart</abbr></abbr> данная тема особо не освещена. 
</p>

<p>
	 
</p>

<p>
	Представьте, что вам нужно прямо сейчас сделать выгрузку для какого-то маркетаплейса, вот только в выгрузке у вас, например, 50к товаров. Крон у вас работает раз в сутки, а менеджер магазина не подключится по ssh и не будет запускать консольный скрипт выгрузки. В подобной ситуации вам поможет система очередей.
</p>

<p>
	<br />
	Очереди - это не конкретная технология, а только принцип. В самом примитивном виде у нас есть издатель и подписчик (producer и worker). Издатель добавляет в очередь данные, по сути инициирует выполнение какого-то действия. А воркер непосредственно выполняет нужную нам работу. Между ними присутствует обменник, который хранит сообщения и распределяет их между воркерами. При этом мы можем использовать несколько воркеров (обработчиков) для одной задачи. 
</p>

<p>
	 
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="659720" href="https://opencartforum.com/storage/attachment/monthly_2021_07/queue.jpeg.ea5a6ab3bb155c0b639f47ba2f678827.jpeg" rel=""><img alt="queue.thumb.jpeg.8ae1cc166e5617a0a76ae434795249da.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="659720" data-ratio="63.30" style="height:auto;" width="1000" data-src="https://opencartforum.com/storage/attachment/monthly_2021_07/queue.thumb.jpeg.8ae1cc166e5617a0a76ae434795249da.jpeg" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	<br /><span style="font-size:18px;"><strong>Где можно использовать очереди</strong></span>
</p>

<p>
	 
</p>

<p>
	<strong>1. Асинхронное (фоновое) выполнение задач.</strong><br />
	Допустим, что при оформлении заказа мы отправляем письма о новом заказе через внешний SMTP-сервер, админу кидаем уведомление в телеграм, а также экспортируем данные о заказе в CRM. 
</p>

<p>
	 
</p>

<p>
	Какие проблемы нас поджидают:
</p>

<ul><li>
		синхронное выполнение каждого процесса - пользователь будет ждать, пока отработает последовательно каждый из процессов
	</li>
	<li>
		зависимость от внешних сервисов - они вполне могут работать нестабильно и все это время клиент будет видеть экран загрузки, вместо быстрого оформления заказа.
	</li>
</ul><p>
	 
</p>

<p>
	Вместо этого мы создаем три отдельных очереди (для email, телеграма и CRM) и просто кидаем в них сообщения. Пользователь успешно оформляет заказ, а в это время процессы отправки писем и уведомлений выполняются параллельно в фоне. 
</p>

<p>
	Также можно: генерировать выгрузки в маркетплейсы по запросу пользователя, делать импорт/экспорт товаров, строить карты сайта, отчеты и т.д.
</p>

<p>
	<br /><strong>2. Имитация многопоточности. </strong><br />
	PHP "из коробки" однопоточный, но с помощью очередей можно сделать несколько обработчиков одной задачи.
</p>

<p>
	 
</p>

<p>
	Примеры:
</p>

<ul><li>
		Импорт товаров - у нас есть исходный эксель-файл с товарами. Распарсить файл и получить данные можно довольно быстро, а вот импорт данных потребует больших ресурсов, т.к. нужно выполнять вставку данных во множество таблиц. 
	</li>
</ul><p>
	 
</p>

<p>
	Исходя из этого разбиваем процесс импорта на два этапа:
</p>

<ol><li>
		 на первом этапе парсим эксель файл и в результате парсинга каждой строки получаем результирующий набор данных, нужный для создания товара. Этот набор данных мы кидаем в очередь для импорта. 
	</li>
	<li>
		создаем обработчик, который будет заниматься только импортом готовых данных. При этом мы можем запустить одновременно 5 таких обработчиков (или сколько позволят ваши ресурсы) и система очередей будет распределять задачи по импорту товаров между данными обработчиками. В итоге вместо построчного импорта товаров мы получаем 5 параллельных обработчиков импорта.
	</li>
</ol><p>
	 
</p>

<p>
	Данную технику можно применить для парсинга товаров. 
</p>

<p>
	<br /><span style="font-size:18px;"><strong>От теории перейдем к практике. </strong></span>
</p>

<p>
	<br /><strong>Система очередей на RabbitMQ</strong>
</p>

<p>
	Выше я уже писал, что система очередей - это лишь архитектурный принцип и он может быть реализован с помощью множества технологий: крон и база данных, хранилище типа Redis и т.д., но я выбрал в качестве примера специалированное ПО RabbitMQ. Кролик содержит множество возможностей: гибкую маршрутизацию, масштабируемость, хранилище сообщений и т.д. 
</p>

<p>
	 
</p>

<p>
	Я использую Docker в качестве среды для локальной разработки, поэтому покажу, как установить RabbitMQ именно в этой среде. Про Docker я писал в этой статье: <a href="https://opencartforum.com/blogs/entry/383-zapusk-i-otladka-opencart-s-pomoschyu-docker-i-xdebug/" rel="">https://opencartforum.com/blogs/entry/383-zapusk-i-otladka-<abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом"><abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом">opencart</abbr></abbr>-s-pomoschyu-docker-i-xdebug/</a>
</p>

<p>
	 
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		Цитата
	</div>

	<div class="ipsQuote_contents">
		<p>
			Установка в Ubuntu/Debian: <a href="https://www.rabbitmq.com/install-debian.html" rel="external nofollow">https://www.rabbitmq.com/install-debian.html</a><br />
			Установка для Windows: <a href="https://www.rabbitmq.com/install-windows.html" rel="external nofollow">https://www.rabbitmq.com/install-windows.html</a>
		</p>
	</div>
</blockquote>

<p>
	 
</p>

<p>
	<strong>Установка RabbitMQ с помощью docker compose</strong>
</p>

<p>
	1. В docker-compose.yml добавляем сервис:
</p>

<p>
	 
</p>

<pre class="ipsCode">
rabbitmq:
    image: rabbitmq:3-management-alpine
    hostname: my-rabbit
    volumes:
      - ./rabbitmq/etc/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf
      - ./rabbitmq/data:/var/lib/rabbitmq/mnesia/rabbit@my-rabbit
      - ./rabbitmq/logs:/var/log/rabbitmq/log
    ports:
      - 5672:5672
      - 15672:15672</pre>

<p>
	 
</p>

<p>
	2. В корневой директории Docker-проекта создаем папку rabbitmq и в ней создаем три папки: data, etc, logs для хранилища данных, конфигов и логов. В папке rabbitmq/etc создаем файл конфига rabbitmq.conf с содержимым:
</p>

<p>
	 
</p>

<pre class="ipsCode">
loopback_users.guest = true
listeners.tcp.default = 5672
management.listener.port = 15672
management.listener.ssl = false</pre>

<p>
	 
</p>

<p>
	Далее как обычно билдим все в Docker для установки нового сервиса и запускаем контейнеры: 
</p>

<p>
	 
</p>

<pre class="ipsCode">
docker-compose up -d --build</pre>

<p>
	 
</p>

<p>
	Для администрирования RabbitMQ поставляется с панелью, которая будет доступна по адресу: http://localhost:15672. В данной панели можно смотреть, какие очереди выполняются, получить сообщения, очистить очереди т.д. 
</p>

<p>
	 
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="659721" href="https://opencartforum.com/storage/attachment/monthly_2021_07/rabbitmq-panel.jpg.93c648737025f02a4467e8aa586241c1.jpg" rel=""><img alt="rabbitmq-panel.thumb.jpg.1b367cc8237f7cafb3abdcd918338e3b.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="659721" data-ratio="63.60" style="height:auto;" width="1000" data-src="https://opencartforum.com/storage/attachment/monthly_2021_07/rabbitmq-panel.thumb.jpg.1b367cc8237f7cafb3abdcd918338e3b.jpg" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	 
</p>

<p>
	Пароль и логин для доступа: <strong>guest</strong> / <strong>guest</strong>. Естественно, на рабочем сервере нужно удалить гостевого пользователя и добавить нового админа.
</p>

<p>
	<br /><span style="font-size:18px;"><strong>Использование RabbitMQ c <abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом"><abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом">Opencart</abbr></abbr></strong></span>
</p>

<p>
	 
</p>

<p>
	Написал простую библиотеку <a href="https://github.com/ozzzi/opencart-rabbitmq" rel="external nofollow">https://github.com/ozzzi/<abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом">opencart</abbr>-rabbitmq</a>
</p>

<p>
	 
</p>

<p>
	Для ее работы нужно:
</p>

<p>
	 
</p>

<p>
	1. Установить зависимость:
</p>

<p>
	 
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted">
<span class="pln">composer require php-amqplib/php-amqplib</span></pre>

<p>
	. 
</p>

<p>
	В библиотеке предполагается, что папка vendor будет лежать на уровень выше, чем корневая директория <abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом"><abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом">opencart</abbr></abbr>.
</p>

<p>
	 
</p>

<p>
	2. Добавить в config.php конфиг RabbitMQ:
</p>

<p>
	 
</p>

<pre class="ipsCode">
define('RABBITMQ_HOST', 'host');
define('RABBITMQ_PORT', '5672');
define('RABBITMQ_USER', 'guest');
define('RABBITMQ_PASS', 'guest');</pre>

<p>
	 
</p>

<p>
	Если речь идет о исполнении в докер-контейнере, то в качестве хоста нужно указать айпи контейнера (читаем предыдущую статью о докере).
</p>

<p>
	 
</p>

<p>
	Затем в опенкарте можем использовать библиотеку:
</p>

<p>
	 
</p>

<pre class="ipsCode">
$this-&gt;load-&gt;library('queue');
$this-&gt;queue-&gt;addTask('queueName', ['some' =&gt; 'data']);</pre>

<p>
	 
</p>

<p>
	Первый параметр в методе <strong>addTask</strong> - имя очереди, а второй параметр (опциональный) - данные, которые нужно передать в воркер.
</p>

<p>
	 
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		Цитата
	</div>

	<div class="ipsQuote_contents">
		<p>
			Воркеры или обработчики - это независимые скрипты, которые мало того, что могут быть написаны на разных языках, они также могут располагаться на других серверах. Т.к. с опенкартом их напрямую ничего не связывает, то и писать их можно в удобном для вас виде. Для примеров я буду использовать мини-приложения с PSR-4 автозагрузчиком из composer.
		</p>
	</div>
</blockquote>

<p>
	 
</p>

<p>
	<br /><span style="font-size:18px;"><strong>Примеры</strong></span>
</p>

<p>
	 
</p>

<p>
	Далее я приведу примеры использования очередей для отправки почты, импорта товаров из Excel, и парсинга сайтов.
</p>

<p>
	 
</p>

<p>
	<span style="font-size:14px;"><strong>1. Отправка email</strong></span>
</p>

<p>
	Полный код: <a href="https://github.com/ozzzi/email_service" rel="external nofollow">https://github.com/ozzzi/email_service</a>
</p>

<p>
	 
</p>

<p>
	В коде <abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом"><abbr title="Бесплатная платформа для электронной коммерции с открытым исходным кодом">Opencart</abbr></abbr>-а меняем родной код отправки email-ов на:
</p>

<p>
	 
</p>

<pre class="ipsCode">
$this-&gt;load-&gt;library('queue');
$this-&gt;queue-&gt;addTask('email', ['data' =&gt; 'examlpe_data']);</pre>

<p>
	 
</p>

<p>
	Т.е. мы добавили в очередь email данные для обработки: кому, от кого и что отсылаем.
</p>

<p>
	За обработку данной очереди отвечает воркер: <strong>cli/email_notify.php</strong>. Вы должны сами реализовать получение конфигов для SMTP-сервера.
</p>

<p>
	 
</p>

<p>
	В панели управления RabbitMQ можно увидеть, что во вкладке очередей появилась новая очередь и одно сообщение. Чтобы очередь его отработала, воркер cli/email_notify.php должен быть запущен. Для примера мы запустим его вручную внутри контейнера, а в конце я объясню, как сделать запуск автоматическим.
</p>

<p>
	 
</p>

<p>
	Смотрим имена контейнеров:
</p>

<p>
	 
</p>

<pre class="ipsCode">
docker ps</pre>

<p>
	 
</p>

<p>
	Нас интересует, контейнер с PHP-FPM. Чтобы войти в него выполняем:
</p>

<p>
	 
</p>

<pre class="ipsCode">
docker exec -it container_name bash</pre>

<p>
	 
</p>

<p>
	Далее переходим в папку cli и стандартно запускаем скрипт:
</p>

<p>
	 
</p>

<pre class="ipsCode">
php email_notify.php</pre>

<p>
	 
</p>

<p>
	Если все прошло успешно, в панели кроля вы увидите, что очередь email очистилась. Значит сообщение ушло получателю.
</p>

<p>
	 
</p>

<p>
	<br /><strong>2. Импорт товаров из Excel</strong>
</p>

<p>
	Полный код: <a href="https://github.com/ozzzi/excel_import_worker" rel="external nofollow">https://github.com/ozzzi/excel_import_worker</a>
</p>

<p>
	 
</p>

<p>
	В админской части вам нужно реализовать загрузку файла, передать конфиг настроек полей и подключить загрузчик композер: '/vendor/autoload.php'
</p>

<p>
	 
</p>

<p>
	Затем реализовываем наш загрузчик (producer):
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted">
<span class="kwd">use</span><span class="pln"> </span><span class="typ">App</span><span class="pln">\Service\ExcelParser</span><span class="pun">;</span><span class="pln">

$file </span><span class="pun">=</span><span class="pln"> </span><span class="str">'price.xlsx'</span><span class="pun">;</span><span class="pln">

$setting </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="str">'category'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'B'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'name'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'C'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'model'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'D'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'price'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'G'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'quantity'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'H'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'manufacturer'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'F'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'description'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="str">'M'</span><span class="pun">,</span><span class="pln">
</span><span class="pun">];</span><span class="pln">

$this</span><span class="pun">-&gt;</span><span class="pln">load</span><span class="pun">-&gt;</span><span class="pln">library</span><span class="pun">(</span><span class="str">'queue'</span><span class="pun">);</span><span class="pln">

$excelService </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ExcelParser</span><span class="pun">(</span><span class="pln">$setting</span><span class="pun">,</span><span class="pln"> $file</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">foreach</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$excelService</span><span class="pun">-&gt;</span><span class="pln">parse</span><span class="pun">()</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> $product</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    $this</span><span class="pun">-&gt;</span><span class="pln">queue</span><span class="pun">-&gt;</span><span class="pln">addTask</span><span class="pun">(</span><span class="str">'import'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">[</span><span class="str">'product'</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> $product</span><span class="pun">]);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	 
</p>

<p>
	В итоге после чтения каждой строки мы добавляем в очередь import объект с данными товара. Чтобы обработать все сообщение из очереди "импорт" запускаем воркер: cli/product_import.php (App\Service\ProductImport - вам нужно реализовать самим <img alt=":)" data-emoticon="" height="20" src="https://opencartforum.com/storage/smiles/emoticons/test_smile.png" srcset="https://opencartforum.com/storage/smiles/emoticons/smile@2x.png 2x" title=":)" width="20" /> ). Чтобы процесс пошел "бодрей", можно запустить параллельно несколько воркеров product_import.php.
</p>

<p>
	 
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="659722" href="https://opencartforum.com/storage/attachment/monthly_2021_07/excel.jpeg.8ce132a51674429d3e2056e679d8f42f.jpeg" rel=""><img alt="excel.thumb.jpeg.e88c4dea807a15fc7da2500998fc154b.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="659722" data-ratio="39.90" style="height:auto;" width="1000" data-src="https://opencartforum.com/storage/attachment/monthly_2021_07/excel.thumb.jpeg.e88c4dea807a15fc7da2500998fc154b.jpeg" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	 
</p>

<p>
	 
</p>

<p>
	<strong>3. Парсер сайта в несколько потоков</strong>
</p>

<p>
	Полный код: <a href="https://github.com/ozzzi/html_parser_worker" rel="external nofollow">https://github.com/ozzzi/html_parser_worker</a>
</p>

<p>
	 
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		Цитата
	</div>

	<div class="ipsQuote_contents">
		<p>
			Информация выложена в учебных целях. Не нужно убивать чужие сайты своими парсерами!
		</p>
	</div>
</blockquote>

<p>
	 
</p>

<p>
	Для примера возьмем первый попавшийся магазин стройматериалов (ссылка в коде) и спарсим все товары из категории "Сухие смеси". Разделим процесс парсинга на два этапа:
</p>

<p>
	 
</p>

<ol><li>
		Получение списка ссылок на карточки товаров. За один запрос мы можем получить все ссылки на товары для первой страницы. Если страниц пагинации в категории 5 и в среднем на загрузку страницы уходит 1 секунда, то за 5 секунд мы получим все ссылки на товары в данной категории. 
	</li>
	<li>
		Парсинг страниц товаров в несколько потоков. Т.к. каждая карточка товара загружается условную секунду, то данный процесс нам нужно распараллелить, запустив несколько воркеров.
	</li>
</ol><p>
	 
</p>

<p>
	Запускаем скрипт <strong>cli/parser_category.php</strong>, который будет отправлять в очередь parse_product ссылки на товары.
</p>

<p>
	 
</p>

<p>
	Затем запустим несколько воркеров <strong>cli/import_worker.php</strong>. В результате <em>ProductParser</em> возвращает сущность Товар, которая импортируется с помощью сервиса <em>ProductImport</em>, с которым вы уже знаете, что делать. 
</p>

<p>
	<br />
	Надеюсь, что вы поняли, как применять очереди в таком простом виде.
</p>

<p>
	 
</p>

<p>
	Теперь расскажу, как запускать воркеры как демоны в автоматическом режиме (чтобы они работали постоянно и перезапускались сами вместе с системой). Один из способов - <strong>supervisor</strong>. 
</p>

<p>
	 
</p>

<p>
	Установка для Debian\Ubuntu стандартная:
</p>

<p>
	 
</p>

<pre class="ipsCode">
apt-get install supervisor</pre>

<p>
	<br />
	Добавляем конфиг для запуска конкретного скрипта. Например, добавим конфиг для запуска воркера в виде двух процессов:
</p>

<p>
	<br />
	Создаем файл:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted">
<span class="pln">/etc/supervisor/conf.d/worker.conf</span></pre>

<pre class="ipsCode">
[program:worker]
command=php /path/to/script.php
stdout_logfile=/var/log/worker.log
autostart=true
autorestart=true
user=www-data
stopsignal=KILL
process_name=%(program_name)s_%(process_num)02d
numprocs=2</pre>

<p>
	 
</p>

<p>
	Перезагружаем supervisor: 
</p>

<p>
	 
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted">
<span class="pln">service supervisor restart</span></pre>

<p>
	<br />
	Если вам скучно, обязательно попробуйте очереди. <img alt=":)" data-emoticon="" height="20" src="https://opencartforum.com/storage/smiles/emoticons/test_smile.png" srcset="https://opencartforum.com/storage/smiles/emoticons/smile@2x.png 2x" title=":)" width="20" /></p>]]></description><guid isPermaLink="false">419</guid><pubDate>Thu, 08 Jul 2021 08:03:59 +0000</pubDate></item><item><title>&#x417;&#x430;&#x43F;&#x443;&#x441;&#x43A; &#x438; &#x43E;&#x442;&#x43B;&#x430;&#x434;&#x43A;&#x430; Opencart &#x441; &#x43F;&#x43E;&#x43C;&#x43E;&#x449;&#x44C;&#x44E; Docker &#x438; xDebug</title><link>https://opencartforum.com/blogs/entry/383-zapusk-i-otladka-opencart-s-pomoschyu-docker-i-xdebug/</link><description><![CDATA[<p>
	Если вы до сих пор в качестве тестовой среды используете OpenServer или Хостинг+FTP, а единственным инструментом для дебага является var_dump, то самое время попробовать Docker и xDebug.
</p>

<p>
	 
</p>

<p>
	<strong>Docker</strong> - ПО для запуска сервисов в изолированных контейнерах. Т.е. для запуска приложения мы можем создать необходимое количество контейнеров: веб-сервер, интерпретатор PHP, запустить базу данных. Нам не нужно захламлять систему всеми этими сервисами, все будет установлено внутри докера. При этом мы можем использовать для разных проектов разный набор версий php, mysql и т.д.
</p>

<p>
	 
</p>

<p>
	В отличии от полноценных виртуальных машин (VirtualBox + Vagrant) нам не нужно скачивать образ операционной системы и ставить каждый раз полноценные версии сервисов. В докере используются облегченные версии ПО и если в одном проекте вы уже использовали PHP 7.3, то для другого проекта не нужно будет ничего качать и заново пересоздавать.
</p>

<p>
	Вместе с Docker мы будем использовать дополнительный инструмент од названием Docker Compose. Он помогаем с помощью одного конфигурационного файла управлять группой контейнеров. 
</p>

<p>
	 
</p>

<p>
	Вместе с Docker мы будем использовать дополнительный инструмент од названием Docker Compose. Он помогаем с помощью одного конфигурационного файла управлять группой контейнеров. 
</p>

<p>
	 
</p>

<p>
	<strong>Что мы установим для нашего проекта:</strong>
</p>

<ul><li>
		PHP-FPM 7.3
	</li>
	<li>
		Nginx
	</li>
	<li>
		MySQL 5.7
	</li>
	<li>
		Adminer
	</li>
</ul><p>
	 
</p>

<p>
	С докером определились, он поможет за 5 минут создать тестовое окружение с нужным набор сервисов.
</p>

<p>
	 
</p>

<p>
	<strong>XDebug</strong> - дополнение к PHP, которое помогает производить отладку и профилирование кода. В статье будет описана работа совместно с IDE: PhpStorm и VS Code. 
</p>

<p>
	 
</p>

<p>
	Приступим к практике.
</p>

<p>
	 
</p>

<p>
	<strong>Установка Docker</strong>
</p>

<p>
	Docker: <a href="https://docs.docker.com/get-docker/" rel="external nofollow">https://docs.docker.com/get-docker/</a><br />
	Docker Compose: <a href="https://docs.docker.com/compose/install/" rel="external nofollow">https://docs.docker.com/compose/install/</a>
</p>

<p>
	 
</p>

<p>
	Надеюсь, git у вас уже установлен (если нет, читайте блог <a href="https://opencartforum.com/blogs/entry/326-git-i-opencart-dlya-samyh-malenkih" rel="">https://opencartforum.com/blogs/entry/326-git-i-opencart-dlya-samyh-malenkih</a>). 
</p>

<p>
	Переходим в директорию проекта и клонируем репозиторий:
</p>

<p>
	 
</p>

<pre class="ipsCode">
git clone git@github.com:ozzzi/docker-opencart.git .</pre>

<p>
	В данном репозитории содержится конфигурация для запуска Opencart.
</p>

<p>
	 
</p>

<p>
	Структура директорий:
</p>

<p>
	<strong>db</strong> - файлы базы данных<br /><strong>hosts</strong> - конфигурационный файл nginx. Конфигурационный файл настроен на использование домена oc.dev<br /><strong>images</strong> - файлы для создания образов<br />
	    <strong>Dockerfile</strong> - файл конфигурации образа<br />
	    <strong>php.ini</strong> - конфиг PHP<br />
	    <strong>mods</strong> - директория для загрузки конфигов дополнений <br /><strong>logs</strong> - логи различных сервисов. По умолчанию настроен только лог для Nginx<br /><strong>.env</strong> - переменные окружения (в нем хранятся конфиги для MySQL).
</p>

<p>
	 
</p>

<p>
	<strong>docker-compose.yml</strong> - конфигурационный файл Docker Compose
</p>

<p>
	 
</p>

<p>
	Разберем файл docker-compose.yml
</p>

<p>
	<strong>services</strong> - список сервисов (контейнеров). Для каждого сервиса (nginx, mysql и т.д.) принято создавать отдельный контейнер.
</p>

<p>
	 
</p>

<p>
	Список основных директив:<br /><strong>image</strong> - образ для создания контейнера. Образы хранятся тут: <a href="https://hub.docker.com" rel="external nofollow">https://hub.docker.com</a><br /><strong>build</strong> - путь к Dockerfile для создания кастомного образа. Например, для PHP нам нужно установить ряд дополнений, поэтому нам нужно создать собственный образ. MySQL, Nginx мы вполне можем использовать без изменений. <br /><strong>container_name</strong> - имя контейнера<br /><strong>ports</strong> - пробрасываем порты из контейнера на порты локального компьютера (порт компьютера:порт контейнера).<br /><strong>volumes</strong> - связываем папки на локальном компьютере и директории в контейнере. Используем для пробрасывания конфигов и файлов в контейнер, а также для сохранения данных сервисов в контейнере после перезагрузки (базы данных и т.д.).
</p>

<p>
	 
</p>

<p>
	Перейдем к настройке PHP в Dockerfile-е.
</p>

<p>
	<strong>FROM</strong> - импортируем базовый образ из docker hub.<br /><strong>RUN</strong> - запускаем консольные команды Linux внутри контейнера для установки нужных пакетов: curl, zip и т.д.<br /><strong>ADD</strong> - импортируем файлы конфигов php и конфиг xDebug в контейнер<br /><strong>WORKDIR</strong> - рабочая директория
</p>

<p>
	 
</p>

<p>
	Команды для управления нашими контейнерами:<br /><strong>docker-compose up</strong> - запуск контейнеров<br /><strong>docker-compose up -d</strong> - тоже самое, но можно не держать открытым окно терминала<br /><strong>docker-compose up -d --build</strong> - собираем проект и запускаем, после внесения изменений также нужно запускать с данным ключем.<br /><strong>docker-compose stop</strong> - останавливаем контейнеры<br /><strong>docker-compose down</strong> - останавливаем  и удаляем контейнеры<br /><strong>docker-compose ps</strong> - выводит список запущенных контейнеров
</p>

<p>
	 
</p>

<p>
	<strong>Установка опенкарт</strong>
</p>

<p>
	Файлы движка нужно закинуть в директорию <strong>www/oc.dev</strong>.
</p>

<p>
	 
</p>

<p>
	Чтобы домен был доступен по адресу oc.dev, нужно править файл hosts:<br /><strong>Ubuntu</strong>:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted">
<span class="pln">sudo gedit(ваш текстовый редактор) /etc/hosts</span></pre>

<p>
	<strong>Windows</strong><span>:</span>
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted">
<span class="pln">windows\system32\drivers\etc\hosts</span></pre>

<p>
	Вносим запись:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted">
<span class="pln">127.0.0.1 oc.dev</span></pre>

<p>
	Данные для доступа к базе данных хранятся в файле .env (название базы, пользователь и пароль), а в качестве имени хоста мы используем имя контейнера базы данных: <strong>db</strong>.
</p>

<p>
	Устанавливаем Opencart как обычно.
</p>

<p>
	 
</p>

<p>
	сли у вас есть проблемы с доступом к файлам и папкам (логи, кеш), вам помогут эти команды:<br /><em>sudo chown -R $USER:$USER directory</em> - устанавливает текущего пользователя владельцем директории (в контейнере скрипты исполняются от пользователя www-data)<br /><em>sudo chmod -R 777 directiroy</em> - права 777 для директории
</p>

<p>
	 
</p>

<p>
	<strong>Настройка xDebug</strong>
</p>

<p>
	 
</p>

<p>
	Файлы конфигурации хранятся по адресу:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted">
<span class="pln">/images/php/mods/xdebug.ini</span></pre>

<p>
	 
</p>

<p>
	Здесь нас интересует одна опция <strong>xdebug.remote_host. </strong>В ней нужно указать ip-адрес контейнера. Для Windows и Mac можно указать равным <strong>host.docker.internal</strong>
</p>

<p>
	 
</p>

<p>
	Для Linux на запущенном контейнере набираем в консоли:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted">
<span class="pln">docker inspect php-fpm | grep IPAddress</span></pre>

<p>
	Полученный IP прописываем в конфиг xdebug.ini, в моем случае:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted">
<span class="pln">xdebug.remote_host = 172.25.0.2 (или 172.25.0.1, если не заведется отладчик)</span></pre>

<p>
	Остановим и перезапустим наши контейнеры с опцией --build.
</p>

<p>
	 
</p>

<p>
	<strong>Настройка PHPStorm для работы с xDebug</strong>
</p>

<p>
	 
</p>

<p>
	1. Заходим в меню Run/Edit Configuration. Добавляем тип конфига "PHP Remote Debug"
</p>

<p>
	 
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="610948" href="https://opencartforum.com/storage/attachment/monthly_2021_01/1-step.jpg.4a17467013892e83e08b5513b80fab80.jpg" rel=""><img alt="1-step.thumb.jpg.f5748f36a54997592d39ffadcc970540.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="610948" data-ratio="62.90" style="height:auto;" width="1000" data-src="https://opencartforum.com/storage/attachment/monthly_2021_01/1-step.thumb.jpg.f5748f36a54997592d39ffadcc970540.jpg" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	 
</p>

<p>
	2. Нужно добавить Server
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="610949" href="https://opencartforum.com/storage/attachment/monthly_2021_01/2-step.jpg.3e1c36b9bc01b7a7b53fd7419d954956.jpg" rel=""><img alt="2-step.thumb.jpg.e86f8c569c6fffd714dbdc3ef0a6a285.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="610949" data-ratio="65.30" style="height:auto;" width="1000" data-src="https://opencartforum.com/storage/attachment/monthly_2021_01/2-step.thumb.jpg.e86f8c569c6fffd714dbdc3ef0a6a285.jpg" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	 
</p>

<p>
	<img alt="3-step.jpg.dbbb23f975f9e10992d7f17100e7c2d7.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="610950" data-ratio="83.72" style="height:auto;" width="823" data-src="https://opencartforum.com/storage/attachment/monthly_2021_01/3-step.jpg.dbbb23f975f9e10992d7f17100e7c2d7.jpg" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></p>

<p>
	 
</p>

<p>
	<strong>Name</strong> - имя сервера вводим любое. Нужно поставить галочку Use path mappings, значения должны быть такими:<br /><strong>слева (File/Directory)</strong> - путь до папки на локальном компьютере, куда копировали Opencart.<br /><strong>справа (Absolute path on the server)</strong> - путь внутри контейнера: /var/www/oc.dev
</p>

<p>
	 
</p>

<p>
	3. Заполняем название конфигурации (любое). Вносим метку, на которую будет реагировать IDE: <strong>PHPSTORM</strong>.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="610951" href="https://opencartforum.com/storage/attachment/monthly_2021_01/4-step.jpg.fd7713a55dc721a8660f9aebf8dd1a05.jpg" rel=""><img alt="4-step.thumb.jpg.634d34f7201a807d54ae4c036143f016.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="610951" data-ratio="65.20" style="height:auto;" width="1000" data-src="https://opencartforum.com/storage/attachment/monthly_2021_01/4-step.thumb.jpg.634d34f7201a807d54ae4c036143f016.jpg" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	 
</p>

<p>
	 
</p>

<p>
	4. Проверим работу xDebug нажатием на ссылку Validate.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="610952" href="https://opencartforum.com/storage/attachment/monthly_2021_01/5-step.jpg.8c592e491aa4a668cb7f4a2ef5ca35b8.jpg" rel=""><img alt="5-step.thumb.jpg.9718e340b3dd2b1d65be75490bce0a30.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="610952" data-ratio="65.70" style="height:auto;" width="1000" data-src="https://opencartforum.com/storage/attachment/monthly_2021_01/5-step.thumb.jpg.9718e340b3dd2b1d65be75490bce0a30.jpg" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	 
</p>

<p>
	<strong>Приступаем к отладке</strong>
</p>

<p>
	 
</p>

<p>
	Для примера мы хотим узнать, какие модули отображаются на главной странице в позиции Top. Открываем файл catalog/controller/common/content_top.php. Нас заинтересовала строка 49, в которой можно посмотреть код модуля. Возле номера строки ставим точку остановки (Breakpoint) левой кнопкой мыши. 
</p>

<p>
	 
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="610953" href="https://opencartforum.com/storage/attachment/monthly_2021_01/6-step.jpg.970a3f37d469261564a6bc605e66f7ee.jpg" rel=""><img alt="6-step.thumb.jpg.8087c898f2e39d54438c1fab83d7470d.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="610953" data-ratio="51.60" style="height:auto;" width="1000" data-src="https://opencartforum.com/storage/attachment/monthly_2021_01/6-step.thumb.jpg.8087c898f2e39d54438c1fab83d7470d.jpg" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	 
</p>

<p>
	Чтобы вызвать работу отладчика выбираем конфигурацию, которую мы добавилил в 3-м пункте, жмем на Debug 'Docker' (зеленый жук) в панели справа,сверху и включаем прослушку Start Listaning for PHP Debug Connections (трубка). Обновляем главную страницу сайта. Должен сработать отладчик и вы увидите:
</p>

<p>
	 
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="610954" href="https://opencartforum.com/storage/attachment/monthly_2021_01/7-step.jpg.c2466c630fdf02b7faa490400b7eff95.jpg" rel=""><img alt="7-step.thumb.jpg.43457db304b30eac58fc52db100a8437.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="610954" data-ratio="30.40" style="height:auto;" width="1000" data-src="https://opencartforum.com/storage/attachment/monthly_2021_01/7-step.thumb.jpg.43457db304b30eac58fc52db100a8437.jpg" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	 
</p>

<p>
	 
</p>

<p>
	Окно отладки:<br />
	1. Данной кнопкой можно поменять брекпойнты и вернуться назад к отладке.<br />
	2. Кнопка просмотра точек остановки.<br />
	3. Синие стрелки вверх/вниз позволяют пробегать построчно по коду (вверх/вниз)<br />
	4. Все доступные переменные на момент остановки скрипта.<br />
	5. Стек вызова - можем проследить полный путь вызова.<br />
	 
</p>

<p>
	В окне отладки мы видим все доступные переменные в указанной нами точке. Мы поставили точку в цикле и скрипт останавливается в первой итерации. Но если мы хотим посмотреть значения переменных в определенной итерации, нажмем правой кнопкой мыши на точке остановки и зададим условие для ее срабатывания. В моем случае остановимся, когда отображается модуль Featured.
</p>

<p>
	 
</p>

<p>
	<img alt="8-step.jpg.bc4b9d71f6e7210d137b8dcdb47ac3d8.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="610955" data-ratio="63.96" style="height:auto;" width="677" data-src="https://opencartforum.com/storage/attachment/monthly_2021_01/8-step.jpg.bc4b9d71f6e7210d137b8dcdb47ac3d8.jpg" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></p>

<p>
	 
</p>

<p>
	 
</p>

<p>
	<strong>Настройка VS Code для работы с xDebug</strong>
</p>

<p>
	 
</p>

<p>
	1. У вас должно быть установлено дополнение PHP Debug
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="610956" href="https://opencartforum.com/storage/attachment/monthly_2021_01/9-step.jpg.9c6aa39c736816ac048636477ed27852.jpg" rel=""><img alt="9-step.thumb.jpg.d78a02008fcb8b5eb4d2b1515f7ede10.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="610956" data-ratio="26.80" style="height:auto;" width="1000" data-src="https://opencartforum.com/storage/attachment/monthly_2021_01/9-step.thumb.jpg.d78a02008fcb8b5eb4d2b1515f7ede10.jpg" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	 
</p>

<p>
	2. Breakpoints ставятся возле нумерации строк.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="610958" href="https://opencartforum.com/storage/attachment/monthly_2021_01/10-step.jpg.fcd729ae5f6cbd24ded43c27634643cd.jpg" rel=""><img alt="10-step.thumb.jpg.7c2f2212c5f8da4a5da5f18d666d9beb.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="610958" data-ratio="46.10" style="height:auto;" width="1000" data-src="https://opencartforum.com/storage/attachment/monthly_2021_01/10-step.thumb.jpg.7c2f2212c5f8da4a5da5f18d666d9beb.jpg" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	 
</p>

<p>
	3. Включаем окно отладки (Ctrl + Shift + D) и нажимаем на ссылку create a launch.json file.
</p>

<p>
	<img alt="11-step.jpg.944f69d02470dc08b6066dffcaf5bff7.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="610959" data-ratio="82.25" style="height:auto;" width="586" data-src="https://opencartforum.com/storage/attachment/monthly_2021_01/11-step.jpg.944f69d02470dc08b6066dffcaf5bff7.jpg" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></p>

<p>
	 
</p>

<p>
	Код launch.json
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted">
<span class="pun">{</span><span class="pln">
    </span><span class="str">"version"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"0.2.0"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"configurations"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
        </span><span class="pun">{</span><span class="pln">
            </span><span class="str">"name"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Listen for XDebug"</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"php"</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"request"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"launch"</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"port"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">9000</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"log"</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"externalConsole"</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"pathMappings"</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="str">"/var/www/oc.dev/"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"${workspaceFolder}"</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">]</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	 
</p>

<p>
	4. Отладка:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="610960" href="https://opencartforum.com/storage/attachment/monthly_2021_01/12-step.jpg.1902bc6848f408b1e2e846404569ee34.jpg" rel=""><img alt="12-step.thumb.jpg.26a82e12b2861266d0a953d6f1a81cdc.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="610960" data-ratio="72.00" style="height:auto;" width="1000" data-src="https://opencartforum.com/storage/attachment/monthly_2021_01/12-step.thumb.jpg.26a82e12b2861266d0a953d6f1a81cdc.jpg" src="https://opencartforum.com/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	 
</p>

<ol><li>
		Запуск отладчика
	</li>
	<li>
		Панель управления. Здесь также можно остановить и обновить отладку, а также пробежаться по строчкам кода.
	</li>
	<li>
		Здесь отображаются все переменные.
	</li>
	<li>
		Стек вызова.
	</li>
</ol><p>
	 
</p>

<p>
	Надеюсь, что эти советы помогут вам вести более продуктивную разработку и отладку кода.
</p>

<p>
	 
</p>]]></description><guid isPermaLink="false">383</guid><pubDate>Sun, 10 Jan 2021 15:19:00 +0000</pubDate></item></channel></rss>
