<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>mcodex</title>
	<atom:link href="https://mcodex.ru/feed/" rel="self" type="application/rss+xml" />
	<link>https://mcodex.ru/</link>
	<description>Короткие статьи об основных понятиях, терминах и концепциях веб-разработки</description>
	<lastBuildDate>Tue, 25 Aug 2026 16:07:16 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1</generator>

<image>
	<url>https://mcodex.ru/wp-content/uploads/2025/11/cropped-favicon_650-32x32.png</url>
	<title>mcodex</title>
	<link>https://mcodex.ru/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Разбираемся с сервис-контейнером в Laravel.</title>
		<link>https://mcodex.ru/laravel-service-container/</link>
		
		<dc:creator><![CDATA[mcodex]]></dc:creator>
		<pubDate>Sun, 09 Aug 2026 15:14:12 +0000</pubDate>
				<category><![CDATA[Laravel]]></category>
		<category><![CDATA[PHP]]></category>
		<guid isPermaLink="false">https://mcodex.ru/?p=405</guid>

					<description><![CDATA[<p>Содержание Keynotes Почти каждый, кто пишет на Laravel, хотя бы раз слышал: &#8220;ну это же контейнер разрешает зависимости&#8221;. Звучит солидно, но за этой фразой у многих пустота. Давайте её заполним. Мы пойдём от проблемы к решению: сначала посмотрим, какую боль контейнер лечит, потом разберём, как он устроен внутри, а в конце напишем свой мини-контейнер строк ... <a title="Разбираемся с сервис-контейнером в Laravel." class="read-more" href="https://mcodex.ru/laravel-service-container/" aria-label="Read more about Разбираемся с сервис-контейнером в Laravel.">Read more</a></p>
<p>Сообщение <a href="https://mcodex.ru/laravel-service-container/">Разбираемся с сервис-контейнером в Laravel.</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h1 class="wp-block-heading">Содержание</h1>



<ol class="wp-block-list">
<li><a href="#block-1" data-type="internal" data-id="#block-1">Сначала главное: что такое контейнер и провайдер</a></li>



<li><a href="#block-2">Проблема, которой не видно на маленьких проектах</a></li>



<li><a href="#block-3">Первое лекарство: внедрение зависимостей</a></li>



<li><a href="#block-4">Проблема, которую породило лекарство</a></li>



<li><a href="#block-5">Что такое контейнер</a></li>



<li><a href="#block-6">Автоматическое разрешение зависимостей</a></li>



<li><a href="#block-7">Привязки</a></li>



<li><a href="#block-8">Контекстные привязки и атрибуты</a></li>



<li><a href="#block-9">Как получить объект из контейнера</a></li>



<li><a href="#block-10">Сервис-провайдеры</a></li>



<li><a href="#block-11">Фасады и вспомогательные функции</a></li>



<li><a href="#block-12">Дополнительные возможности контейнера</a></li>



<li><a href="#block-13">Тестирование</a></li>



<li><a href="#block-14">Типичные ошибки</a></li>



<li><a href="#block-15">Свой мини-контейнер</a></li>



<li><a href="#block-16">Итог</a></li>
</ol>



<h2 class="wp-block-heading">Keynotes</h2>



<ul class="wp-block-list">
<li><strong>Контейнер</strong> <strong>(service container)</strong> отвечает за создание объектов и их зависимостей.</li>



<li><strong>Внедрение зависимостей (dependency injection, DI)</strong> означает, что класс получает нужные ему объекты извне, а не создаёт их сам.</li>



<li><strong>Автоматическое разрешение зависимостей (autowiring)</strong> &#8211; контейнер читает типы параметров конструктора и сам создаёт нужные объекты, рекурсивно повторяя это для их зависимостей.</li>



<li><strong>Привязка (binding)</strong> нужна, когда контейнеру недостаточно информации: например, когда зависимость является интерфейсом или требует конкретных значений.</li>



<li><strong>Сервис-провайдер</strong> <strong>(service provider)</strong> &#8211; класс, который Laravel запускает при старте приложения; в нём регистрируют такие привязки и выполняют начальную настройку.</li>



<li><strong>Контекстные атрибуты (contextual attributes)</strong> (Laravel 11+) позволяют описать то же правило прямо у параметра конструктора, не заводя привязку в провайдере.</li>



<li>В большинстве обычных классов достаточно написать тип зависимости в конструкторе. Не нужно регистрировать каждый класс вручную.</li>



<li>Контейнер — это инструмент сборки объектов. Он не заменяет внедрение зависимостей и не является обязательной частью самого принципа DI.</li>
</ul>



<p class="wp-block-paragraph">Почти каждый, кто пишет на Laravel, хотя бы раз слышал: &#8220;ну это же контейнер разрешает зависимости&#8221;. Звучит солидно, но за этой фразой у многих пустота. Давайте её заполним.</p>



<p class="wp-block-paragraph">Мы пойдём от проблемы к решению: сначала посмотрим, какую боль контейнер лечит, потом разберём, как он устроен внутри, а в конце напишем свой мини-контейнер строк на семьдесят, чтобы магия окончательно перестала быть магией.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="block-1" class="wp-block-heading">1. Сначала главное: что такое контейнер и провайдер</h2>



<p class="wp-block-paragraph">Прежде чем разбирать код, полезно сразу разделить две вещи, которые в Laravel часто упоминают вместе.</p>



<p class="wp-block-paragraph"><strong>Контейнер</strong> &#8211; это объект, который умеет создавать другие объекты и подставлять им необходимые зависимости.</p>



<p class="wp-block-paragraph"><strong>Сервис-провайдер</strong> &#8211; это класс, который Laravel запускает при старте приложения, до обработки запроса. Именно в нём приложение объясняет контейнеру, <strong>как именно создавать некоторые объекты</strong>.</p>



<p class="wp-block-paragraph">Например, контейнер и без всякой настройки понимает, как создать:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>class OrderService
{
    public function __construct(
        private OrderRepository $orders,
    ) {}
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">OrderService</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">OrderRepository</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">orders</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Если <code>OrderRepository</code> можно создать через <code>new</code>  (то есть это не интерфейс и не абстрактный класс),  параметры его конструктора тоже объявлены типами классов, Laravel соберёт всю цепочку сам.</p>



<p class="wp-block-paragraph">Важная оговорка, на которой спотыкаются чаще всего: контейнер подставляет зависимости только тем объектам, которые создаёт он сам. Базовый класс при этом роли не играет &#8211; наследование от <code>Controller</code> или <code>Job</code> ни на что не влияет.  Класс <code>OrderService</code> из примера выше не наследует ничего.</p>



<p class="wp-block-paragraph">Работает это так. <br>Есть точки входа, которые Laravel создаёт через контейнер без вашего участия: контроллеры, посредники, консольные команды, слушатели событий, методы <code>handle()</code> у заданий очереди. Дальше цепочка продолжается сама: контроллер объявил в конструкторе параметер типа <code>OrderService</code> — значит, инстанс <code>OrderService</code> тоже создаёт контейнер, а раз так, подставится и его <code>OrderRepository</code>. <br>Глубина не ограничена и лежать эти классы могут где угодно, например, в созданной разработчиком папке <code>app/Services</code>.</p>



<p class="wp-block-paragraph">Рвётся цепочка ровно в одном случае &#8211; когда объект создаёте вы сами:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>// Контейнер не участвует: все аргументы нужно передать руками
$service = new OrderService(new OrderRepository());

// Просим контейнер — зависимости подставятся сами
$service = app(OrderService::class);
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// Контейнер не участвует: все аргументы нужно передать руками</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">service</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">OrderService</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">OrderRepository</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Просим контейнер — зависимости подставятся сами</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">service</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">app</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">OrderService</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<pre class="wp-block-verse">Отсюда и практическая рекоммендация: для классов с зависимостями не пишите <code>new</code>, а объявляйте тип в конструкторе там, куда объект в итоге попадёт.</pre>



<p class="wp-block-paragraph">Но если класс требует строку:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>class SmsSender
{
    public function __construct(
        private string $apiKey,
    ) {}
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SmsSender</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">apiKey</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">контейнер не знает, какую именно строку нужно передать. В этом случае мы должны зарегистрировать привязку в сервис-провайдере:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$this->app->bind(SmsSender::class, function () {
    return new SmsSender(config('services.sms.key'));
});
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">bind</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">SmsSender</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SmsSender</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">config</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">services.sms.key</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">То есть схема очень простая:</p>



<p class="wp-block-paragraph"><strong>сервис-провайдер регистрирует правила → контейнер использует эти правила → приложение получает готовые объекты.</strong></p>



<p class="wp-block-paragraph">Провайдер при этом не является самим контейнером и не создаёт зависимости вместо него. Его задача — <strong>настроить приложение и зарегистрировать в контейнере нужные правила</strong>.</p>



<p class="wp-block-paragraph">Теперь посмотрим, зачем вообще понадобился такой механизм.</p>



<h3 class="wp-block-heading">Четыре понятия, которые важно не смешивать</h3>



<p class="wp-block-paragraph">Их легко запутать, поэтому зафиксируем:</p>



<ul class="wp-block-list">
<li><strong>внедрение зависимостей</strong> &#8211; объект получает то, что ему нужно, извне;</li>



<li><strong>контейнер</strong>  умеет создавать и находить эти объекты;</li>



<li><strong>автоматическое разрешение зависимостей</strong> &#8211; контейнер создаёт объект, читая типы параметров его конструктора, и рекурсивно делает то же самое для каждой зависимости;</li>



<li><strong>сервис-провайдер</strong> &#8211; класс, запускаемый при старте приложения, в котором мы настраиваем контейнер и другие части приложения.</li>
</ul>



<p class="wp-block-paragraph">Именно поэтому контейнер не стоит воспринимать как «магическую замену <code>new</code>». Его задача — централизовать правила создания объектов там, где это действительно необходимо.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="block-2" class="wp-block-heading">2. Проблема, которой не видно на маленьких проектах</h2>



<p class="wp-block-paragraph">Представим задачу: нужно отправлять уведомления пользователям через какой-нибудь внешний сервис.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>class SmsSender
{
    public function send(string $phone, string $text): void
    {
        // обращение к внешнему шлюзу
    }
}

class OrderService
{
    public function confirm(Order $order): void
    {
        $sender = new SmsSender();
        $sender->send($order->user->phone, 'Заказ подтверждён');
    }
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SmsSender</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">send</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">phone</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">text</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// обращение к внешнему шлюзу</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">OrderService</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">confirm</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Order</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">order</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">sender</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SmsSender</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">sender</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">send</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">order</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">phone</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Заказ подтверждён</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Работает. Пока не начнёшь задавать вопросы.</p>



<p class="wp-block-paragraph"><strong>Вопрос первый.</strong> У <code>SmsSender</code> появился конструктор: ему нужен ключ доступа и адрес шлюза.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>class SmsSender
{
    public function __construct(
        private string $apiKey,
        private string $endpoint,
    ) {}
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SmsSender</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">apiKey</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">endpoint</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Теперь <code>OrderService</code> обязан знать про ключи и адреса. Это не его дело: он про заказы, а не про настройки внешнего шлюза. Но ему придётся, потому что он сам создаёт объект.</p>



<p class="wp-block-paragraph"><strong>Вопрос второй.</strong> Таких мест в коде не одно, а тридцать. Ключ поменялся — правим тридцать файлов.</p>



<p class="wp-block-paragraph"><strong>Вопрос третий.</strong> Пришёл бизнес и сказал: в Казахстане шлём не через SMS, а через мессенджер. Значит, нужен другой класс. И теперь по всему коду расползаются <code>if</code>.</p>



<p class="wp-block-paragraph"><strong>Вопрос четвёртый, самый болезненный.</strong> Как это протестировать? В тесте <code>OrderService::confirm()</code> реально уйдёт в интернет и потратит деньги. Подменить нечем: объект создаётся жёстко, внутри метода.</p>



<p class="wp-block-paragraph">Корень всех четырёх проблем один и тот же: <strong>класс сам создаёт то, чем пользуется</strong>. Он берёт на себя лишнюю ответственность и намертво связывает себя с конкретной реализацией.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="block-3" class="wp-block-heading">3. Первое лекарство: внедрение зависимостей</h2>



<p class="wp-block-paragraph">Лечится это до смешного просто. Пусть класс не создаёт зависимости, а <strong>принимает их снаружи</strong>, обычно через конструктор.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>class OrderService
{
    public function __construct(
        private SmsSender $sender,
    ) {}

    public function confirm(Order $order): void
    {
        $this->sender->send($order->user->phone, 'Заказ подтверждён');
    }
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">OrderService</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SmsSender</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">sender</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">confirm</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Order</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">order</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">sender</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">send</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">order</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">phone</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Заказ подтверждён</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Вот и всё. Это и называется <strong>внедрением зависимостей</strong> (dependency injection). Никакой философии: объект объявляет, что ему нужно для работы, а кто и как это соберёт &#8211; не его забота.</p>



<p class="wp-block-paragraph">Сделаем ещё шаг и заменим конкретный класс на договорённость (интерфейс):</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>interface NotificationSender
{
    public function send(string $phone, string $text): void;
}

class OrderService
{
    public function __construct(
        private NotificationSender $sender,
    ) {}
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">interface</span><span style="color: #D8DEE9FF"> NotificationSender</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">send</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">phone</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">text</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">OrderService</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">NotificationSender</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">sender</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Теперь <code>OrderService</code> вообще не знает, кто там за интерфейсом: SMS, мессенджер или заглушка в тесте. Он знает только, что у объекта есть метод <code>send()</code>. Связанность резко упала.</p>



<p class="wp-block-paragraph">Но появилась новая проблема.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="block-4" class="wp-block-heading">4. Проблема, которую породило лекарство</h2>



<p class="wp-block-paragraph">Раньше мы писали <code>new OrderService()</code>. Теперь так нельзя:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$service = new OrderService(
    new SmsSender(
        config('services.sms.key'),
        config('services.sms.endpoint'),
    )
);
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">service</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">OrderService</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SmsSender</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">config</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">services.sms.key</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">config</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">services.sms.endpoint</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">А если у <code>SmsSender</code> есть свои зависимости, а у них &#8211; свои, то сборка одного объекта превращается в трёхэтажную конструкцию. И такую конструкцию придётся повторять в каждом контроллере, команде, обработчике очереди.</p>



<p class="wp-block-paragraph">Мы просто перенесли боль из одного места в другое.</p>



<p class="wp-block-paragraph">Значит, нужен кто-то, кто возьмёт на себя всю работу по сборке объектов. Один на всё приложение. Кто-то, кому можно сказать: &#8220;дай мне <code>OrderService</code>&#8220;, а он сам разберётся, что для этого нужно создать и в каком порядке.</p>



<p class="wp-block-paragraph">Этот &#8220;кто-то&#8221; и есть <strong>контейнер служб</strong> (service container).</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="block-5" class="wp-block-heading">5. Что такое контейнер</h2>



<pre class="wp-block-verse has-base-background-color has-background">Контейнер — это объект, который умеет создавать другие объекты и разрешать их зависимости.</pre>



<p class="wp-block-paragraph">По сути это механизм сборки объектов плюс хранилище уже созданных экземпляров. Дальше начинаются детали: как контейнер понимает, что создавать, как регистрируются правила и сколько живёт созданный объект.</p>



<p class="wp-block-paragraph">В Laravel контейнер является объектом приложения (<code>класс Illuminate\Foundation\Application</code>, который наследуется от <code>Illuminate\Container\Container</code>). Получить его можно с помощью глобальной функции <code><a href="https://laravel.com/docs/13.x/helpers#method-app" data-type="link" data-id="https://laravel.com/docs/13.x/helpers#method-app">app()</a></code>.</p>



<p class="wp-block-paragraph">Самое главное: <strong>вы почти никогда не обращаетесь к контейнеру напрямую</strong>. Laravel сам создаёт через него контроллеры, консольные команды, слушатели событий, посредники. Именно поэтому достаточно написать тип в конструкторе контроллера — и объект окажется там сам.</p>



<p class="wp-block-paragraph">Отдельно стоит сказать про задания очередей (Job): их вы создаёте сами (<code>new SendSms($order)</code> и <code>dispatch()</code>), а между постановкой в очередь и выполнением объект сериализуется и десериализуется. Конструктор задания через контейнер не проходит. Зато метод <code>handle()</code> вызывается через контейнер, поэтому зависимости удобно объявлять именно в нём. Но об этом ниже, в разделе про внедрение в методы.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="block-6" class="wp-block-heading">6. Автоматическое разрешение: где именно происходит &#8220;магия&#8221;</h2>



<p class="wp-block-paragraph">Пишем:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>class OrderController extends Controller
{
    public function __construct(
        private OrderService $orders,
    ) {}
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">OrderController</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Controller</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">OrderService</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">orders</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Мы нигде не сказали контейнеру, что такое <code>OrderService</code>. Откуда он взялся?</p>



<p class="wp-block-paragraph">Пошагово контейнер делает следующее:</p>



<ol class="wp-block-list">
<li>Смотрит на строку <code>App\Services\OrderService</code>. Проверяет, нет ли уже созданного ранее объекта . Нет.</li>



<li>Раз объекта нет, но класс существует — попробуем создать его сами.</li>



<li>Через рефлексию (<code>ReflectionClass</code>) заглядывает внутрь класса и достаёт конструктор.</li>



<li>Перебирает параметры конструктора и смотрит на их типы.</li>



<li>Тип <code>NotificationSender</code> — не скалярный, значит это чья-то зависимость. Рекурсивно повторяем весь алгоритм уже для неё.</li>



<li>Когда все параметры собраны, вызывает <code>new OrderService(...$параметры)</code>.</li>
</ol>



<p class="wp-block-paragraph">Ключевое слово здесь — <strong><a href="https://www.php.net/manual/ru/book.reflection.php">рефлексия</a></strong>: встроенная в PHP возможность программно изучать структуру классов во время выполнения. Контейнер буквально читает сигнатуру вашего конструктора и по типам аргументов понимает, что нужно подставить.</p>



<p class="wp-block-paragraph">Этот механизм называют автоматическим связыванием (autowiring). Он требует выполнения хотя бы одного из условий для каждого параметра:</p>



<ul class="wp-block-list">
<li>параметр объявлен типом класса, и этот тип разрешим:  либо это конкретный создаваемый класс, либо интерфейс, для которого явно указана реализация;</li>



<li>у параметра есть значение по умолчанию. Тогда контейнер просто возьмёт его и не будет ничего собирать. Это касается и скаляров (<code>string $prefix = 'sms'</code>), и классов (<code>?LoggerInterface $logger = null</code>).</li>
</ul>



<p class="wp-block-paragraph">Как только появляется обязательный параметр, который не подпадает ни под один пункт, автоматика останавливается и важно знать, что сообщения об ошибке будут разными.</p>



<p class="wp-block-paragraph">Для интерфейса или абстрактного класса без привязки:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>Target &#91;App\Contracts\NotificationSender&#93; is not instantiable
  while building &#91;App\Http\Controllers\OrderController&#93;.
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF">Target </span><span style="color: #ECEFF4">&#91;</span><span style="color: #D8DEE9FF">App\Contracts\NotificationSender</span><span style="color: #ECEFF4">&#93;</span><span style="color: #D8DEE9FF"> is not instantiable</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">while</span><span style="color: #D8DEE9FF"> building </span><span style="color: #ECEFF4">&#91;</span><span style="color: #D8DEE9FF">App\Http\Controllers\OrderController</span><span style="color: #ECEFF4">&#93;</span><span style="color: #81A1C1">.</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Для обязательного скаляра:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>Unresolvable dependency resolving [Parameter #0 &#91; &lt;required> string $apiKey &#93;]
  in class App\Services\SmsSender
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF">Unresolvable dependency resolving </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF">Parameter </span><span style="color: #616E88">#0 &#91; &lt;required&gt; string $apiKey &#93;]</span></span>
<span class="line"><span style="color: #D8DEE9FF">  in </span><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">App</span><span style="color: #D8DEE9FF">\Services\SmsSender</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Оба исключения — это <code>Illuminate\Contracts\Container\BindingResolutionException</code>. Разница в тексте мелочь, но именно по нему вы будете искать причину, поэтому полезно различать: первое означает «не знаю, какую реализацию взять», второе — «не знаю, какое значение подставить». И это правильное поведение: угадывать за вас контейнер не должен.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="block-7" class="wp-block-heading">7. Привязки: объясняем контейнеру то, чего он не угадает</h2>



<p class="wp-block-paragraph">Инструкции контейнеру мы указываем в <strong>сервис-провайдере</strong> (service provider). Обычно это класс <code>App\Providers\AppServiceProvider</code>, метод <code>register()</code>.</p>



<h3 class="wp-block-heading">7.1. bind(). Рецепт создания экземпляра класса.</h3>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>public function register(): void
{
    $this->app->bind(SmsSender::class, function (Application $app) {
        return new SmsSender(
            config('services.sms.key'),
            config('services.sms.endpoint'),
        );
    });
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">register</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">bind</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">SmsSender</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Application</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">app</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SmsSender</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">config</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">services.sms.key</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">config</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">services.sms.endpoint</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Читается так: <strong>&#8220;когда у тебя попросят <code>SmsSender</code>, не пытайся собрать его сам, а выполни вот эту функцию&#8221;.</strong></p>



<p class="wp-block-paragraph">Замыкание получает два аргумента: сам контейнер и массив параметров, переданных при разрешении (о них в разделе про makeWith). Полная сигнатура выглядит так:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$this->app->bind(SmsSender::class, function (Application $app, array $parameters = []) {
    // внутри доступен $app->make(...) для остальных зависимостей
});
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">bind</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">SmsSender</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Application</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">app</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">parameters</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[])</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// внутри доступен $app-&gt;make(...) для остальных зависимостей</span></span>
<span class="line"><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Второй аргумент нужен редко, поэтому обычно пишут только первый.</p>



<p class="wp-block-paragraph">Важная деталь: bind создаёт <strong>новый объект при каждом разрешении</strong>. Речь именно про каждый вызов make()/app(), а не про HTTP-запрос: попросили пять раз внутри одного запроса — получили пять разных объектов.</p>



<h3 class="wp-block-heading">7.2. Привязка интерфейса к реализации.</h3>



<p class="wp-block-paragraph">Ради этого контейнеры и придумывали:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$this->app->bind(NotificationSender::class, SmsSender::class);
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">bind</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">NotificationSender</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SmsSender</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph"><strong>&#8220;Когда кто-то просит интерфейс — давай ему вот этот класс&#8221;. </strong><br>Теперь автоматическое связывание снова работает: контейнер, увидев <code>NotificationSender</code> в конструкторе, знает, чем его заменить.</p>



<p class="wp-block-paragraph">Хотите переключиться на мессенджер? Меняете одну строку в сервис-провайдере, а код приложения не трогаете вообще. </p>



<p class="wp-block-paragraph">Можно и по условию:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$this->app->bind(NotificationSender::class, function (Application $app) {
    return config('services.default_channel') === 'telegram'
        ? $app->make(TelegramSender::class)
        : $app->make(SmsSender::class);
});
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">bind</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">NotificationSender</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Application</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">app</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">config</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">services.default_channel</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">===</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">telegram</span><span style="color: #ECEFF4">&#39;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">TelegramSender</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">SmsSender</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<h3 class="wp-block-heading">7.3. singleton(). Один экземпляр на жизненный цикл контейнера.</h3>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$this->app->singleton(CurrencyRates::class, function (Application $app) {
    return new CurrencyRates($app->make(HttpClient::class));
});
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">singleton</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">CurrencyRates</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Application</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">app</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">CurrencyRates</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">HttpClient</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Первый вызов создаёт объект, последующие обращения к контейнеру возвращают тот же экземпляр. В обычном PHP-приложении с коротким жизненным циклом процесса это обычно означает один экземпляр в рамках текущего запроса. В долгоживущих процессах, например при использовании Laravel Octane или обработчиков очередей, такой объект может жить намного дольше. Поэтому состояние внутри singleton нужно хранить очень осторожно.</p>



<h3 class="wp-block-heading">7.4. instance(). Использовать готовый объект.</h3>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$client = new HttpClient(&#91;'timeout' => 5&#93;);
$this->app->instance(HttpClient::class, $client);
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">client</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">HttpClient</span><span style="color: #ECEFF4">(&#91;</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">timeout</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #ECEFF4">&#93;)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">instance</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">HttpClient</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Здесь мы не даём рецепт, а сразу передаем готовый объект.</p>



<h3 class="wp-block-heading">7.5. scoped(). Один экземпляр на жизненный цикл запроса.</h3>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$this->app->scoped(RequestContext::class, function () {
    return new RequestContext();
});
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">scoped</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">RequestContext</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">RequestContext</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Ведёт себя как singleton, но экземпляр привязан к текущему жизненному циклу. В долгоживущих процессах Laravel очищает такие экземпляры между отдельными запросами или заданиями. Это особенно важно для Laravel Octane и очередей: состояние одного запроса или задания не должно переходить к следующему.</p>



<h3 class="wp-block-heading">7.6. singletonIf() и bindIf()</h3>



<p class="wp-block-paragraph">Регистрируют привязку, только если такой ещё нет. Полезно в пакетах: не перетираем то, что пользователь настроил у себя.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="block-8" class="wp-block-heading">8. Контекстные привязки: одному одно, другому другое.</h2>



<p class="wp-block-paragraph">Бывает, что один и тот же интерфейс должен разрешаться по-разному в зависимости от того, кто просит. Классический пример — хранилище файлов:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$this->app->when(PhotoController::class)
    ->needs(Filesystem::class)
    ->give(function () {
        return Storage::disk('public');
    });

$this->app->when(DocumentController::class)
    ->needs(Filesystem::class)
    ->give(function () {
        return Storage::disk('private');
    });
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">when</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">PhotoController</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">needs</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Filesystem</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">give</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Storage</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">disk</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">public</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">when</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">DocumentController</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">needs</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Filesystem</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">give</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Storage</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">disk</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">private</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Тем же способом можно подставлять скалярные значения, которые нельзя разрешить автоматически:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$this->app->when(SmsSender::class)
    ->needs('$apiKey')
    ->give(fn () => config('services.sms.key'));
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">when</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">SmsSender</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">needs</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">$apiKey</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">give</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">config</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">services.sms.key</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Обратите внимание на знак доллара: здесь мы указываем не тип, а конкретное имя параметра конструктора.</p>



<p class="wp-block-paragraph">Если правило одинаковое для нескольких классов, <code>when()</code> принимает массив:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$this->app->when(&#91;VideoController::class, UploadController::class&#93;)
    ->needs(Filesystem::class)
    ->give(fn () => Storage::disk('s3'));
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">when</span><span style="color: #ECEFF4">(&#91;</span><span style="color: #8FBCBB">VideoController</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">UploadController</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">&#93;)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">needs</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Filesystem</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">give</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Storage</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">disk</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">s3</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">А для самого частого случая — подстановки значения из конфига — есть сокращение:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$this->app->when(SmsSender::class)
    ->needs('$apiKey')
    ->giveConfig('services.sms.key');
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">when</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">SmsSender</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">needs</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">$apiKey</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">giveConfig</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">services.sms.key</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Аналогично <code>giveTagged('reports')</code> отдаёт группу объектов, помеченных меткой (о метках — в разделе 11).</p>



<h3 class="wp-block-heading">8.1. Контекстные атрибуты: то же самое, но без провайдера</h3>



<p class="wp-block-paragraph">Начиная с Laravel 11 у контекстных привязок появилась альтернатива, которая обычно читается лучше. Вместо того чтобы описывать правило в провайдере, вы помечаете сам параметр конструктора атрибутом:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>use Illuminate\Container\Attributes\Config;

class SmsSender
{
    public function __construct(
        #&#91;Config('services.sms.key')&#93; private string $apiKey,
        #&#91;Config('services.sms.endpoint')&#93; private string $endpoint,
    ) {}
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Container</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Attributes</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Config</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SmsSender</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        #&#91;Config</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">services.sms.key</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF">&#93; </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">apiKey</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        #&#91;Config</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">services.sms.endpoint</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF">&#93; </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">endpoint</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Всё, привязка больше не нужна: контейнер увидит атрибут и подставит значение сам. Тот же приём для дисков:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>use Illuminate\Container\Attributes\Storage;
use Illuminate\Contracts\Filesystem\Filesystem;

class PhotoController extends Controller
{
    public function __construct(
        #&#91;Storage('public')&#93; private Filesystem $disk,
    ) {}
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Container</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Attributes</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Storage</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Contracts</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Filesystem</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Filesystem</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">PhotoController</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Controller</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        #&#91;Storage</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">public</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF">&#93; </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Filesystem</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">disk</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Кроме <code>Config</code> и <code>Storage</code> в коробке есть <code>Auth</code>, <code>Cache</code>, <code>Context</code>, <code>DB</code>, <code>Give</code>, <code>Log</code>, <code>RequestAttribute</code>, <code>RouteParameter</code> и <code>Tag</code>. Отдельно стоит <code>CurrentUser</code> — он внедряет текущего аутентифицированного пользователя, в том числе прямо в замыкание маршрута:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>use App\Models\User;
use Illuminate\Container\Attributes\CurrentUser;

Route::get('/user', function (#&#91;CurrentUser&#93; User $user) {
    return $user;
})->middleware('auth');
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Models</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">User</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Container</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Attributes</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">CurrentUser</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">Route</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">/user</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">#&#91;CurrentUser&#93; </span><span style="color: #8FBCBB">User</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">middleware</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">auth</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Свой атрибут делается реализацией контракта <code>Illuminate\Contracts\Container\ContextualAttribute</code>: контейнер вызовет метод <code>resolve()</code> и подставит то, что тот вернёт.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>use Attribute;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Container\ContextualAttribute;

#&#91;Attribute(Attribute::TARGET_PARAMETER)&#93;
class CurrentTenant implements ContextualAttribute
{
    public static function resolve(self $attribute, Container $container): Tenant
    {
        return $container->make(TenantResolver::class)->current();
    }
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Attribute</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Contracts</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Container</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Container</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Contracts</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Container</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">ContextualAttribute</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">#&#91;Attribute</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Attribute</span><span style="color: #81A1C1">::</span><span style="color: #D8DEE9FF">TARGET_PARAMETER</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF">&#93;</span></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">CurrentTenant</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">implements</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">ContextualAttribute</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">static</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">resolve</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">attribute</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Container</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">container</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Tenant</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">container</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">TenantResolver</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">current</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Что выбрать. Атрибут удобнее, когда правило принадлежит самому классу и не меняется от окружения: ключ из конфига, конкретный диск, текущий пользователь. Привязка в провайдере лучше, когда правило именно внешнее — например, вы подменяете реализацию в зависимости от конфигурации или подсовываете свои значения в класс из чужого пакета, куда атрибут не поставить. Ни один способ не отменяет другой.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="block-9" class="wp-block-heading">9. Как достать объект из контейнера</h2>



<p class="wp-block-paragraph">Способов несколько, и они не равнозначны по стилю.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>// Предпочтительный способ: просто объявите тип, Laravel подставит сам
public function __construct(private OrderService $orders) {}

// Явное создание
$orders = app(OrderService::class);
$orders = App::make(OrderService::class);
$orders = resolve(OrderService::class);

// С передачей части аргументов вручную
$sender = app()->makeWith(SmsSender::class, &#91;'apiKey' => 'test-key'&#93;);
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// Предпочтительный способ: просто объявите тип, Laravel подставит сам</span></span>
<span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">OrderService</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">orders</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Явное создание</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">orders</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">app</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">OrderService</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">orders</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">App</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">OrderService</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">orders</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">resolve</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">OrderService</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// С передачей части аргументов вручную</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">sender</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">app</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">makeWith</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">SmsSender</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#91;</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">apiKey</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">test-key</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">&#93;)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph"><code>app()</code> и <code>resolve()</code> — вспомогательные функции для получения объекта из контейнера. Непосредственно у самого контейнера для этого используется метод <code>make()</code>.</p>



<p class="wp-block-paragraph">Про <code>makeWith</code> важно знать две вещи. Во-первых, переданные значения доходят только до сборки класса или до замыкания-рецепта (тот самый второй аргумент из раздела 6.1) и сопоставляются по имени параметра. Во-вторых, результат такого вызова <strong>не кэшируется</strong>, даже если привязка зарегистрирована как <code>singleton</code>: контейнер считает разрешение с параметрами частным случаем и общий экземпляр не сохраняет и не подменяет. Это ровно то поведение, которое нужно, но оно удивляет, если ждать обратного.</p>



<p class="wp-block-paragraph">Контейнер также реализует стандарт PSR-11 (<code>Psr\Container\ContainerInterface</code>), то есть у него есть методы <code>get()</code> и <code>has()</code>. Пригодится, если библиотека умеет работать с любым PSR-совместимым контейнером. Разница с <code>make()</code> в поведении при ошибке: <code>get()</code> бросает исключения из стандарта PSR, а не <code>BindingResolutionException</code>.</p>



<p class="wp-block-paragraph">Контейнер умеет разрешать зависимости не только в конструкторах, но и <strong>в методах</strong>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>public function store(Request $request, OrderService $orders, string $id)
{
    // $request и $orders придут из контейнера,
    // $id — из параметров маршрута
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">store</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Request</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">OrderService</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">orders</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// $request и $orders придут из контейнера,</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// $id — из параметров маршрута</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Laravel умеет внедрять зависимости в методы, которые фреймворк вызывает через контейнер. Например, зависимости передаются в методы контроллеров, а также в методы вроде <code>handle()</code> у заданий очереди и слушателей событий — то есть там, где объект запускает сам фреймворк. Параметры маршрута при этом сопоставляются по имени, а зависимости — по типу, поэтому порядок аргументов в сигнатуре роли не играет.</p>



<p class="wp-block-paragraph">Тот же механизм доступен вручную через <code>call()</code>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>app()->call(&#91;$report, 'generate'&#93;);
app()->call(function (OrderService $orders) {
    // ...
});
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">app</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">call</span><span style="color: #ECEFF4">(&#91;</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">report</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">generate</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">&#93;)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #88C0D0">app</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">call</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">OrderService</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">orders</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// ...</span></span>
<span class="line"><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="block-10" class="wp-block-heading">10. Где живут настройки: сервис-провайдеры</h2>



<p class="wp-block-paragraph">Сервис-провайдер — это класс, через который Laravel настраивает часть приложения. В нём чаще всего встречаются два метода: <code>register()</code> и <code>boot()</code>. Они нужны для разных задач.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Только привязки. Больше ничего.
        $this->app->bind(NotificationSender::class, SmsSender::class);
    }

    public function boot(): void
    {
        // Здесь уже можно пользоваться другими службами
        View::share('appVersion', config('app.version'));
    }
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">AppServiceProvider</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">ServiceProvider</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">register</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// Только привязки. Больше ничего.</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">bind</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">NotificationSender</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SmsSender</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">boot</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// Здесь уже можно пользоваться другими службами</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">View</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">share</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">appVersion</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">config</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">app.version</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph"><strong>Правило, которое стоит запомнить:</strong> в <code>register()</code> мы только записываем рецепты, но не готовим по ним. Причина простая: Laravel сначала вызывает <code>register()</code> у всех сервис-провайдеров и только потом <code>boot()</code> у всех. Если в <code>register()</code> вы попробуете достать из контейнера что-то, что регистрируется сервис-провайдером ниже по списку, вы получите ошибку или, что хуже, объект, собранный не до конца.</p>



<p class="wp-block-paragraph">В <code>boot()</code> основные привязки уже зарегистрированы, поэтому там удобно разрешать зависимости, регистрировать обработчики событий, настраивать представления и выполнять другую инициализацию.</p>



<h3 class="wp-block-heading">10.1 Откуда берутся провайдеры</h3>



<p class="wp-block-paragraph">После этого раздела легко решить, что провайдер — это что-то, что нужно немедленно завести. Обычно нет. Провайдеры в приложении появляются из трёх источников, и только один из них ваш.</p>



<p class="wp-block-paragraph"><strong>Провайдеры самого фреймворка.</strong> Маршрутизация, кэш, очереди, сессии — всё это регистрируется провайдерами Laravel. Они загружаются автоматически, и трогать их не нужно.</p>



<p class="wp-block-paragraph"><strong>Провайдеры пакетов.</strong> Начиная с Laravel 5.5 работает автообнаружение: пакет объявляет свой провайдер в <code>composer.json</code> (секция <code>extra.laravel.providers</code>), и после <code>composer require</code> он подключается сам. Прописывать его куда-либо вручную не требуется.</p>



<p class="wp-block-paragraph"><strong>Ваши собственные.</strong> В свежем проекте уже есть один — <code>App\Providers\AppServiceProvider</code>. Для подавляющего большинства приложений его достаточно: все привязки живут в его <code>register()</code>, вся начальная настройка — в <code>boot()</code>. Отдельный провайдер имеет смысл заводить, когда вокруг одной подсистемы набирается заметный кусок настройки (платежи, интеграция с внешним API) и его хочется вынести, либо когда провайдер нужен отложенным, либо когда вы пишете пакет.</p>



<p class="wp-block-paragraph">Если такой момент настал, файл не создают руками:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>php artisan make:provider PaymentServiceProvider
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF">php artisan make</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF">provider PaymentServiceProvider</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">В Laravel 11 и новее команда сама допишет класс в <code>bootstrap/providers.php</code>. В более старых версиях его нужно добавить в массив <code>providers</code> файла <code>config/app.php</code>.</p>



<h3 class="wp-block-heading">10.2 Отложенные сервис-провайдеры</h3>



<p class="wp-block-paragraph">Если сервис-провайдер нужен редко, его можно не грузить на каждый запрос:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>lass SmsServiceProvider extends ServiceProvider implements DeferrableProvider
{
    public function register(): void
    {
        $this->app->singleton(SmsSender::class, fn () => new SmsSender(/* ... */));
    }

    public function provides(): array
    {
        return &#91;SmsSender::class&#93;;
    }
}
```</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF">lass SmsServiceProvider extends ServiceProvider implements DeferrableProvider</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">register</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">singleton</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">SmsSender</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SmsSender</span><span style="color: #ECEFF4">(</span><span style="color: #616E88">/* ... */</span><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">provides</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#91;</span><span style="color: #8FBCBB">SmsSender</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">&#93;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">```</span></span></code></pre></div>



<p class="wp-block-paragraph">Laravel запомнит, что этот провайдер предоставляет <code>SmsSender</code>, и сможет загрузить провайдер только тогда, когда такая служба действительно понадобится. Соответствие «служба → провайдер» кэшируется в <code>bootstrap/cache/services.php</code>, поэтому после изменения <code>provides()</code> кэш стоит сбросить (<code>php artisan clear-compiled</code>).</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="block-11" class="wp-block-heading">11. Фасады и вспомогательные функции — это тоже контейнер</h2>



<p class="wp-block-paragraph">Многих смущает конструкция вида <code>Cache::get('key')</code>. Выглядит как статический вызов, но <code>get()</code> там точно не статический метод.</p>



<p class="wp-block-paragraph">Внутри всё устроено предельно просто. Фасад — это класс, у которого определён магический метод <code>__callStatic()</code>. Он достаёт из контейнера настоящий объект по строковому ключу и перенаправляет вызов ему:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>class Cache extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return 'cache';
    }
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Cache</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Facade</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">static</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getFacadeAccessor</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">cache</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">То есть <code>Cache::get('key')</code> по смыслу можно представить примерно как обращение к объекту, который контейнер хранит под ключом <code>cache</code>: <code>app('cache')-&gt;get('key')</code>. Фасад скрывает получение этого объекта за статическим синтаксисом.</p>



<p class="wp-block-paragraph">Отсюда же растут ноги у удобного тестирования: <code>Cache::shouldReceive(...)</code> просто подменяет объект в контейнере на заглушку, а весь остальной код продолжает работать, ничего не заметив.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="block-12" class="wp-block-heading">12. Приёмы, до которых доходят не сразу</h2>



<h3 class="wp-block-heading">12.1 Метки (tags)</h3>



<p class="wp-block-paragraph">Когда нужно получить целую группу объектов:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$this->app->bind(PdfReport::class, fn () => new PdfReport());
$this->app->bind(ExcelReport::class, fn () => new ExcelReport());

$this->app->tag(&#91;PdfReport::class, ExcelReport::class&#93;, 'reports');

// Позже
foreach ($this->app->tagged('reports') as $report) {
    $report->generate();
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">bind</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">PdfReport</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">PdfReport</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">bind</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">ExcelReport</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ExcelReport</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">tag</span><span style="color: #ECEFF4">(&#91;</span><span style="color: #8FBCBB">PdfReport</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ExcelReport</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">&#93;,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">reports</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Позже</span></span>
<span class="line"><span style="color: #81A1C1">foreach</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">tagged</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">reports</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">as</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">report</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">report</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">generate</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Отличный способ собрать все обработчики, все правила проверки или все каналы уведомлений без ручного перечисления.</p>



<p class="wp-block-paragraph">Одна ловушка: <code>tagged()</code> возвращает не массив, а <code>Illuminate\Container\RewindableGenerator</code>. В <code>foreach</code> он ведёт себя как обычная коллекция и объекты создаются лениво, но обратиться по индексу или прогнать через <code>array_map()</code> не получится. Если нужен именно массив — <code>iterator_to_array($this-&gt;app-&gt;tagged('reports'))</code>.</p>



<h3 class="wp-block-heading">12.2 Расширение уже созданного (extend)</h3>



<p class="wp-block-paragraph">Позволяет вклиниться и обернуть объект, не переписывая исходную привязку:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$this->app->extend(NotificationSender::class, function ($sender, $app) {
    return new LoggingSenderDecorator($sender, $app->make(LoggerInterface::class));
});
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">extend</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">NotificationSender</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">sender</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">app</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">LoggingSenderDecorator</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">sender</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">LoggerInterface</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Особенно ценно, когда нужно доработать поведение стороннего пакета.</p>



<h3 class="wp-block-heading">12.3 События контейнера</h3>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$this->app->resolving(SmsSender::class, function ($sender, $app) {
    // выполнится при фактической сборке SmsSender
});

$this->app->afterResolving(...);
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">resolving</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">SmsSender</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">sender</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">app</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// выполнится при фактической сборке SmsSender</span></span>
<span class="line"><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">afterResolving</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">...</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Вещь довольно узкая, но иногда спасает: например, для донастройки объектов из чужих пакетов.</p>



<p class="wp-block-paragraph">Тонкость, которая обычно всплывает уже на отладке: коллбэк срабатывает при <strong>сборке</strong> объекта, а не при каждом обращении к контейнеру. Если служба зарегистрирована через <code>singleton</code> или <code>instance</code>, то со второго раза <code>make()</code> вернёт готовый экземпляр из кэша, не дойдя до коллбэков. Для <code>bind</code> объект собирается каждый раз, поэтому там коллбэк отработает при каждом разрешении.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="block-13" class="wp-block-heading">13. Тестирование: ради этого тоже всё затевалось</h2>



<p class="wp-block-paragraph">Вернёмся к самой первой проблеме. Как теперь протестировать <code>OrderService</code>, не тратя деньги на реальные SMS?</p>



<p class="wp-block-paragraph">Способ первый, без всякого Laravel:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$fakeSender = new class implements NotificationSender {
    public array $sent = [];

    public function send(string $phone, string $text): void
    {
        $this->sent[] = &#91;$phone, $text&#93;;
    }
};

$service = new OrderService($fakeSender);
$service->confirm($order);

$this->assertCount(1, $fakeSender->sent);
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">fakeSender</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">implements</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">NotificationSender</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">sent</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">send</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">phone</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">text</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">sent</span><span style="color: #ECEFF4">[]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#91;</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">phone</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">text</span><span style="color: #ECEFF4">&#93;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">service</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">OrderService</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">fakeSender</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">service</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">confirm</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">order</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">assertCount</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">fakeSender</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">sent</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Обратите внимание: контейнер здесь вообще не нужен. Если класс принимает зависимости через конструктор, его всегда можно собрать руками. Это признак хорошего дизайна.</p>



<p class="wp-block-paragraph">Способ второй, через подмену в контейнере:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$this->mock(NotificationSender::class, function ($mock) {
    $mock->shouldReceive('send')->once();
});

$this->post('/orders/1/confirm');
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">mock</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">NotificationSender</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">mock</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">mock</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">shouldReceive</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">send</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">once</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">post</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">/orders/1/confirm</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Теперь весь код приложения, который просит <code>NotificationSender</code>, получит заглушку. Ни строчки в самом приложении менять не пришлось.</p>



<p class="wp-block-paragraph">Есть ещё <code>$this-&gt;swap(Интерфейс::class, $объект)</code> и <code>$this-&gt;instance(...)</code> — те же идеи в разных обёртках.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="block-14" class="wp-block-heading">14. Грабли, на которые наступают все</h2>



<p class="wp-block-paragraph"><strong>Синглтон с состоянием.</strong> Если в объекте, зарегистрированном как <code>singleton</code>, вы накапливаете данные, при обычной работе это относительно безопасно (процесс живёт один запрос). Но под Octane или в долгоживущем обработчике очереди процесс обслуживает тысячи запросов подряд, и данные одного пользователя утекут к другому. Для состояния, привязанного к запросу, используйте <code>scoped</code>, а не <code>singleton</code>.</p>



<p class="wp-block-paragraph"><strong>Внедрение запроса в синглтон.</strong> Классическая ловушка: объект-синглтон принимает <code>Illuminate\Http\Request</code> в конструкторе. Экземпляр создаётся один раз, вместе с ним один раз разрешается и запрос — дальше объект будет вечно держать самый первый. В обычном FPM это всплывает редко, а под Octane превращается в чужие данные в ответе.</p>



<p class="wp-block-paragraph">Лечится одним из трёх способов, по возрастанию сложности:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>// 1. Не хранить запрос вообще: получать его в момент вызова
public function handle(): void
{
    $ip = request()->ip();
}

// 2. Принимать запрос параметром метода, а не конструктора
public function handle(Request $request): void
{
    $ip = $request->ip();
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// 1. Не хранить запрос вообще: получать его в момент вызова</span></span>
<span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handle</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">ip</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">request</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">ip</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 2. Принимать запрос параметром метода, а не конструктора</span></span>
<span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handle</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Request</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">ip</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">ip</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Третий способ нужен, когда объект обязан хранить запрос у себя — например, это адаптер к стороннему SDK. Тогда попросите контейнер обновлять ссылку при каждой пересборке <code>request</code>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$this->app->singleton(TrackingClient::class, function ($app) {
    $client = new TrackingClient($app&#91;'request'&#93;);

    // при каждой пересборке 'request' объекту подсунут актуальный запрос
    $app->refresh('request', $client, 'setRequest');

    return $client;
});
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">singleton</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">TrackingClient</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">app</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">client</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">TrackingClient</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">app</span><span style="color: #ECEFF4">&#91;</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">request</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">&#93;)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// при каждой пересборке &#39;request&#39; объекту подсунут актуальный запрос</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">refresh</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">request</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">setRequest</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">client</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph"><code>refresh()</code> — это обёртка над <code>rebinding()</code>: как только в контейнере переопределяется <code>request</code>, у объекта вызовется <code>setRequest()</code> с новым экземпляром. Ровно так внутри устроены сами компоненты Laravel, которые вынуждены держать запрос.</p>



<p class="wp-block-paragraph">То же рассуждение касается всего, что живёт ровно один запрос: аутентифицированного пользователя, текущей локали, идентификатора трассировки.</p>



<p class="wp-block-paragraph"><strong>Контейнер как справочная служба.</strong> Когда <code>app(SomeService::class)</code> разбросан по всему коду, вы формально используете контейнер, но реально вернулись к жёсткой связанности: класс снова сам добывает зависимости, просто через посредника. Зависимости при этом не видны в сигнатуре, а тестировать становится тяжело. Правило: <strong>объявляйте зависимости в конструкторе, к контейнеру обращайтесь напрямую только там, куда внедрение не дотягивается</strong> (фабрики, ленивое создание тяжёлых объектов, устаревший код).</p>



<p class="wp-block-paragraph"><strong>Интерфейс ради интерфейса.</strong> Если у абстракции всегда будет ровно одна реализация и подменять её вы не собираетесь даже в тестах, интерфейс добавляет файл и не добавляет пользы. Абстракции стоят денег, они должны окупаться.</p>



<p class="wp-block-paragraph"><strong>Циклическая зависимость.</strong> <code>A</code> требует <code>B</code>, <code>B</code> требует <code>A</code> — контейнер уйдёт в бесконечную рекурсию. Отдельной проверки на цикл в нём нет, поэтому осмысленного исключения вы не увидите: процесс упрётся в лимит памяти, в максимальную вложенность вызовов (если стоит Xdebug) или просто рухнет. Полезный приём при отладке — посмотреть на стек вызовов: повторяющийся фрагмент из <code>Container::build()</code> сразу покажет пару виновников. Это не баг контейнера, а сигнал, что ответственность между классами распределена неудачно.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="block-15" class="wp-block-heading">15. Пишем свой контейнер</h2>



<p class="wp-block-paragraph">Лучший способ понять механизм — реализовать его. Ниже рабочий мини-контейнер: он умеет привязки, синглтоны и автоматическое разрешение через рефлексию. Это, конечно, сильно упрощённая модель. Она показывает основную идею автоматического разрешения зависимостей, но реальный контейнер Laravel умеет гораздо больше.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>&lt;?php

use Closure;
use ReflectionClass;
use RuntimeException;

class MiniContainer
{
    /** Рецепты создания: 'Интерфейс' => &#91;'concrete' => ..., 'shared' => bool&#93; */
    protected array $bindings = [];

    /** Уже созданные общие экземпляры */
    protected array $instances = [];

    /** Что сейчас собираем — нужно, чтобы поймать циклическую зависимость */
    protected array $buildStack = [];

    public function bind(string $abstract, Closure|string|null $concrete = null, bool $shared = false): void
    {
        $this->bindings&#91;$abstract&#93; = &#91;
            'concrete' => $concrete ?? $abstract,
            'shared'   => $shared,
        &#93;;
    }

    public function singleton(string $abstract, Closure|string|null $concrete = null): void
    {
        $this->bind($abstract, $concrete, true);
    }

    public function instance(string $abstract, object $object): void
    {
        $this->instances&#91;$abstract&#93; = $object;
    }

    /** Главный метод: получить объект по имени */
    public function make(string $abstract): mixed
    {
        // 1. Уже создавали и договаривались хранить? Отдаём то же самое.
        if (isset($this->instances&#91;$abstract&#93;)) {
            return $this->instances&#91;$abstract&#93;;
        }

        // 2. Защита от циклов: A требует B, B требует A.
        if (isset($this->buildStack&#91;$abstract&#93;)) {
            throw new RuntimeException(
                'Циклическая зависимость: ' . implode(' -> ', array_keys($this->buildStack)) . " -> {$abstract}."
            );
        }

        $this->buildStack&#91;$abstract&#93; = true;

        try {
            $binding  = $this->bindings&#91;$abstract&#93; ?? null;
            $concrete = $binding&#91;'concrete'&#93; ?? $abstract;

            if ($concrete instanceof Closure) {
                // 3. Рецепт-функция: просто выполняем её.
                $object = $concrete($this);
            } elseif ($concrete === $abstract) {
                // 4. Рецепта нет (или он указывает сам на себя) — строим класс.
                $object = $this->build($concrete);
            } else {
                // 5. Рецепт — другое имя. Разрешаем его целиком,
                //    чтобы сработали привязки, зарегистрированные уже для него.
                $object = $this->make($concrete);
            }
        } finally {
            unset($this->buildStack&#91;$abstract&#93;);
        }

        // 6. Синглтон? Запоминаем.
        if ($binding !== null &amp;&amp; $binding&#91;'shared'&#93;) {
            $this->instances&#91;$abstract&#93; = $object;
        }

        return $object;
    }

    /** Сборка класса через рефлексию — то самое автоматическое связывание */
    protected function build(string $class): object
    {
        if (! class_exists($class)) {
            throw new RuntimeException("Не найден класс {$class}.");
        }

        $reflector = new ReflectionClass($class);

        if (! $reflector->isInstantiable()) {
            throw new RuntimeException(
                "Нельзя создать {$class}: это интерфейс или абстрактный класс. Нужна привязка."
            );
        }

        $constructor = $reflector->getConstructor();

        // Конструктора нет — создаём напрямую
        if ($constructor === null) {
            return new $class();
        }

        $arguments = [];

        foreach ($constructor->getParameters() as $parameter) {
            $type = $parameter->getType();

            // Скаляр, отсутствие типа, объединение или пересечение типов —
            // всё это наш учебный контейнер разрешить не умеет.
            // Единственное, что мы можем сделать, — взять значение по умолчанию.
            if ($type === null || ! $type instanceof \ReflectionNamedType || $type->isBuiltin()) {
                if ($parameter->isDefaultValueAvailable()) {
                    $arguments[] = $parameter->getDefaultValue();
                    continue;
                }

                throw new RuntimeException(
                    "Не могу разрешить параметр \${$parameter->getName()} класса {$class}."
                );
            }

            // Тип — класс или интерфейс: рекурсивно создаём его.
            // Если не вышло, но параметр допускает значение по умолчанию, берём его.
            try {
                $arguments[] = $this->make($type->getName());
            } catch (RuntimeException $e) {
                if (! $parameter->isDefaultValueAvailable()) {
                    throw $e;
                }

                $arguments[] = $parameter->getDefaultValue();
            }
        }

        return $reflector->newInstanceArgs($arguments);
    }
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">&lt;?</span><span style="color: #D8DEE9FF">php</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Closure</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ReflectionClass</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">RuntimeException</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">MiniContainer</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/** Рецепты создания: &#39;Интерфейс&#39; =&gt; &#91;&#39;concrete&#39; =&gt; ..., &#39;shared&#39; =&gt; bool&#93; */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">bindings</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/** Уже созданные общие экземпляры */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">instances</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/** Что сейчас собираем — нужно, чтобы поймать циклическую зависимость */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">buildStack</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">bind</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">abstract</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Closure</span><span style="color: #ECEFF4">|</span><span style="color: #81A1C1">string</span><span style="color: #ECEFF4">|</span><span style="color: #81A1C1">null</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">concrete</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">bool</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">shared</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">bindings</span><span style="color: #ECEFF4">&#91;</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">abstract</span><span style="color: #ECEFF4">&#93;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#91;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">concrete</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">concrete</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">??</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">abstract</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">shared</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">shared</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#93;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">singleton</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">abstract</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Closure</span><span style="color: #ECEFF4">|</span><span style="color: #81A1C1">string</span><span style="color: #ECEFF4">|</span><span style="color: #81A1C1">null</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">concrete</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">bind</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">abstract</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">concrete</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">instance</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">abstract</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">object</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">object</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">instances</span><span style="color: #ECEFF4">&#91;</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">abstract</span><span style="color: #ECEFF4">&#93;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">object</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/** Главный метод: получить объект по имени */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">abstract</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">mixed</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 1. Уже создавали и договаривались хранить? Отдаём то же самое.</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">isset</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">instances</span><span style="color: #ECEFF4">&#91;</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">abstract</span><span style="color: #ECEFF4">&#93;))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">instances</span><span style="color: #ECEFF4">&#91;</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">abstract</span><span style="color: #ECEFF4">&#93;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 2. Защита от циклов: A требует B, B требует A.</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">isset</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">buildStack</span><span style="color: #ECEFF4">&#91;</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">abstract</span><span style="color: #ECEFF4">&#93;))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">RuntimeException</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Циклическая зависимость: </span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">implode</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C"> -&gt; </span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> array_keys</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">buildStack</span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C"> -&gt; </span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">abstract</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">.</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">buildStack</span><span style="color: #ECEFF4">&#91;</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">abstract</span><span style="color: #ECEFF4">&#93;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">binding</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">bindings</span><span style="color: #ECEFF4">&#91;</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">abstract</span><span style="color: #ECEFF4">&#93;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">??</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">concrete</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">binding</span><span style="color: #ECEFF4">&#91;</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">concrete</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">&#93;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">??</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">abstract</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">concrete</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">instanceof</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Closure</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">                </span><span style="color: #616E88">// 3. Рецепт-функция: просто выполняем её.</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">object</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">concrete</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">elseif</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">concrete</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">===</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">abstract</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">                </span><span style="color: #616E88">// 4. Рецепта нет (или он указывает сам на себя) — строим класс.</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">object</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">build</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">concrete</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">                </span><span style="color: #616E88">// 5. Рецепт — другое имя. Разрешаем его целиком,</span></span>
<span class="line"><span style="color: #ECEFF4">                </span><span style="color: #616E88">//    чтобы сработали привязки, зарегистрированные уже для него.</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">object</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">concrete</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">finally</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">unset</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">buildStack</span><span style="color: #ECEFF4">&#91;</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">abstract</span><span style="color: #ECEFF4">&#93;)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// 6. Синглтон? Запоминаем.</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">binding</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!==</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">binding</span><span style="color: #ECEFF4">&#91;</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">shared</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">&#93;)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">instances</span><span style="color: #ECEFF4">&#91;</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">abstract</span><span style="color: #ECEFF4">&#93;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">object</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">object</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/** Сборка класса через рефлексию — то самое автоматическое связывание */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">build</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">object</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">class_exists</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">class</span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">RuntimeException</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Не найден класс </span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">class</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">reflector</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ReflectionClass</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">reflector</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isInstantiable</span><span style="color: #ECEFF4">())</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">RuntimeException</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Нельзя создать </span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">class</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">: это интерфейс или абстрактный класс. Нужна привязка.</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">constructor</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">reflector</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getConstructor</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// Конструктора нет — создаём напрямую</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">constructor</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">===</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">class</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">arguments</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">foreach</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">constructor</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getParameters</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">as</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">parameter</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">type</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">parameter</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getType</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// Скаляр, отсутствие типа, объединение или пересечение типов —</span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// всё это наш учебный контейнер разрешить не умеет.</span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// Единственное, что мы можем сделать, — взять значение по умолчанию.</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">type</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">===</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">||</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">type</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">instanceof</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">ReflectionNamedType</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">||</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">type</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isBuiltin</span><span style="color: #ECEFF4">())</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">parameter</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isDefaultValueAvailable</span><span style="color: #ECEFF4">())</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">arguments</span><span style="color: #ECEFF4">[]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">parameter</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getDefaultValue</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #81A1C1">continue;</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">RuntimeException</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Не могу разрешить параметр </span><span style="color: #EBCB8B">\$</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">parameter</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getName</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C"> класса </span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">class</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">.</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// Тип — класс или интерфейс: рекурсивно создаём его.</span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// Если не вышло, но параметр допускает значение по умолчанию, берём его.</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">arguments</span><span style="color: #ECEFF4">[]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">type</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getName</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">RuntimeException</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">e</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">parameter</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isDefaultValueAvailable</span><span style="color: #ECEFF4">())</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">e</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">arguments</span><span style="color: #ECEFF4">[]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">parameter</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getDefaultValue</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">reflector</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">newInstanceArgs</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">arguments</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Проверим:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>interface NotificationSender {
    public function send(string $phone, string $text): void;
}

class SmsSender implements NotificationSender {
    public function __construct(private string $apiKey) {}
    public function send(string $phone, string $text): void {
        echo "SMS на {$phone}: {$text}\n";
    }
}

class OrderService {
    public function __construct(public NotificationSender $sender) {}
}

$container = new MiniContainer();

$container->bind(NotificationSender::class, function () {
    return new SmsSender('секретный-ключ');
});

$service = $container->make(OrderService::class);
$service->sender->send('+7999', 'Заказ подтверждён');
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">interface</span><span style="color: #D8DEE9FF"> NotificationSender </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">send</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">phone</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">text</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SmsSender</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">implements</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">NotificationSender</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">apiKey</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">send</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">phone</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">text</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">SMS на </span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">phone</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">: </span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">text</span><span style="color: #81A1C1">}</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">OrderService</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">NotificationSender</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">sender</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">container</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">MiniContainer</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">container</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">bind</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">NotificationSender</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SmsSender</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">секретный-ключ</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">service</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">container</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">OrderService</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">service</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">sender</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">send</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">+7999</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Заказ подтверждён</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Мы нигде не сказали, как создавать <code>OrderService</code>. Контейнер сам прочитал конструктор, увидел <code>NotificationSender</code>, нашёл для него рецепт, выполнил его и собрал всё вместе.</p>



<p class="wp-block-paragraph">В Laravel происходит похожий процесс: контейнер анализирует зависимости, находит зарегистрированные правила и создаёт объекты. Но реальный контейнер значительно сложнее: он поддерживает контекстные привязки и атрибуты, метки, события разрешения, <code>extend</code>, <code>scoped</code>, <code>rebinding</code>, разные времена жизни объектов, передачу параметров и множество других случаев.</p>



<p class="wp-block-paragraph">Чего наш мини-контейнер намеренно не умеет — полезно держать в голове, если вдруг захочется взять его как основу:</p>



<ul class="wp-block-list">
<li><strong>variadic-параметры</strong> (<code>__construct(Handler ...$handlers)</code>) — Laravel умеет собирать их из привязки-массива или из метки;</li>



<li><strong>типы <code>self</code>, <code>static</code>, <code>parent</code></strong> — формально это <code>ReflectionNamedType</code> с <code>isBuiltin() === false</code>, и <code>make('self')</code> предсказуемо упадёт;</li>



<li><strong><code>makeWith</code></strong>, то есть передачу части аргументов вручную;</li>



<li><strong><code>scoped</code></strong> и сброс таких экземпляров между запросами;</li>



<li><strong><code>extend</code>, метки, коллбэки <code>resolving</code></strong> и контекстные привязки.</li>
</ul>



<p class="wp-block-paragraph">Зато основная идея — прочитать конструктор рефлексией и рекурсивно собрать зависимости — здесь ровно та же, что и в настоящем контейнере.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="block-16" class="wp-block-heading">16. Итог</h2>



<p class="wp-block-paragraph">Если оставить одну мысль, пусть будет эта:</p>



<p class="wp-block-paragraph"><strong>Классы не должны сами добывать то, чем пользуются, — они должны честно объявлять это в конструкторе. Это внедрение зависимостей. А контейнер — то, что делает такой стиль посильным: он берёт на себя сборку, которая иначе расползлась бы по всему приложению.</strong></p>



<p class="wp-block-paragraph">Всё остальное — следствия:</p>



<ul class="wp-block-list">
<li>зависимости видны в сигнатуре конструктора, класс сам себя документирует;</li>



<li>реализация меняется в одной строке сервис-провайдера, а не в тридцати местах;</li>



<li>тесты пишутся без плясок, потому что подменить можно что угодно;</li>



<li>сборка сложных объектов происходит один раз в одном месте.</li>
</ul>



<h3 class="wp-block-heading">Практический совет напоследок. </h3>



<p class="wp-block-paragraph">Не регистрируйте вручную каждый класс и не создавайте интерфейсы просто «на всякий случай». Автоматическое разрешение зависимостей покрывает множество обычных случаев без какой-либо настройки. Явные привязки особенно часто нужны, когда используется интерфейс, который нельзя создать напрямую, когда в конструктор нужно передать конкретные значения, или когда требуется особое управление созданием и временем жизни объекта. </p>



<p class="wp-block-paragraph">Есть и другие возможности контейнера — контекстные привязки и атрибуты, метки, расширение уже созданных объектов и другие специальные случаи. Главное — использовать их тогда, когда они действительно решают задачу.</p>



<p class="wp-block-paragraph"></p>
<p>Сообщение <a href="https://mcodex.ru/laravel-service-container/">Разбираемся с сервис-контейнером в Laravel.</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Воркеры в Laravel</title>
		<link>https://mcodex.ru/workers-in-laravel/</link>
		
		<dc:creator><![CDATA[mcodex]]></dc:creator>
		<pubDate>Sat, 08 Aug 2026 09:32:03 +0000</pubDate>
				<category><![CDATA[Laravel]]></category>
		<guid isPermaLink="false">https://mcodex.ru/?p=389</guid>

					<description><![CDATA[<p>«Воркер» &#8211; это не какая-то особая сущность фреймворка. Это обычный долгоживущий PHP-процесс, запущенный из командной строки, который крутит бесконечный цикл и не завершается. Когда вы выполняете php artisan queue:work, PHP-скрипт запускается и просто не возвращает управление. Внутри у него: Всё. Воркер &#8211; это исполнитель цикла «взять → выполнить → повторить». Чем воркер принципиально отличается от ... <a title="Воркеры в Laravel" class="read-more" href="https://mcodex.ru/workers-in-laravel/" aria-label="Read more about Воркеры в Laravel">Read more</a></p>
<p>Сообщение <a href="https://mcodex.ru/workers-in-laravel/">Воркеры в Laravel</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">«Воркер» &#8211; это не какая-то особая сущность фреймворка. Это <strong>обычный долгоживущий PHP-процесс</strong>, запущенный из командной строки, который крутит бесконечный цикл и не завершается.</p>



<p class="wp-block-paragraph">Когда вы выполняете <code>php artisan queue:work</code>, PHP-скрипт запускается и просто не возвращает управление. Внутри у него:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>while (true) {
    $job = взять задачу из Redis;

    if (!$job) {
        sleep(3);          // очередь пуста
        continue;
    }

    unserialize($job);     // развернуть объект
    $job->handle();        // выполнить

    удалить задачу из Redis (или вернуть при ошибке);

    проверить: не пора ли мне умереть?  // память, --max-jobs, --max-time, сигнал
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">while</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">true</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">$job</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">взять</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">задачу</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">из</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">Redis</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9">$job</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">sleep(3</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">          </span><span style="color: #88C0D0">//</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">очередь</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">пуста</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">continue;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">unserialize($job</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">     </span><span style="color: #88C0D0">//</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">развернуть</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">объект</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">$job-&gt;handle</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">//</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">выполнить</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">удалить</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">задачу</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">из</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">Redis</span><span style="color: #D8DEE9FF"> (или </span><span style="color: #A3BE8C">вернуть</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">при</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ошибке</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">проверить:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">не</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">пора</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ли</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">мне</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">умереть?</span><span style="color: #D8DEE9FF">  </span><span style="color: #A3BE8C">//</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">память,</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--max-jobs,</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--max-time,</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">сигнал</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Всё. Воркер &#8211; это исполнитель цикла «взять → выполнить → повторить».</p>



<h3 class="wp-block-heading">Чем воркер принципиально отличается от обычного PHP-запроса</h3>



<p class="wp-block-paragraph">Вот это главное, что нужно уложить в голове.</p>



<p class="wp-block-paragraph">При работе через PHP-FPM каждый HTTP-запрос — это чистый лист: фреймворк загружается, отрабатывает, умирает, вся память освобождается. Утечки не страшны, «протухшее» состояние невозможно.</p>



<p class="wp-block-paragraph">Воркер устроен наоборот: <strong>фреймворк бутстрапится один раз</strong>, при старте процесса, и дальше сотни задач выполняются в одном и том же экземпляре приложения. Отсюда три практических следствия:</p>



<p class="wp-block-paragraph"><strong>Утечки памяти накапливаются.</strong> Процесс, отработавший сутки, может съесть гигабайт. Поэтому у воркера есть <code>--memory=128</code>: после каждой задачи он проверяет <code>memory_get_usage()</code> и, превысив лимит, сам завершается — чтобы менеджер процессов поднял свежий.</p>



<p class="wp-block-paragraph"><strong>Изменения в коде не подхватываются.</strong> Воркер держит в памяти классы, загруженные при старте. Задеплоили новую версию job — процесс продолжит выполнять старую. Именно поэтому после каждого деплоя обязателен <code>php artisan queue:restart</code> (или <code>horizon:terminate</code>).</p>



<p class="wp-block-paragraph"><strong>Состояние приложения «протухает».</strong> Синглтоны в контейнере, статические свойства, уже разрешённые сервисы, <code>Auth::user()</code> от предыдущей задачи, изменённая конфигурация — всё это переживает границу между задачами. Классический баг: в одной job поменяли локаль или подключение к БД, и следующие сто задач работают с чужими настройками. Laravel сбрасывает часть состояния между задачами, но не всё — на свои синглтоны полагаться нельзя.</p>



<p class="wp-block-paragraph">Есть альтернатива — <code>php artisan queue:listen</code>: он поднимает отдельный дочерний процесс на <strong>каждую</strong> задачу, то есть чистый лист как при HTTP. Ни утечек, ни протухшего кода, ни <code>queue:restart</code>. Расплата — бутстрап фреймворка на каждую задачу, что в разы медленнее. В продакшене используют <code>queue:work</code>, <code>listen</code> — иногда в разработке.</p>



<h3 class="wp-block-heading">Параллелизм — это просто много процессов</h3>



<p class="wp-block-paragraph">В PHP нет потоков. «Десять воркеров» означает буквально десять независимых процессов ОС, у каждого свой PID и своя память. Они ничего друг о друге не знают и координируются исключительно через Redis.</p>



<p class="wp-block-paragraph">Отсюда вопрос: почему два воркера не схватят одну задачу? Потому что операция «забрать» атомарна — Laravel выполняет Lua-скрипт, который одним неделимым действием вынимает задачу из списка и кладёт её в sorted set <code>queues:default:reserved</code> с меткой времени. Второй воркер её там уже не увидит. Если первый умрёт, не доделав, — через <code>retry_after</code> секунд задача из reserved вернётся в основной список и достанется кому-то другому.</p>



<h3 class="wp-block-heading">Как воркер умирает</h3>



<p class="wp-block-paragraph">Штатных причин несколько, и все они — часть дизайна:</p>



<ul class="wp-block-list">
<li><strong><code>--timeout</code></strong> — задача выполняется дольше лимита. Реализовано через <code>pcntl_alarm()</code>: перед запуском job взводится будильник, по срабатыванию процесс убивается. Это единственный способ прервать зависший <code>handle()</code> — потому и нужно расширение <code>pcntl</code>.</li>



<li><strong><code>--memory</code></strong> — превышен лимит памяти, воркер выходит после текущей задачи.</li>



<li><strong><code>--max-jobs</code> / <code>--max-time</code></strong> — профилактический перезапуск: отработал 1000 задач или час, вышел, поднялся заново с чистой памятью.</li>



<li><strong>Сигнал</strong> — <code>SIGTERM</code> при деплое или остановке. Воркер дорабатывает текущую задачу и выходит.</li>
</ul>



<p class="wp-block-paragraph">Во всех случаях подразумевается, что процесс кто-то перезапустит. Сам по себе воркер не самовосстанавливается — «умереть и переродиться» и есть его штатный способ обновиться. Именно это делает Horizon (для своих воркеров) или supervisord (для Horizon в целом).</p>



<h3 class="wp-block-heading">Где воркеры в схеме <a href="https://mcodex.ru/laravel-horizon-supervisor/" data-type="post" data-id="381">Очереди в Laravel, Horizon, Supervisor</a></h3>



<p class="wp-block-paragraph">В тексте статьи воркеры — нижний уровень иерархии, процессы <code>php artisan horizon:work</code>. Это те же самые <code>queue:work</code> по сути, только запущенные не вами вручную, а процессом <code>horizon:supervisor</code> согласно <code>config/horizon.php</code>, и с дополнительной отправкой метрик в Redis для дашборда.</p>



<p class="wp-block-paragraph"></p>
<p>Сообщение <a href="https://mcodex.ru/workers-in-laravel/">Воркеры в Laravel</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Очереди в Laravel, Horizon, Supervisor</title>
		<link>https://mcodex.ru/laravel-horizon-supervisor/</link>
		
		<dc:creator><![CDATA[mcodex]]></dc:creator>
		<pubDate>Sat, 08 Aug 2026 09:07:50 +0000</pubDate>
				<category><![CDATA[Laravel]]></category>
		<guid isPermaLink="false">https://mcodex.ru/?p=381</guid>

					<description><![CDATA[<p>Как работают очереди в Laravel: Redis + Horizon + Supervisor Разберём эту систему по слоям: от концепции очередей до того, как Linux следит, чтобы всё это не падало. 1. Зачем вообще нужны очереди Когда пользователь делает запрос (например, регистрируется на сайте), некоторые действия долгие: отправить email, сгенерировать PDF, обработать видео, дёрнуть внешний API. Если делать ... <a title="Очереди в Laravel, Horizon, Supervisor" class="read-more" href="https://mcodex.ru/laravel-horizon-supervisor/" aria-label="Read more about Очереди в Laravel, Horizon, Supervisor">Read more</a></p>
<p>Сообщение <a href="https://mcodex.ru/laravel-horizon-supervisor/">Очереди в Laravel, Horizon, Supervisor</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">Как работают очереди в Laravel: Redis + Horizon + Supervisor</h2>



<p class="wp-block-paragraph">Разберём эту систему по слоям: от концепции очередей до того, как Linux следит, чтобы всё это не падало.</p>



<h2 class="wp-block-heading">1. Зачем вообще нужны очереди</h2>



<p class="wp-block-paragraph">Когда пользователь делает запрос (например, регистрируется на сайте), некоторые действия долгие: отправить email, сгенерировать PDF, обработать видео, дёрнуть внешний API. Если делать это синхронно &#8211; пользователь будет ждать несколько секунд, а то и упадёт по таймауту.</p>



<p class="wp-block-paragraph">Решение: вместо того чтобы выполнять тяжёлую работу прямо в HTTP-запросе, Laravel кладёт задачу («job») в очередь и сразу отвечает пользователю. Кто-то другой (worker) потом эту задачу заберёт и выполнит в фоне.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>ProcessPodcast::dispatch($podcast); // мгновенно, не блокирует ответ
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">ProcessPodcast::dispatch($podcast</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">//</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">мгновенно,</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">не</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">блокирует</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ответ</span></span>
<span class="line"></span></code></pre></div>



<h2 class="wp-block-heading">2. Redis как хранилище очереди</h2>



<p class="wp-block-paragraph">Очереди нужно где-то хранить. Laravel поддерживает разные драйверы (<code>database</code>, <code>sqs</code>, <code>redis</code> и т.д.). Redis &#8211; самый популярный выбор для очередей, потому что:</p>



<ul class="wp-block-list">
<li>он in-memory и очень быстрый;</li>



<li>имеет встроенные структуры данных, удобные для очередей (списки, sorted sets);</li>



<li>поддерживает атомарные операции. Важно, чтобы два worker&#8217;а не схватили одну и ту же задачу. Laravel реализует эту атомарность через Lua-скрипты, выполняемые Redis целиком, без возможности вклиниться между шагами.</li>
</ul>



<p class="wp-block-paragraph">Технически, когда вы вызываете <code>dispatch()</code>, Laravel сериализует job (класс, его свойства) и помещает её в Redis-очередь (список <code>queues:default</code>).</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">Подробнее про сериализацию задачи.</h3>



<p class="wp-block-paragraph">Сериализуется не класс, а объект класса Job.</p>



<h4 class="wp-block-heading">Что реально уходит в Redis</h4>



<p class="wp-block-paragraph">В Redis-список кладётся <strong>JSON-строка</strong> &#8211; конверт с метаданными. А сам объект job лежит внутри неё отдельным полем, сериализованный нативным PHP <code>serialize()</code>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>// Illuminate\Queue\Queue::createObjectPayload()
'data' => &#91;
    'commandName' => get_class($job),        // 'App\Jobs\ProcessPodcast'
    'command'     => serialize(clone $job),  // ← вот здесь сам инстанс
&#93;,
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">//</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">Illuminate</span><span style="color: #EBCB8B">\Q</span><span style="color: #A3BE8C">ueue</span><span style="color: #EBCB8B">\Q</span><span style="color: #A3BE8C">ueue::createObjectPayload</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #88C0D0">&#39;data&#39;</span><span style="color: #D8DEE9FF"> =</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> &#91;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">&#39;commandName&#39;</span><span style="color: #D8DEE9FF"> =</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">get_class</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9">$job</span><span style="color: #ECEFF4">)</span><span style="color: #A3BE8C">,</span><span style="color: #D8DEE9FF">        </span><span style="color: #A3BE8C">//</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">App\Jobs\ProcessPodcast</span><span style="color: #ECEFF4">&#39;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">&#39;command&#39;</span><span style="color: #D8DEE9FF">     =</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">serialize</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">clone</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$job</span><span style="color: #ECEFF4">)</span><span style="color: #A3BE8C">,</span><span style="color: #D8DEE9FF">  </span><span style="color: #A3BE8C">//</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">←</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">вот</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">здесь</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">сам</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">инстанс</span></span>
<span class="line"><span style="color: #D8DEE9FF">&#93;,</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Итоговый payload примерно такой:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>{
  "uuid": "9a1c...",
  "displayName": "App\\Jobs\\ProcessPodcast",
  "job": "Illuminate\\Queue\\CallQueuedHandler@call",
  "maxTries": 3,
  "timeout": 60,
  "retryUntil": null,
  "attempts": 0,
  "data": {
    "commandName": "App\\Jobs\\ProcessPodcast",
    "command": "O:24:\"App\\Jobs\\ProcessPodcast\":1:{s:7:\"podcast\";..."
  }
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #88C0D0">&quot;uuid&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">9a1c...</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #88C0D0">&quot;displayName&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">App</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">Jobs</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">ProcessPodcast</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #88C0D0">&quot;job&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Illuminate</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">Queue</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">CallQueuedHandler@call</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #88C0D0">&quot;maxTries&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #88C0D0">&quot;timeout&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">60</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #88C0D0">&quot;retryUntil&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">null,</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #88C0D0">&quot;attempts&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #88C0D0">&quot;data&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">&quot;commandName&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">App</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">Jobs</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">ProcessPodcast</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">&quot;command&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">O:24:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">App</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">Jobs</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">ProcessPodcast</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">:1:{s:7:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">podcast</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;...</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Метаданные (<code>maxTries</code>, <code>timeout</code>, <code>retryUntil</code>, <code>attempts</code>) вынесены наружу специально: воркеру нужно прочитать их <strong>до</strong> того, как он развернёт объект,  например, чтобы решить, не пора ли отправить задачу в <code>failed_jobs</code>. На стороне воркера <code>CallQueuedHandler@call</code> делает <code>unserialize($data['command'])</code> и вызывает <code>handle()</code> через контейнер (поэтому в <code>handle()</code> работает method injection).</p>



<p class="wp-block-paragraph"><code>clone</code> перед <code>serialize()</code> нужен, чтобы магия <code>__serialize()</code>/<code>__sleep()</code> не покалечила исходный объект в текущем процессе — он ведь может использоваться дальше.</p>



<h3 class="wp-block-heading">Практические следствия</h3>



<p class="wp-block-paragraph"><strong>Сериализуются все свойства</strong>: public, protected, private.<br>Значит, в job нельзя класть то, что PHP сериализовать не умеет:<br>PDO-соединения, файловые дескрипторы, ресурсы, замыкания.<br>Для очередей из замыканий (<code>dispatch(function () { ... })</code>) Laravel подключает отдельный пакет <code>laravel/serializable-closure</code>.</p>



<p class="wp-block-paragraph"><strong>Трейт <code>SerializesModels</code> меняет правила для Eloquent.</strong> Он перехватывает <code>__serialize()</code> и подменяет модель на <code>ModelIdentifier</code> &#8211; класс, id, имя соединения и список загруженных связей.<br>При десериализации модель <strong>заново достаётся из БД</strong>.<br>Два следствия:</p>



<ul class="wp-block-list">
<li>воркер видит актуальное состояние записи, а не снимок на момент dispatch — обычно это то, что нужно;</li>



<li>если запись успели удалить, <code>unserialize</code> бросит <code>ModelNotFoundException</code>. По умолчанию (<code>deleteWhenMissingModels</code>) такая задача тихо удаляется, не попадая в <code>failed_jobs</code>.</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">Для отложенных задач (<code>-&gt;delay()</code>) и для задач, уже взятых воркером в работу («reserved»), Laravel использует <strong>sorted set</strong>: задачи там отсортированы по timestamp&#8217;у, когда они должны стать доступными снова.</p>



<h3 class="wp-block-heading">Как воркер узнаёт о новой задаче</h3>



<p class="wp-block-paragraph">Здесь важная деталь, которую часто описывают неверно. По умолчанию воркер <strong>не</strong> висит в блокирующем ожидании,  он работает в режиме polling:</p>



<ol class="wp-block-list">
<li>пытается забрать задачу из очереди;</li>



<li>если очередь пуста &#8211; спит <code>--sleep=3</code> секунды;</li>



<li>повторяет.</li>
</ol>



<p class="wp-block-paragraph">Блокирующее ожидание (<code>BLPOP</code> по служебному ключу <code>queues:default:notify</code>) включается, только если в <code>config/queue.php</code> для redis-подключения явно задан <code>block_for</code>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>'redis' => &#91;
    'driver' => 'redis',
    'connection' => 'default',
    'queue' => env('REDIS_QUEUE', 'default'),
    'retry_after' => 90,
    'block_for' => 5, // по умолчанию null → polling
&#93;,
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">&#39;redis&#39;</span><span style="color: #D8DEE9FF"> =</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> &#91;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">&#39;driver&#39;</span><span style="color: #D8DEE9FF"> =</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">redis</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">&#39;connection&#39;</span><span style="color: #D8DEE9FF"> =</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">default</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">&#39;queue&#39;</span><span style="color: #D8DEE9FF"> =</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">env</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">&#39;REDIS_QUEUE&#39;</span><span style="color: #88C0D0">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">default</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">&#39;retry_after&#39;</span><span style="color: #D8DEE9FF"> =</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">90</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">&#39;block_for&#39;</span><span style="color: #D8DEE9FF"> =</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #A3BE8C">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">//</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">по</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">умолчанию</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">null</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">→</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">polling</span></span>
<span class="line"><span style="color: #D8DEE9FF">&#93;,</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Блокирующий режим снижает задержку подхвата задачи и нагрузку на Redis, но занимает соединение на всё время ожидания.</p>



<p class="wp-block-paragraph"><strong><code>retry_after</code> и <code>timeout</code> &#8211; источники дублей</strong></p>



<p class="wp-block-paragraph">Два параметра, которые обязательно надо согласовать:</p>



<ul class="wp-block-list">
<li><code>retry_after</code> (в <code>config/queue.php</code>).  Через сколько секунд Laravel считает задачу «зависшей» и возвращает её в очередь;</li>



<li><code>timeout</code> воркера (в Horizon &#8211; ключ <code>timeout</code> в <code>config/horizon.php</code>). Через сколько секунд принудительно убить процесс, выполняющий job.</li>
</ul>



<p class="wp-block-paragraph"><strong><code>retry_after</code> должен быть строго больше самого долгого <code>timeout</code>.</strong> Иначе задача будет отдана второму воркеру, пока первый её ещё выполняет и вы получите дублирующиеся отправки писем и списания.</p>



<h2 class="wp-block-heading">3. Кто выполняет задачи: воркеры</h2>



<p class="wp-block-paragraph">См: <a href="https://mcodex.ru/workers-in-laravel/" data-type="post" data-id="389">Что такое Воркеры</a></p>



<p class="wp-block-paragraph">Сама по себе задача в Redis просто лежит и ждёт. Её должен кто-то забрать и выполнить. Это делает команда:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>php artisan queue:work redis
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">artisan</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">queue:work</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">redis</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Это <strong>долгоживущий процесс</strong>: обычно он запускается один раз и работает непрерывно (в цикле: взять задачу → выполнить → взять следующую). При этом worker может завершиться по различным причинам,  например, из-за заданных ограничений времени/количества jobs (<code>--max-time</code>, <code>--max-jobs</code>), превышения лимита памяти (<code>--memory</code>), ошибки или получения сигнала. Именно поэтому его обычно запускают под управлением отдельного менеджера процессов или Horizon.</p>



<p class="wp-block-paragraph">Проблема: что если worker упадёт (ошибка, сервер перезагрузился, память закончилась)? Кто его перезапустит? Тут в игру вступают Horizon и Supervisor.</p>



<p class="wp-block-paragraph">Задачи, исчерпавшие попытки, попадают в failed jobs, по умолчанию в таблицу <code>failed_jobs</code> (драйвер <code>database</code>). Horizon дополнительно хранит их копию в Redis, чтобы показывать в дашборде и давать кнопку retry; срок хранения этих данных задаётся в <code>config/horizon.php</code> (<code>trim</code> / <code>failed</code> в секции хранения).</p>



<h2 class="wp-block-heading">4. Что такое Laravel Horizon</h2>



<p class="wp-block-paragraph">Horizon &#8211; это надстройка над обычными Redis-воркерами, которая даёт:</p>



<ol class="wp-block-list">
<li><strong>Красивый дашборд</strong>. Видно, сколько задач выполнено, сколько упало, сколько задач в очереди, throughput в реальном времени, время выполнения job&#8217;ов.</li>



<li><strong>Управление несколькими очередями и воркерами из одного конфига</strong> (<code>config/horizon.php</code>).  Можно описать, сколько процессов держать на очереди <code>high</code>, сколько на <code>default</code>, с авто-масштабированием (<code>'balance' =&gt; 'auto'</code>).</li>



<li><strong>Единую точку запуска</strong>.  Вместо того чтобы вручную поднимать десяток <code>queue:work</code> процессов, вы запускаете один процесс:</li>
</ol>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>php artisan horizon
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">artisan</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">horizon</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Horizon сам, внутри себя, запускает и следит за нужным числом дочерних worker-процессов согласно конфигу (supervisors в терминологии Horizon — не путать с Linux Supervisor, это разные вещи с одинаковым названием!).</p>



<p class="wp-block-paragraph">Два важных нюанса:</p>



<ul class="wp-block-list">
<li><strong>Horizon работает только с Redis-драйвером очередей.</strong> Это не универсальный менеджер для любых очередей.</li>



<li><strong>Нужны PHP-расширения <code>pcntl</code> и <code>posix</code>.</strong> Без них не работает обработка сигналов, а значит — ни <code>horizon:terminate</code>, ни корректный graceful shutdown, ни контроль дочерних процессов.</li>
</ul>



<h2 class="wp-block-heading">5. Два разных &#8220;Supervisor&#8221;,  не запутайтесь.</h2>



<p class="wp-block-paragraph">Тут стоит остановиться отдельно, потому что термин «supervisor» используется на двух уровнях:</p>



<ul class="wp-block-list">
<li><strong>Horizon Supervisor</strong>.  Это логическая сущность внутри <code>config/horizon.php</code>, группа воркеров, обслуживающих определённые очереди (например, <code>supervisor-1</code> держит 10 процессов на очереди <code>emails</code>).</li>



<li><strong>Linux Supervisor (supervisord).</strong>  Это отдельная системная программа, вообще не знающая ничего про Laravel, чья задача &#8211; следить, чтобы <em>один конкретный процесс</em> (в нашем случае <code>php artisan horizon</code>) был постоянно запущен.</li>
</ul>



<p class="wp-block-paragraph">Важно: Horizon <strong>не</strong> порождает воркеров напрямую из мастер-процесса. Иерархия трёхуровневая — на каждую supervisor-группу из конфига поднимается свой процесс-посредник:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>supervisord (Linux)
   └── следит за процессом: php artisan horizon        ← MasterSupervisor
              └── php artisan horizon:supervisor ...   ← по процессу на каждую
                    │                                     supervisor-группу из конфига
                    └── php artisan horizon:work ...    ← собственно воркеры
                              └── каждый воркер читает задачи из Redis
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">supervisord</span><span style="color: #D8DEE9FF"> (Linux)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #88C0D0">└──</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">следит</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">за</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">процессом:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">artisan</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">horizon</span><span style="color: #D8DEE9FF">        </span><span style="color: #A3BE8C">←</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">MasterSupervisor</span></span>
<span class="line"><span style="color: #D8DEE9FF">              </span><span style="color: #88C0D0">└──</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">artisan</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">horizon:supervisor</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">...</span><span style="color: #D8DEE9FF">   </span><span style="color: #A3BE8C">←</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">по</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">процессу</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">на</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">каждую</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #88C0D0">│</span><span style="color: #D8DEE9FF">                                     </span><span style="color: #A3BE8C">supervisor-группу</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">из</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">конфига</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #88C0D0">└──</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">artisan</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">horizon:work</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">...</span><span style="color: #D8DEE9FF">    </span><span style="color: #A3BE8C">←</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">собственно</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">воркеры</span></span>
<span class="line"><span style="color: #D8DEE9FF">                              </span><span style="color: #88C0D0">└──</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">каждый</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">воркер</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">читает</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">задачи</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">из</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">Redis</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Именно поэтому в <code>ps aux | grep horizon</code> процессов больше, чем сумма <code>maxProcesses</code> по конфигу: к воркерам добавляются мастер и по одному процессу на группу.</p>



<h2 class="wp-block-heading">6. Зачем нужен Linux Supervisor, если есть Horizon</h2>



<p class="wp-block-paragraph">Сам <code>php artisan horizon</code> — это тоже просто процесс. Если сервер перезагрузится, если процесс упадёт по ошибке PHP, если кто-то случайно его убьёт &#8211; задачи просто перестанут обрабатываться, и никто не заметит, пока очередь не разрастётся до небес.</p>



<p class="wp-block-paragraph">Supervisord решает эту проблему на уровне ОС: он запускает процесс, следит за его PID, и если процесс завершается — перезапускает автоматически. Конфиг обычно выглядит так:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>&#91;program:horizon&#93;
process_name=%(program_name)s
command=/usr/bin/php8.3 /var/www/project/artisan horizon
autostart=true
autorestart=true
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/var/www/project/storage/logs/horizon.log
stopwaitsecs=3600
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #ECEFF4">&#91;</span><span style="color: #D8DEE9FF">program:horizon</span><span style="color: #ECEFF4">&#93;</span></span>
<span class="line"><span style="color: #D8DEE9">process_name</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">%</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">program_name</span><span style="color: #ECEFF4">)</span><span style="color: #A3BE8C">s</span></span>
<span class="line"><span style="color: #D8DEE9">command</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">/usr/bin/php8.3</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">/var/www/project/artisan</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">horizon</span></span>
<span class="line"><span style="color: #D8DEE9">autostart</span><span style="color: #81A1C1">=</span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9">autorestart</span><span style="color: #81A1C1">=</span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">www-data</span></span>
<span class="line"><span style="color: #D8DEE9">numprocs</span><span style="color: #81A1C1">=</span><span style="color: #B48EAD">1</span></span>
<span class="line"><span style="color: #D8DEE9">redirect_stderr</span><span style="color: #81A1C1">=</span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9">stdout_logfile</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">/var/www/project/storage/logs/horizon.log</span></span>
<span class="line"><span style="color: #D8DEE9">stopwaitsecs</span><span style="color: #81A1C1">=</span><span style="color: #B48EAD">3600</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Разберём ключевые директивы:</p>



<ul class="wp-block-list">
<li><strong>command</strong> &#8211; указывайте абсолютный путь к бинарнику PHP: у supervisord свой <code>PATH</code>, и просто <code>php</code> может не разрешиться (или разрешиться не в ту версию).</li>



<li><strong>autostart</strong> &#8211; запускать при старте supervisord (обычно при загрузке сервера).</li>



<li><strong>autorestart</strong> &#8211; если процесс завершился, запустить заново.</li>



<li><strong>numprocs=1</strong> &#8211; явно фиксируем: Horizon должен быть в единственном экземпляре. Два параллельных мастер-процесса &#8211; это двойной набор воркеров и гонки.</li>



<li><strong>user</strong> &#8211; от чьего имени работает процесс (обычно тот же, что и у веб-сервера, ради прав доступа к файлам).</li>



<li><strong>stopwaitsecs=3600</strong> &#8211; сколько supervisord ждёт после отправки <code>SIGTERM</code>, прежде чем добить процесс через <code>SIGKILL</code>. Это применяется при <code>supervisorctl stop horizon</code>, <code>supervisorctl restart horizon</code> и при выключении/перезагрузке сервера. Значение должно быть не меньше максимально возможного времени выполнения job &#8211; иначе supervisord прибьёт Horizon посреди работы и задача оборвётся.</li>
</ul>



<p class="wp-block-paragraph">Обратите внимание: <code>stopwaitsecs</code> <strong>не</strong> участвует в сценарии <code>php artisan horizon:terminate</code> — там Horizon завершается сам, и supervisord просто фиксирует выход процесса. Подробнее — в следующем разделе.</p>



<h2 class="wp-block-heading">7. Как это выглядит при деплое</h2>



<p class="wp-block-paragraph">Правильный паттерн деплоя с Horizon+Supervisor такой:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>php artisan horizon:terminate
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">artisan</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">horizon:terminate</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Это не отключает конфигурацию Supervisor. Команда через Redis отправляет сигнал текущему процессу Horizon: тот инициирует graceful shutdown — текущие jobs получают возможность завершиться, новые не берутся, после чего воркеры и сам мастер-процесс выходят.</p>



<p class="wp-block-paragraph">Supervisord, увидев, что процесс завершился (а <code>autorestart=true</code>), запускает <strong>новый</strong> процесс <code>php artisan horizon</code> — уже с новым кодом. Так выполняется graceful restart Horizon после деплоя.</p>



<p class="wp-block-paragraph">Здесь supervisord ничего не ждёт и <code>stopwaitsecs</code> не применяется: он реагирует на уже случившийся выход процесса, а не сам его останавливает.</p>



<h2 class="wp-block-heading">8. Собираем всю картину воедино</h2>



<ol class="wp-block-list">
<li>Ваш Laravel-код вызывает <code>Job::dispatch()</code> → задача сериализуется и пушится в Redis-список (или в sorted set, если задан <code>delay</code>).</li>



<li><code>php artisan horizon</code> &#8211; единственный процесс, за которым следит Linux Supervisor (<code>autorestart</code>, живёт вечно).</li>



<li>Внутри себя Horizon поднимает по процессу <code>horizon:supervisor</code> на каждую supervisor-группу из <code>config/horizon.php</code>, а те, в свою очередь, — N процессов-воркеров (сколько на какую очередь, авто-баланс).</li>



<li>Каждый воркер опрашивает Redis (или блокирующе ждёт, если задан <code>block_for</code>), забирает задачу и выполняет. Horizon управляет worker-процессами и собирает метрики; данные Horizon тоже хранятся в Redis.</li>



<li>Если отдельный worker завершается, его дальнейшей судьбой управляет сам Horizon. Если завершается процесс Horizon, Linux Supervisor запускает его заново.</li>



<li>Дашборд Horizon (<code>/horizon</code> в браузере) использует данные Horizon из Redis и показывает состояние очередей и worker-процессов.</li>
</ol>



<h2 class="wp-block-heading">Что стоит посмотреть своими руками</h2>



<p class="wp-block-paragraph">Чтобы это закрепилось не только в теории, полезно:</p>



<ul class="wp-block-list">
<li>Открыть <code>config/horizon.php</code> и посмотреть на секцию <code>environments</code> — там видно, как задаются supervisor-группы (<code>supervisor-1</code>) и сколько процессов на какую очередь.<br></li>



<li>Выполнить <code>ps aux | grep horizon</code> и увидеть все три уровня: мастер, <code>horizon:supervisor</code>, <code>horizon:work</code>.<br></li>



<li>Зайти в Redis через <code>redis-cli</code> и посмотреть, сколько задач реально лежит в очереди. Учтите, что Laravel добавляет к ключам префикс из <code>config/database.php</code> (обычно <code>laravel_database_</code>), поэтому голый <code>LLEN queues:default</code> вернёт 0. Сначала найдите реальный ключ: <code>redis-cli --scan --pattern '*queues*' redis-cli LLEN laravel_database_queues:default</code> Это хорошо снимает «магию» с процесса.<br></li>



<li>Посмотреть <code>supervisorctl status</code> на сервере — увидеть, что Horizon значится там просто как один managed-процесс.<br></li>



<li>Намеренно уронить Horizon и посмотреть, как быстро supervisord его поднимет обратно. Используйте <code>kill &lt;pid&gt;</code> (SIGTERM) или <code>supervisorctl stop horizon</code>, а <strong>не</strong> <code>kill -9</code>: SIGKILL нельзя перехватить, мастер умрёт мгновенно, а дочерние <code>horizon:supervisor</code> и <code>horizon:work</code> останутся висеть сиротами. Horizon умеет подчищать осиротевшие группы, но не мгновенно — какое-то время на сервере будет двойной набор процессов.</li>
</ul>
<p>Сообщение <a href="https://mcodex.ru/laravel-horizon-supervisor/">Очереди в Laravel, Horizon, Supervisor</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Локальный сайт с SSL</title>
		<link>https://mcodex.ru/local-site-with-ssl/</link>
		
		<dc:creator><![CDATA[mcodex]]></dc:creator>
		<pubDate>Sun, 12 Jul 2026 09:58:11 +0000</pubDate>
				<category><![CDATA[DevOps]]></category>
		<guid isPermaLink="false">https://mcodex.ru/?p=375</guid>

					<description><![CDATA[<p>Иногда возникает необходимость открывать в браузере локальный сайт через https, например, для отладки функционала, доступ к которому ограничен по http (геопозиция, копирование в буфер и т.д.). 1. Устанаваливаем утилиту mkcert 2. Создаем корневой сертификат 3. Создаем сертификат для локального сайта 4. Проверяем папку, должны появится файлы 5. Добавляем сертфикат в настройку Nginx 6. Перегружаем Nginx</p>
<p>Сообщение <a href="https://mcodex.ru/local-site-with-ssl/">Локальный сайт с SSL</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Иногда возникает необходимость открывать в браузере локальный сайт через https, например, для отладки функционала, доступ к которому ограничен по http (геопозиция, копирование в буфер и т.д.).</p>



<p class="wp-block-paragraph"><strong>1. Устанаваливаем утилиту mkcert</strong></p>



<ol class="wp-block-list"></ol>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>sudo apt update
sudo apt install libnss3-tools
sudo apt install mkcert
sudo apt install golang-go
go install filippo.io/mkcert@latest</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">apt</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">update</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">apt</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">install</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">libnss3-tools</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">apt</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">install</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mkcert</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">apt</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">install</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">golang-go</span></span>
<span class="line"><span style="color: #88C0D0">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">install</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">filippo.io/mkcert@latest</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>2. Создаем корневой сертификат</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>mkcert -install</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">mkcert</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-install</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>3. Создаем сертификат для локального сайта</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>mkdir -p ~/certs &amp;&amp; cd ~/certs
mkcert my-project.local localhost 127.0.0.1</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">mkdir</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">~/certs</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&amp;&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">cd</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">~/certs</span></span>
<span class="line"><span style="color: #88C0D0">mkcert</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my-project.local</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">localhost</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">127.0</span><span style="color: #A3BE8C">.0.1</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>4. Проверяем папку, должны появится файлы</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>~/certs ls 
# my-project.local+2.pem (сертификат) 
# my-project.local+2-key.pem (ключ)</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">~</span><span style="color: #D8DEE9FF">/certs ls </span></span>
<span class="line"><span style="color: #616E88"># my-project.local+2.pem (сертификат) </span></span>
<span class="line"><span style="color: #616E88"># my-project.local+2-key.pem (ключ)</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>5. Добавляем сертфикат в настройку Nginx</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>server {
    listen 443 ssl;
    server_name my-project.local;

    ssl_certificate     /home/username/certs/my-project.local+2.pem;
    ssl_certificate_key /home/username/certs/my-project.local+2-key.pem;

    root /var/www/my-project;
    index index.html;
    .....
}

server {
    listen 80;
    server_name my-project.local;
    return 301 https://$host$request_uri;
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">server</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">listen</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">443</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ssl</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">server_name</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my-project.local</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">ssl_certificate</span><span style="color: #D8DEE9FF">     </span><span style="color: #A3BE8C">/home/username/certs/my-project.local+2.pem</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">ssl_certificate_key</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">/home/username/certs/my-project.local+2-key.pem</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">root</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">/var/www/my-project</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">index</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">index.html</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">.....</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">server</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">listen</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">80</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">server_name</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my-project.local</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">301</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">https://</span><span style="color: #D8DEE9">$host$request_uri</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>6. Перегружаем Nginx</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>sudo nginx -t
sudo systemctl reload nginx</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">nginx</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-t</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">systemctl</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">reload</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">nginx</span></span></code></pre></div>



<p class="wp-block-paragraph"></p>
<p>Сообщение <a href="https://mcodex.ru/local-site-with-ssl/">Локальный сайт с SSL</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Полезные консольные команды в Ubuntu</title>
		<link>https://mcodex.ru/ubuntu-commands/</link>
		
		<dc:creator><![CDATA[mcodex]]></dc:creator>
		<pubDate>Wed, 01 Jul 2026 15:37:39 +0000</pubDate>
				<category><![CDATA[DevOps]]></category>
		<guid isPermaLink="false">https://mcodex.ru/?p=372</guid>

					<description><![CDATA[<p>Поиск строки в файлах</p>
<p>Сообщение <a href="https://mcodex.ru/ubuntu-commands/">Полезные консольные команды в Ubuntu</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Поиск строки в файлах</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly># поиск в определенном файле
grep "строка которую ищем" file.txt

# поиск во всех файлах и папках в определенной папке
grep -r "строка которую ищем" /etc/log

# поиск во всех файлах и папках в текущей папке
grep -r "строка которую ищем" .

# показываем строки (-n) и игнорируем регистр (-i)
grep -rni "строка которую ищем" .

# ищем во всех файлах и папках, кроме исключений
grep -rn \
  --exclude-dir=vendor \
  --exclude-dir=.git \
  "строка которую ищем" .  </textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88"># поиск в определенном файле</span></span>
<span class="line"><span style="color: #88C0D0">grep</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">строка которую ищем</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">file.txt</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># поиск во всех файлах и папках в определенной папке</span></span>
<span class="line"><span style="color: #88C0D0">grep</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-r</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">строка которую ищем</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">/etc/log</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># поиск во всех файлах и папках в текущей папке</span></span>
<span class="line"><span style="color: #88C0D0">grep</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-r</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">строка которую ищем</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># показываем строки (-n) и игнорируем регистр (-i)</span></span>
<span class="line"><span style="color: #88C0D0">grep</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-rni</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">строка которую ищем</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># ищем во всех файлах и папках, кроме исключений</span></span>
<span class="line"><span style="color: #88C0D0">grep</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-rn</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #A3BE8C">--exclude-dir=vendor</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #A3BE8C">--exclude-dir=.git</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">строка которую ищем</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">.</span><span style="color: #D8DEE9FF">  </span></span></code></pre></div>
<p>Сообщение <a href="https://mcodex.ru/ubuntu-commands/">Полезные консольные команды в Ubuntu</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Полезные команды Artisan</title>
		<link>https://mcodex.ru/artisan-commands/</link>
		
		<dc:creator><![CDATA[mcodex]]></dc:creator>
		<pubDate>Wed, 01 Jul 2026 15:24:06 +0000</pubDate>
				<category><![CDATA[Laravel]]></category>
		<guid isPermaLink="false">https://mcodex.ru/?p=313</guid>

					<description><![CDATA[<p>Проверка соответсвия времени и таймзон на сервере и в базе данных</p>
<p>Сообщение <a href="https://mcodex.ru/artisan-commands/">Полезные команды Artisan</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Проверка соответсвия времени и таймзон на сервере и в базе данных</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>php artisan tinker --execute="echo json_encode(DB::select('SELECT NOW() as now, UTC_TIMESTAMP() as utc, @@global.time_zone as g, @@session.time_zone as s')&#91;0&#93;);"</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">artisan</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">tinker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--execute=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">echo json_encode(DB::select(&#39;SELECT NOW() as now, UTC_TIMESTAMP() as utc, @@global.time_zone as g, @@session.time_zone as s&#39;)&#91;0&#93;);</span><span style="color: #ECEFF4">&quot;</span></span></code></pre></div>



<p class="wp-block-paragraph"></p>
<p>Сообщение <a href="https://mcodex.ru/artisan-commands/">Полезные команды Artisan</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Полезные команды Git</title>
		<link>https://mcodex.ru/git-commands/</link>
		
		<dc:creator><![CDATA[mcodex]]></dc:creator>
		<pubDate>Thu, 21 May 2026 08:44:03 +0000</pubDate>
				<category><![CDATA[Git]]></category>
		<guid isPermaLink="false">https://mcodex.ru/?p=298</guid>

					<description><![CDATA[<p>Шапргалка по командам Git, используемых в ежедневной работе.</p>
<p>Сообщение <a href="https://mcodex.ru/git-commands/">Полезные команды Git</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><strong>Содержание</strong></p>



<ul class="wp-block-list">
<li><a href="#git-1" type="internal" id="#git-1">Не отслеживать изменение прав на файлы  и папки</a></li>



<li><a href="#git-2" type="internal" id="#git-2">Исправить описание в последнем коммите</a></li>



<li><a href="#git-3">Анализ проекта</a></li>



<li><a href="#git-4" type="internal" id="#git-4">Изменить удаленный репозиторий</a></li>



<li><a href="#git-5" type="internal" id="#git-5">Тэги</a></li>



<li><a href="#git-6">История изменений</a></li>



<li><a href="#git-7">Переименование ветки</a></li>
</ul>



<p class="wp-block-paragraph" id="git-1"><strong>Не отслеживать изменение прав</strong> </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>git config core.fileMode false</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">config</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">core.fileMode</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span></code></pre></div>



<p class="wp-block-paragraph" id="git-2"><strong>Исправить описание в последнем коммите</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>git commit --amend -m "New commit message"</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">commit</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--amend</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-m</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">New commit message</span><span style="color: #ECEFF4">&quot;</span></span></code></pre></div>



<p class="wp-block-paragraph" id="git-3"><strong>Анализ проекта</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly># Какие файлы меняются чаще всего
git log --format=format: --name-only --since="1 year ago" | sort | uniq -c | sort -nr | head -20

# Все контрибьюторы отсортированные по числу коммитов
git shortlog -sn --no-merges

# Кто активен за последние полгода
git shortlog -sn --no-merges --since="6 months ago"

# Количество коммитов помесячно
git log --format='%ad' --date=format:'%Y-%m' | sort | uniq -c

#Где скапливаются баги, отфильтровано по коммитам с ключевыми словами
git log -i -E --grep="fix|bug|broken" --name-only --format='' | sort | uniq -c | sort -nr | head -20

# Как часто делаются revert/hotfix
git log --oneline --since="1 year ago" | grep -iE 'revert|hotfix|emergency|rollback'</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88"># Какие файлы меняются чаще всего</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">log</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--format=format:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--name-only</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--since=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">1 year ago</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sort</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">uniq</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-c</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sort</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-nr</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">head</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-20</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Все контрибьюторы отсортированные по числу коммитов</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">shortlog</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-sn</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--no-merges</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Кто активен за последние полгода</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">shortlog</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-sn</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--no-merges</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--since=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">6 months ago</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Количество коммитов помесячно</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">log</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--format=</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">%ad</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--date=format:</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">%Y-%m</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sort</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">uniq</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-c</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">#Где скапливаются баги, отфильтровано по коммитам с ключевыми словами</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">log</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-i</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-E</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--grep=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fix|bug|broken</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--name-only</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--format=</span><span style="color: #ECEFF4">&#39;&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sort</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">uniq</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-c</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sort</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-nr</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">head</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-20</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Как часто делаются revert/hotfix</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">log</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--oneline</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--since=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">1 year ago</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">grep</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-iE</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">revert|hotfix|emergency|rollback</span><span style="color: #ECEFF4">&#39;</span></span></code></pre></div>



<p class="wp-block-paragraph" id="git-4"><strong>Изменить удаленный репозиторий</strong><br>Проект склонирован через https, нужно изменить на  ssh</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly># текущее состояние
git remote -v
#origin https://gitlab.com/my-username/my-project.git (fetch)
#origin https://gitlab.com/my-username/my-project.git (push)


# замена
git remote set-url origin git@gitlab.com:my-username/my-project.git

# проверка
git remote -v
# origin git@gitlab.com:my-username/my-project.git (fetch)
# origin git@gitlab.com:my-username/my-project.git (push)
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88"># текущее состояние</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">remote</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-v</span></span>
<span class="line"><span style="color: #616E88">#origin https://gitlab.com/my-username/my-project.git (fetch)</span></span>
<span class="line"><span style="color: #616E88">#origin https://gitlab.com/my-username/my-project.git (push)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># замена</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">remote</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">set-url</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">origin</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">git@gitlab.com:my-username/my-project.git</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># проверка</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">remote</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-v</span></span>
<span class="line"><span style="color: #616E88"># origin git@gitlab.com:my-username/my-project.git (fetch)</span></span>
<span class="line"><span style="color: #616E88"># origin git@gitlab.com:my-username/my-project.git (push)</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph" id="git-5"><strong>Тэги</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly># просмотреть список тэгов
git tag

# посмотреть информацию по конкретному тэгу
git show 1.2.4

# добавить тэг
git tag -a v1.0 -m "version 1.0"

# тэги по умолчанию не пушатся в удаленный репозиторий. 
# запушить конкретный тэг 
git push origin v1.1

# запушить все тэги 
git push origin --tags

# запушить коммит с новым тэгом 
git push --follow-tags

# можно тэгом помечать ранее сделанный коммит.
# смотрим лог коммитов с хэшами: 
git log --pretty=oneline

# берем первые 7 символов хэша нужного коммита и ставим тэг
git tag -a v1.0 -m "release v1.0" 700b806</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88"># просмотреть список тэгов</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">tag</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># посмотреть информацию по конкретному тэгу</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">show</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1.2</span><span style="color: #A3BE8C">.4</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># добавить тэг</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">tag</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-a</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">v1.0</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-m</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">version 1.0</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># тэги по умолчанию не пушатся в удаленный репозиторий. </span></span>
<span class="line"><span style="color: #616E88"># запушить конкретный тэг </span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">push</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">origin</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">v1.1</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># запушить все тэги </span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">push</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">origin</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--tags</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># запушить коммит с новым тэгом </span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">push</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--follow-tags</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># можно тэгом помечать ранее сделанный коммит.</span></span>
<span class="line"><span style="color: #616E88"># смотрим лог коммитов с хэшами: </span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">log</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--pretty=oneline</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># берем первые 7 символов хэша нужного коммита и ставим тэг</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">tag</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-a</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">v1.0</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-m</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">release v1.0</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">700</span><span style="color: #A3BE8C">b806</span></span></code></pre></div>



<p class="wp-block-paragraph" id="git-6"><strong>История изменений</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly># история коммитов в виде дерева
git log --pretty=oneline --graph

# вывод информации по изменениям, которые внесли коммиты (список файлов)
git log --stat

# изменения в последних 2-х коммитах
git log -p -2 

# не показывать коммиты слияния
git log --no-merges 

# +фильтр по автору
git log --stat --no-merges --author=johndoe

git log -- path/to/file

# изменения в конкретном файле
# красный - что было
# зеленый - что стало 
git log -p --no-merges --author=johndoe -- resources/views/en/page.blade.php

# изменения в последнем коммите
git log -p -1 -- resources/views/en/page.blade.php

# или
git diff branch1 branch2 

# какие есть коммиты в ветке develop, которых нет в main
# можно выполнять как в develop, так и в main
git log develop --not main 

# какие изменения будут внесены из develop в main
git log develop -p main

# кто и когда делал изменения каждой строки файла
git blame resources/views/home.blade.php</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88"># история коммитов в виде дерева</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">log</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--pretty=oneline</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--graph</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># вывод информации по изменениям, которые внесли коммиты (список файлов)</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">log</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--stat</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># изменения в последних 2-х коммитах</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">log</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-2</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># не показывать коммиты слияния</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">log</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--no-merges</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># +фильтр по автору</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">log</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--stat</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--no-merges</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--author=johndoe</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">log</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">path/to/file</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># изменения в конкретном файле</span></span>
<span class="line"><span style="color: #616E88"># красный - что было</span></span>
<span class="line"><span style="color: #616E88"># зеленый - что стало </span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">log</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--no-merges</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--author=johndoe</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">resources/views/en/page.blade.php</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># изменения в последнем коммите</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">log</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-1</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">resources/views/en/page.blade.php</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># или</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">diff</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">branch1</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">branch2</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># какие есть коммиты в ветке develop, которых нет в main</span></span>
<span class="line"><span style="color: #616E88"># можно выполнять как в develop, так и в main</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">log</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">develop</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--not</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">main</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># какие изменения будут внесены из develop в main</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">log</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">develop</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">main</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># кто и когда делал изменения каждой строки файла</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">blame</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">resources/views/home.blade.php</span></span></code></pre></div>



<p class="wp-block-paragraph" id="git-7"><strong>Переименование ветки</strong></p>



<p class="wp-block-paragraph">В локальном репозитории есть ветка, в ней коммиты, ветка запушена в origin. Необходимо переименовать и локальную и удаленную ветки. </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly># переименовываем локальную ветку
git branch -m новое-имя

# пушим переименованную ветку
git push origin -u новое-имя

# удаляем старую ветку из origin
git push origin --delete старое-имя</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88"># переименовываем локальную ветку</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">branch</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-m</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">новое-имя</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># пушим переименованную ветку</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">push</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">origin</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">новое-имя</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># удаляем старую ветку из origin</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">push</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">origin</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--delete</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">старое-имя</span></span></code></pre></div>



<p class="wp-block-paragraph"></p>
<p>Сообщение <a href="https://mcodex.ru/git-commands/">Полезные команды Git</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Использование MySQL в контейнере Docker</title>
		<link>https://mcodex.ru/mysql-in-docker/</link>
		
		<dc:creator><![CDATA[mcodex]]></dc:creator>
		<pubDate>Sun, 17 May 2026 10:43:47 +0000</pubDate>
				<category><![CDATA[DevOps]]></category>
		<guid isPermaLink="false">https://mcodex.ru/?p=220</guid>

					<description><![CDATA[<p>Пошаговая инструкция по разворачиванию СУБД MySQL в docker-контейнере и подключении к нему проектов.</p>
<p>Сообщение <a href="https://mcodex.ru/mysql-in-docker/">Использование MySQL в контейнере Docker</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">1. Установка Docker</h2>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>curl -fsSL https://get.docker.com | sh

# добавляем текущего пользователя в группу docker
sudo usermod -aG docker $USER
newgrp docker</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">curl</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-fsSL</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">https://get.docker.com</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sh</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># добавляем текущего пользователя в группу docker</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">usermod</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-aG</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$USER</span></span>
<span class="line"><span style="color: #88C0D0">newgrp</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">docker</span></span></code></pre></div>



<h2 class="wp-block-heading">2. Файл docker-compose.yaml</h2>



<p class="wp-block-paragraph">Создаем папку, например, /docker-containers/mysql, в ней файл docker-compose.yaml</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>services:
  mysql:
    image: mysql:8.0
    container_name: mysql_container
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: your_root_password
      MYSQL_DATABASE: my_project_db
      MYSQL_USER: my_project_user
      MYSQL_PASSWORD: my_project_password
    volumes:
      - mysql_data:/var/lib/mysql
      - ./conf.d:/etc/mysql/conf.d
    ports:
      - "127.0.0.1:3306:3306"

volumes:
  mysql_data:</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #8FBCBB">services</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">mysql</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql:8.0</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">container_name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql_container</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">restart</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">always</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">environment</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">MYSQL_ROOT_PASSWORD</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">your_root_password</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">MYSQL_DATABASE</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project_db</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">MYSQL_USER</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project_user</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">MYSQL_PASSWORD</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project_password</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">volumes</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql_data:/var/lib/mysql</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">./conf.d:/etc/mysql/conf.d</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">ports</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">127.0.0.1:3306:3306</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">volumes</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">mysql_data</span><span style="color: #ECEFF4">:</span></span></code></pre></div>



<p class="wp-block-paragraph">Используем образ MySQL фиксированной версии, что бы в случае пересоздания контейнера не столкнуться с проблемами несовместимости, которые потенциально могут быть, если использовать образ latest.</p>



<p class="wp-block-paragraph">Пароли и имена придумываем свои, пароли долджны быть сложные,  MYSQL_ROOT_PASSWORD используется при инициализации контейнера, в дальнейшем он будет нужен для подключения к MySQL от имени пользователя root, остальные данные будут нужны при подключении проекта.</p>



<h2 class="wp-block-heading">3. Запускаем контейнер</h2>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>cd /docker-containers/mysql
docker compose up -d

# Проверяем, что контейнер запущен
docker ps
docker logs mysql_container

# включаем автозапуск docker после перезагрузки VPS
sudo systemctl enable docker</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">cd</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">/docker-containers/mysql</span></span>
<span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">compose</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">up</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-d</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Проверяем, что контейнер запущен</span></span>
<span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ps</span></span>
<span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">logs</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql_container</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># включаем автозапуск docker после перезагрузки VPS</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">systemctl</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">enable</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">docker</span></span></code></pre></div>



<p class="wp-block-paragraph">При первом запуске контейнера Docker-образ MySQL сам создаёт базу и пользователя, а так-же дает пользователю все права на эту базу.</p>



<h2 class="wp-block-heading">4. Импорт дампа в контейнер</h2>



<p class="wp-block-paragraph">На данном шаге в нашем контейнере существует пустая база данных <strong>my_project_db</strong>, в которую можно, при необходимости, загружить дамп проекта. Для этого выполняем mysql внутри контейнера.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker exec -i mysql_container mysql -u my_project_user -p'my_project_password' my_project_db &lt; /path/to/dump/backup-dump.sql</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-i</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql_container</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project_user</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my_project_password</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project_db</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">/path/to/dump/backup-dump.sql</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>Важно:</strong> это работает только при <strong>первом</strong> запуске, когда volume ещё пустой. Если контейнер уже запускался и данные есть — переменные игнорируются, база уже существует.</p>



<p class="wp-block-paragraph">Проверям, что импорт прошел успешно.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker exec -it mysql_container mysql -u my_project_user -p'my_project_password' my_project_db -e "SHOW TABLES;"</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-it</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql_container</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project_user</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my_project_password</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project_db</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-e</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">SHOW TABLES;</span><span style="color: #ECEFF4">&quot;</span></span></code></pre></div>



<p class="wp-block-paragraph">В выводе должен быть список таблиц в базе данных  <strong>my_project_db</strong></p>



<h2 class="wp-block-heading">5. Подключаем проект</h2>



<p class="wp-block-paragraph">Теперь нужно подключить проект к контейнеру с базой данных. Проекты могут использовать разные фреймворки и CMS.</p>



<p class="wp-block-paragraph"><strong>Вариант А. Проект на WordPress. </strong></p>



<p class="wp-block-paragraph">Переходим в папку проекта, открываем файл wp-config.php, меняем значения констант.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>define( 'DB_NAME', 'my_project_db' );
define( 'DB_USER', 'my_project_user' );
define( 'DB_PASSWORD', 'my_project_password' );
define( 'DB_HOST', '127.0.0.1:3306' );</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">define(</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">DB_NAME</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my_project_db</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> )</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #88C0D0">define(</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">DB_USER</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my_project_user</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> )</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #88C0D0">define(</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">DB_PASSWORD</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my_project_password</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> )</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #88C0D0">define(</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">DB_HOST</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">127.0.0.1:3306</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> )</span><span style="color: #81A1C1">;</span></span></code></pre></div>



<p class="wp-block-paragraph">Проверяем, что сайт открывается, нет ошибки подключения в базе данных. <br>После подключение проекта к контейнеру, СУБД на хостинге больше не нужна, можно удалить.</p>



<h2 class="wp-block-heading">6. Добавление еще одной базы данных</h2>



<p class="wp-block-paragraph">Сейчас в контейнере создана только одна база данных для проекта my_project. Предположим, на этом же VPS расположен еще один проект, my_project2. Создадим в этом же контейнере еще одну БД для этого проекта. Для этого заходим в контейнер как пользователь root (пароль берем из MYSQL_ROOT_PASSWORD файла docker-compose.yaml ) и создаем нового пользователя и базу данных.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly># Зайди в контейнер
docker exec -it mysql_container mysql -u root -p

# Внутри MySQL создай всё для второго сайта
CREATE DATABASE my_project2_db;
CREATE USER 'my_project2_user'@'%' IDENTIFIED BY 'my_project2_password';
GRANT ALL PRIVILEGES ON my_project2_db.* TO 'my_project2_user'@'%';
QUIT;</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88"># Зайди в контейнер</span></span>
<span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-it</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql_container</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">root</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Внутри MySQL создай всё для второго сайта</span></span>
<span class="line"><span style="color: #88C0D0">CREATE</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">DATABASE</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project2_db</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #88C0D0">CREATE</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">USER</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my_project2_user</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">@</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">%</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">IDENTIFIED</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">BY</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my_project2_password</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #88C0D0">GRANT</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ALL</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">PRIVILEGES</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ON</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project2_db.</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">TO</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my_project2_user</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">@</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">%</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #88C0D0">QUIT</span><span style="color: #81A1C1">;</span></span></code></pre></div>



<p class="wp-block-paragraph">Подключение во второй проект аналогично п5.</p>



<h2 class="wp-block-heading">7. Хранение данных</h2>



<p class="wp-block-paragraph">Разбираемся, где лежат файлы MySQL на хост-машине. <br>В docker-compose.yaml указан именованный том <strong>mysql_data</strong>.  По сути это папка на хосте, в которой лежат файлы MySQL. Так же указано, что на нее маппируется папка /var/lib/mysql в контейнере (стандартная папка с данными MySQL).  Что бы узнать точное имя тома (папки) можно выполнить команду: </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker volume ls

# Вывод
# DRIVER    VOLUME NAME
# local     mysql-docker_mysql_data
# local     mysql_mysql_data
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">volume</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ls</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Вывод</span></span>
<span class="line"><span style="color: #616E88"># DRIVER    VOLUME NAME</span></span>
<span class="line"><span style="color: #616E88"># local     mysql-docker_mysql_data</span></span>
<span class="line"><span style="color: #616E88"># local     mysql_mysql_data</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Видно, что точное имя тома <strong>mysql_mysql_data</strong>, так как к имени, указанному в опции volume добавляется имя папки, в которой лежит docker-compose.yaml, которое считается префиксом проекта.</p>



<p class="wp-block-paragraph">Проверяем, где находится данный том</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker volume inspect mysql_mysql_data

# Вывод
# ....
# "Mountpoint": "/var/lib/docker/volumes/mysql_mysql_data/_data",
# "Name": "mysql_mysql_data",
# ....
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">volume</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">inspect</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql_mysql_data</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Вывод</span></span>
<span class="line"><span style="color: #616E88"># ....</span></span>
<span class="line"><span style="color: #616E88"># &quot;Mountpoint&quot;: &quot;/var/lib/docker/volumes/mysql_mysql_data/_data&quot;,</span></span>
<span class="line"><span style="color: #616E88"># &quot;Name&quot;: &quot;mysql_mysql_data&quot;,</span></span>
<span class="line"><span style="color: #616E88"># ....</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Mountpoint &#8211; это и есть путь к папке тома на хост-машине. <br>Том не зависит от контейнера, поэтому при остановке, перезапуске, удалении контейнера данные в базе данных будут сохранены.</p>



<p class="wp-block-paragraph"><strong>Сделать дамп одной базы данных</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker exec mysql_container mysqldump \
  -u my_project_user -p'my_project_password' \
  my_project_db > my_project_db.sql</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql_container</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysqldump</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project_user</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my_project_password</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #A3BE8C">my_project_db</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project_db.sql</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>Сделать дамп всех баз данных</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker exec mysql_container mysqldump \
  -u root -p'your_root_password' \
  --all-databases > backup_all.sql</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql_container</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysqldump</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">root</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">your_root_password</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #A3BE8C">--all-databases</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">backup_all.sql</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>Восстановление из дампа</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker exec -i mysql_container mysql \
  -u my_project_user -p'my_project_password' \
  my_project_db &lt; backup.sql</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-i</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql_container</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project_user</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my_project_password</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #A3BE8C">my_project_db</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">backup.sql</span></span></code></pre></div>



<h2 class="wp-block-heading">8. Полезные команды docker</h2>



<p class="wp-block-paragraph">Напоминание: <br>1.  mysql_container &#8211; это не зарезервированное слово, это имя контейнера, заданное в файле docker-compose.yaml (см. п2), может быть любым.<br>2. команда &#8216;docker&#8217; может выполнятся из любой папки. <br>3. команда &#8216;docker compose&#8217; выполняется из папки, в которой находится файл docker-compose.yaml (или указывается путь к файлу через флаг -f)</p>



<figure class="wp-block-table is-style-stripes has-small-font-size"><table class="has-fixed-layout"><tbody><tr><td><strong>Действие</strong></td><td><strong>Команда</strong></td></tr><tr><td>Запустить контейнер в режиме демона</td><td>docker compose up -d</td></tr><tr><td>Посмотреть все запущенные контейнеры</td><td>docker ps</td></tr><tr><td>Остановить все запущенные контейнеры</td><td>docker stop $(docker ps -a -q)</td></tr><tr><td>Удалить все остановленные контейнеры</td><td>docker rm $(docker ps -a -q)</td></tr><tr><td>Остановить контейнер (данные сохраняются)</td><td>docker compose stop</td></tr><tr><td>Остановить и удалить контейнер (данные сохраняются)</td><td>docker compose down</td></tr><tr><td>Перезапустить контейнер (данные сохраняются)</td><td>docker compose restart</td></tr><tr><td>Удалить контейнер mysql_container (данные сохраняются)</td><td>docker rm mysql_container</td></tr><tr><td>Удалить контейнер вместе с данными </td><td>docker compose down -v</td></tr><tr><td>Посмотреть логи контейнера mysql_container</td><td>docker logs -f mysql_container</td></tr><tr><td>Войти в MySQL как root в контейнере mysql_container </td><td>docker exec -it mysql_container mysql -u root -p</td></tr><tr><td>Сделать дамп</td><td>docker exec mysql_container mysqldump -u root -p my_project_db &gt; my_project_db.sql</td></tr><tr><td>Зайти в контейнер mysql_container </td><td>docker exec -it mysql_container  /bin/bash</td></tr><tr><td>Удаление всех контейнеров, образов, сетей</td><td>docker system prune</td></tr><tr><td>Удаление всех  контейнеров, образов, сетей, томов</td><td>docker system prune &#8211;volumes</td></tr><tr><td></td><td></td></tr></tbody></table></figure>



<p class="wp-block-paragraph"></p>
<p>Сообщение <a href="https://mcodex.ru/mysql-in-docker/">Использование MySQL в контейнере Docker</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Zero-downtime деплой через CICD Gitlab</title>
		<link>https://mcodex.ru/deploy-cicd-gitlab/</link>
		
		<dc:creator><![CDATA[mcodex]]></dc:creator>
		<pubDate>Wed, 13 May 2026 14:53:05 +0000</pubDate>
				<category><![CDATA[DevOps]]></category>
		<guid isPermaLink="false">https://mcodex.ru/?p=154</guid>

					<description><![CDATA[<p>Пошаговая инструкция по настройке деплоя проектов на VPS через Gitlab CICD</p>
<p>Сообщение <a href="https://mcodex.ru/deploy-cicd-gitlab/">Zero-downtime деплой через CICD Gitlab</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><strong>Содержание</strong></p>



<ul class="wp-block-list">
<li><a href="#description" type="internal" id="#description">Описание задачи и требования к проекту</a></li>



<li><a href="#step-1" type="internal" id="#step-1">Начальная настройка проекта</a></li>



<li><a href="#step-2" type="internal" id="#step-2">Пользователи для деплоя</a></li>



<li><a href="#step-3" type="internal" id="#step-3">SSH-ключи</a></li>



<li><a href="#step-4" type="internal" id="#step-4">Переменные Gitlab</a></li>



<li><a href="#step-5" type="internal" id="#step-5">Структура проекта для CICD</a></li>



<li><a href="#step-6" type="internal" id="#step-6">Переключение Nginx на новый проект</a></li>



<li><a href="#step-7" type="internal" id="#step-7">Первый деплой через CICD</a></li>



<li><a href="#step-8" type="internal" id="#step-8">Откат</a></li>
</ul>



<h2 id="description" class="wp-block-heading"><strong>Описание задачи и требования к проекту</strong></h2>



<p class="wp-block-paragraph">Проект разрабатыватся на локальной машине, есть 2 сервера, dev и prodcution.<br>Стэк одинаковый: VPS Ubuntu/Nginx/PHP/MySQL/Laravel/Supervisor</p>



<p class="wp-block-paragraph">Шаги в инструкции выполняются на обеих серверах. В результате получаем 2 развернутых версии проекта:<br>на dev-сервере (ветка develop) и production (ветка main).</p>



<p class="wp-block-paragraph">После применения инструкции все дальнейшие загрузки обновлений происходят без ручного вмешательства на серверах, на dev-сервере &#8211; после пуша в ветку develop, на production &#8211; после пуша (или PR) в main  и клика на кнопке деплоя (опциональный предохранитель).</p>



<p class="wp-block-paragraph">Перед внедрением CICD, необходимо проверить следующие пункты:</p>



<ol class="wp-block-list">
<li>На серверах есть полномочия sudo</li>



<li>В проекте есть тесты</li>



<li>В проекте есть публично доступный роут<code> GET /health/ready</code>, возвращает код 200.</li>



<li> Перед пушем в gitlab прогоняем тесты, линтер, pint.</li>
</ol>



<h2 id="step-1" class="wp-block-heading"><strong>Шаг 1. Начальная настройка проекта</strong></h2>



<p class="wp-block-paragraph">Разворачиваем проекты на серверах из репозитория Gitlab вручную, без CICD, настраиваем Nginx, подключаем СУБД и т.д. Проверяем, что все работает.<br>Предположим, проект развернут в папке <strong><code>/var/www/my-project.com/classic-deploy</code></strong></p>



<p class="wp-block-paragraph">Сразу решаем, в какой папке будет расположен проект, разворачиваемый через CICD.<br>Допустим, он будет в папке <strong><code>/var/www/my-project.com/cicd-deploy</code></strong></p>



<h2 id="step-2" class="wp-block-heading"><strong>Шаг 2. Создаем пользователя для деплоя</strong></h2>



<p class="wp-block-paragraph">2.1 На обеих серверах добавляем пользователя <strong>deployer</strong>. Через него Gitlab будет делать деплой.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>sudo adduser deployer
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">adduser</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">deployer</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">2.2 Добавляем пользователя <code>deployer </code>в группу <code>www-data</code></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>sudo usermod -aG www-data deployer</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">usermod</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">aG</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">www</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">deployer</span></span></code></pre></div>



<p class="wp-block-paragraph">Проверяем, что добавлен в группу</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>groups deployer</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">groups</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">deployer</span></span></code></pre></div>



<p class="wp-block-paragraph">в списке должна быть<code> <strong>www-data</strong></code></p>



<p class="wp-block-paragraph">2.3 Добавляем возможность запуска от имени deployer определенных команд без пароля.<br> Для этого создаем файл конфига:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>sudo visudo -f /etc/sudoers.d/deploy</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">visudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">f</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">etc</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">sudoers</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">d</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">deploy</span></span></code></pre></div>



<p class="wp-block-paragraph">Добавляем в него строку (версию PHP при необходимости изменить):</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>deployer ALL=(ALL) NOPASSWD: /usr/bin/setfacl, /usr/bin/supervisorctl, /usr/bin/systemctl reload php8.5-fpm</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ALL</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ALL</span><span style="color: #D8DEE9FF">) NOPASSWD</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">usr</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">bin</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">setfacl</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">usr</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">bin</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">supervisorctl</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">usr</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">bin</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">systemctl</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">reload</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">php8</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">5</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">fpm</span></span></code></pre></div>



<p class="wp-block-paragraph">Перед добавлением можно проверить, где находятся файлы</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>which supervisorctl
which systemctl
which setfacl</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">which</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">supervisorctl</span></span>
<span class="line"><span style="color: #D8DEE9">which</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">systemctl</span></span>
<span class="line"><span style="color: #D8DEE9">which</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">setfacl</span></span></code></pre></div>



<p class="wp-block-paragraph">Проверяем валидность конфига файлов s<code>udoers</code></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>visudo -c</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF">visudo </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">c</span></span></code></pre></div>



<p class="wp-block-paragraph">Ожидаемый вывод</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>/etc/sudoers: parsed OK
/etc/sudoers.d/README: parsed OK
/etc/sudoers.d/deploy: parsed OK</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">etc</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">sudoers</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> parsed OK</span></span>
<span class="line"><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">etc</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">sudoers</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">d</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">README</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> parsed OK</span></span>
<span class="line"><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">etc</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">sudoers</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">d</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">deploy</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> parsed OK</span></span></code></pre></div>



<h2 id="step-3" class="wp-block-heading"><strong>Шаг 3. Настраиваем SSH-ключи</strong></h2>



<h3 class="wp-block-heading">3.1 Создаем ключ</h3>



<p class="wp-block-paragraph">Логинимся на сервер как пользователь <code>deployer</code></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>ssh deployer@ip-address-here</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">ssh</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">deployer</span><span style="color: #D8DEE9FF">@</span><span style="color: #D8DEE9">ip</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">address</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">here</span></span></code></pre></div>



<p class="wp-block-paragraph">Создаем новый ключ, без ключевой фразы, для дев-сервера используем префикс dev</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>ssh-keygen -t ed25519 -C "gitlab-deploy-prod"</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">ssh</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">keygen</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ed25519</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">C</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">gitlab-deploy-prod</span><span style="color: #ECEFF4">&quot;</span></span></code></pre></div>



<p class="wp-block-paragraph">Появляется папка <code>/home/deployer/.ssh</code>, в ней файлы ключа id_ed25519.pub и id_ed25519.<br><strong>Имена файлов ключей лучше использовать дефолтные, иначе може возникнуть ошибка при подключении к Gitlab.</strong></p>



<p class="wp-block-paragraph">Задаем права на ключи</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
chmod 600 ~/.ssh/known_hosts</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF">chmod </span><span style="color: #B48EAD">700</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">~/</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">ssh</span></span>
<span class="line"><span style="color: #D8DEE9FF">chmod </span><span style="color: #B48EAD">600</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">~/</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">ssh</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">authorized_keys</span></span>
<span class="line"><span style="color: #D8DEE9FF">chmod </span><span style="color: #B48EAD">600</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">~/</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">ssh</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">id_ed25519</span></span>
<span class="line"><span style="color: #D8DEE9FF">chmod </span><span style="color: #B48EAD">644</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">~/</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">ssh</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">id_ed25519</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">pub</span></span>
<span class="line"><span style="color: #D8DEE9FF">chmod </span><span style="color: #B48EAD">600</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">~/</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">ssh</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">known_hosts</span></span></code></pre></div>



<h3 class="wp-block-heading">3.2 Добавляем публичный ключ в authorized_keys</h3>



<p class="wp-block-paragraph">Сначала проверяем, какой файл SSH-сервер реально читает для пользователя <code>deployer</code></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>sudo sshd -T -C user=deployer | grep -Ei 'authorizedkeysfile|authorizedkeyscommand|pubkeyauth|allowusers|allowgroups|denyusers|denygroups'</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF">sudo sshd </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">T </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">C user</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF">deployer </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> grep </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">Ei </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">authorizedkeysfile|authorizedkeyscommand|pubkeyauth|allowusers|allowgroups|denyusers|denygroups</span><span style="color: #ECEFF4">&#39;</span></span></code></pre></div>



<p class="wp-block-paragraph"> Получаем вывод </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>pubkeyauthentication yes
authorizedkeyscommand none
authorizedkeyscommanduser none
authorizedkeysfile .ssh/authorized_keys_123
pubkeyauthoptions none</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF">pubkeyauthentication </span><span style="color: #81A1C1">yes</span></span>
<span class="line"><span style="color: #D8DEE9FF">authorizedkeyscommand none</span></span>
<span class="line"><span style="color: #D8DEE9FF">authorizedkeyscommanduser none</span></span>
<span class="line"><span style="color: #D8DEE9FF">authorizedkeysfile </span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">ssh</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">authorized_keys_123</span></span>
<span class="line"><span style="color: #D8DEE9FF">pubkeyauthoptions none</span></span></code></pre></div>



<p class="wp-block-paragraph">Смотрим значение <code>authorizedkeysfile</code>. Это файл в папке<code> /home/deployer/.ssh</code><br>Может быть как дефолтным <code>authorized_keys</code>, так и переопределенным, как в примере выше</p>



<p class="wp-block-paragraph">Добавляем ключ deployer&#8217;a:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys_123</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">cat</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">~/</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">ssh</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">id_ed25519</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">pub</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&gt;&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">~/</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">ssh</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">authorized_keys_123</span></span></code></pre></div>



<p class="wp-block-paragraph">Содержимое файла публичного ключа добавляем в ключи в репозитории проекта на Gitlab<br><code>Settings->Reposirory->Deploy keys->Add new key</code></p>



<p class="wp-block-paragraph">В названии ключа указываем сервер и пользователя, что бы не путаться, например, &#8220;Production deployer&#8221; </p>



<pre class="wp-block-verse">После добавления найти ключ в вкладке "Privately accessible deploy keys" и нажать Enable. <br>Ключ появится во вкладке "Enabled deploy keys"</pre>



<p class="wp-block-paragraph">Секретный ключ кодируем в base64</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>base64 -w 0 ~/.ssh/id_ed25519</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">base64</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">w</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">~/</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">ssh</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">id_ed25519</span></span></code></pre></div>



<p class="wp-block-paragraph">Полученный вывод пока копируем в текстовый файл, он понадобится в следующем шаге.</p>



<p class="wp-block-paragraph">Проверяем, что ключ работает. Для этого в произвольной папке клонируем репозиторий.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>mkdir ~/test
cd ~/test
git clone git@gitlab.com:your-project.git</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">mkdir</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">~/</span><span style="color: #D8DEE9">test</span></span>
<span class="line"><span style="color: #D8DEE9">cd</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">~/</span><span style="color: #D8DEE9">test</span></span>
<span class="line"><span style="color: #D8DEE9">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">clone</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">git</span><span style="color: #D8DEE9FF">@</span><span style="color: #D8DEE9">gitlab</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">com</span><span style="color: #D8DEE9FF">:</span><span style="color: #D8DEE9">your</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">git</span></span></code></pre></div>



<p class="wp-block-paragraph">После проверки удаляем тестовую папку, закрываем соединение под <code>deployer</code>.</p>



<h2 id="step-4" class="wp-block-heading"><strong>Шаг 4. Задаем переменные Gitlab</strong></h2>



<p class="wp-block-paragraph">В Gitlab, в папке проекта открываем <code>Settings->CICD->Variables</code><br>Создаем переменные: <br><strong>PROD_HOST </strong>&#8211; IP адрес, например, 123.1.123.12<br><strong>PROD_BASE</strong> &#8211; путь к папке cicd-проекта,<code> /var/www/my-project.com/cicd-deploy</code><br><strong>PROD_APP_URL</strong> &#8211; публичный URL проекта, для запуска health check, например,<code> https://my-project.com.</code> <br><strong>PROD_USER</strong> &#8211; deployer<br><strong>PROD_SSH_PRIVATE_KEY_B64</strong> &#8211; приватный ключ в формате base64 из предыдущего шага<br>Тип переменной &#8211; variable (default), visibility: visible, protect variable &#8211; выключено<br>Для дев-севера аналогично, но с префиксом <strong>DEV_</strong></p>



<p class="wp-block-paragraph">В результате в репозитории есть набор переменных</p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="504" src="https://mcodex.ru/wp-content/uploads/2026/05/gitlab-vars-1024x504.png" alt="переменные Gitlab" class="wp-image-167" srcset="https://mcodex.ru/wp-content/uploads/2026/05/gitlab-vars-1024x504.png 1024w, https://mcodex.ru/wp-content/uploads/2026/05/gitlab-vars-300x148.png 300w, https://mcodex.ru/wp-content/uploads/2026/05/gitlab-vars-768x378.png 768w, https://mcodex.ru/wp-content/uploads/2026/05/gitlab-vars-1536x755.png 1536w, https://mcodex.ru/wp-content/uploads/2026/05/gitlab-vars.png 1570w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<div style="height:15px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph"><strong>Результат на данном этапе:  </strong><br>Проект работает по прежнему из папки classic-deploy, на сервере добавлен пользователь deployer.</p>



<h2 id="step-5" class="wp-block-heading"><strong>Шаг 5. Создаем структуру проекта для CICD</strong></h2>



<p class="wp-block-paragraph">Допустим, проект, развернутый через CICD будет расположен в папке<code> /var/www/my-project.com/cicd-deploy</code></p>



<p class="wp-block-paragraph">В этом случае структура downtime-zero проекта будет следующей</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>/var/www/my-porject.com/cicd-deploy/
  ├── current -> releases/20260501-1430-abc1234 ← симлинк на активный релиз
  ├── releases/
  │ ├── 20260430-1731-2b5cc17/
  │ ├── 20260501-0900-69025b4/
  │ └── 20260501-1430-abc1234/ ← текущий
  └── shared/
    ├── .env
    └── storage/</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">/var</span><span style="color: #D8DEE9FF">/</span><span style="color: #D8DEE9">www</span><span style="color: #D8DEE9FF">/my-porject.com/cicd-deploy/</span></span>
<span class="line"><span style="color: #D8DEE9FF">  ├── </span><span style="color: #D8DEE9">current</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">releases</span><span style="color: #81A1C1">/</span><span style="color: #B48EAD">20260501</span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">1430</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">abc1234</span><span style="color: #D8DEE9FF"> ← </span><span style="color: #D8DEE9">симлинк</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">на</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">активный</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">релиз</span></span>
<span class="line"><span style="color: #D8DEE9FF">  ├── </span><span style="color: #D8DEE9">releases</span><span style="color: #81A1C1">/</span></span>
<span class="line"><span style="color: #D8DEE9FF">  │ ├── </span><span style="color: #B48EAD">20260430</span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">1731</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">2</span><span style="color: #D8DEE9">b5cc17</span><span style="color: #81A1C1">/</span></span>
<span class="line"><span style="color: #D8DEE9FF">  │ ├── </span><span style="color: #B48EAD">20260501</span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">0900</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">69025</span><span style="color: #D8DEE9">b4</span><span style="color: #81A1C1">/</span></span>
<span class="line"><span style="color: #D8DEE9FF">  │ └── </span><span style="color: #B48EAD">20260501</span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">1430</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">abc1234</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF"> ← </span><span style="color: #D8DEE9">текущий</span></span>
<span class="line"><span style="color: #D8DEE9FF">  └── </span><span style="color: #D8DEE9">shared</span><span style="color: #81A1C1">/</span></span>
<span class="line"><span style="color: #D8DEE9FF">    ├── </span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">env</span></span>
<span class="line"><span style="color: #D8DEE9FF">    └── </span><span style="color: #D8DEE9">storage</span><span style="color: #81A1C1">/</span></span></code></pre></div>



<p class="wp-block-paragraph">5.1 Делаем первый деплой вручную.</p>



<p class="wp-block-paragraph">Логинимся под <strong>root</strong>, создаем папку ~/deploy, в нее кладем файл<strong> first_deploy.sh</strong></p>



<p class="wp-block-paragraph">Заменить: <br>BASE &#8211; путь к новой папке проекта <br>OLD_PATH &#8211; текущий проект<br>REPO &#8211; репозиторий Gitlab</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>#!/usr/bin/env bash
umask 0002
set -euo pipefail

BASE=/var/www/my-project.com/cicd-deploy
OLD_PATH=/var/www/my-porject.com/classic-deploy
SHARED=$BASE/shared
TS=$(date +%Y%m%d-%H%M)
REL=$BASE/releases/${TS}-manual
REPO=git@gitlab.com:your-project-repo.git

mkdir -p $BASE/releases
mkdir -p $BASE/shared/storage/{app/public,framework/cache/data,framework/sessions,framework/testing,framework/views,logs}
sudo chown -R deployer:www-data $BASE
sudo find $BASE -type d -exec chmod 2775 {} \;
sudo find $BASE -type f -exec chmod 0664 {} \;

cp -a $OLD_PATH/.env $BASE/shared/.env
cp -a $OLD_PATH/storage/. $BASE/shared/storage/
sudo chown -R deployer:www-data $BASE/shared
sudo find $BASE/shared -type d -exec chmod 2775 {} \;
sudo find $BASE/shared -type f -exec chmod 0664 {} \;
sudo chmod 0640 $BASE/shared/.env

# Default ACL, чтобы файлы/директории, создаваемые php-fpm (www-data) уже после
# деплоя (sources/*, логи и т.п.), тоже оставались доступны deployer и наоборот —
# setgid/umask этого не гарантируют для новых записей.
sudo setfacl -R -m u:deployer:rwx,g:www-data:rwx,o::--- "$BASE/shared/storage"
sudo setfacl -R -d -m u:deployer:rwx,g:www-data:rwx,o::--- "$BASE/shared/storage"

sudo -u deployer git clone --depth 1 --branch develop "$REPO" "$REL"

sudo -u deployer ln -sfn "$SHARED/.env" "$REL/.env"
sudo -u deployer rm -rf "$REL/storage"
sudo -u deployer ln -sfn "$SHARED/storage" "$REL/storage"

# на проде добавить --no-dev
sudo -u deployer bash -c "cd '$REL' &amp;&amp; composer install --optimize-autoloader --no-interaction"
sudo -u deployer php "$REL/artisan" storage:link
sudo -u deployer php "$REL/artisan" config:cache
sudo -u deployer php "$REL/artisan" route:cache
sudo -u deployer php "$REL/artisan" view:cache
sudo -u deployer php "$REL/artisan" event:cache
sudo -u deployer php "$REL/artisan" migrate --force

sudo rm -rf "$BASE/current.new"
sudo -u deployer ln -sfn "$REL" "$BASE/current.new"
sudo -u deployer mv -Tf "$BASE/current.new" "$BASE/current"
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">#!/usr/bin/env bash</span></span>
<span class="line"><span style="color: #88C0D0">umask</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0002</span></span>
<span class="line"><span style="color: #88C0D0">set</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-euo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">pipefail</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">BASE</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">/var/www/my-project.com/cicd-deploy</span></span>
<span class="line"><span style="color: #D8DEE9">OLD_PATH</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">/var/www/my-porject.com/classic-deploy</span></span>
<span class="line"><span style="color: #D8DEE9">SHARED</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/shared</span></span>
<span class="line"><span style="color: #D8DEE9">TS</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">$(</span><span style="color: #88C0D0">date</span><span style="color: #A3BE8C"> +%Y%m%d-%H%M</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9">REL</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/releases/</span><span style="color: #81A1C1">${</span><span style="color: #D8DEE9">TS</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">-manual</span></span>
<span class="line"><span style="color: #D8DEE9">REPO</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">git@gitlab.com:your-project-repo.git</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">mkdir</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/releases</span></span>
<span class="line"><span style="color: #88C0D0">mkdir</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/shared/storage/{app/public,framework/cache/data,framework/sessions,framework/testing,framework/views,logs}</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">chown</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-R</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer:www-data</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">find</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-type</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">d</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">chmod</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2775</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">{}</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\;</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">find</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-type</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">f</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">chmod</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0664</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">{}</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">cp</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-a</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$OLD_PATH</span><span style="color: #A3BE8C">/.env</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/shared/.env</span></span>
<span class="line"><span style="color: #88C0D0">cp</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-a</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$OLD_PATH</span><span style="color: #A3BE8C">/storage/.</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/shared/storage/</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">chown</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-R</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer:www-data</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/shared</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">find</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/shared</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-type</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">d</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">chmod</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2775</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">{}</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\;</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">find</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/shared</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-type</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">f</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">chmod</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0664</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">{}</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\;</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">chmod</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0640</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/shared/.env</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Default ACL, чтобы файлы/директории, создаваемые php-fpm (www-data) уже после</span></span>
<span class="line"><span style="color: #616E88"># деплоя (sources/*, логи и т.п.), тоже оставались доступны deployer и наоборот —</span></span>
<span class="line"><span style="color: #616E88"># setgid/umask этого не гарантируют для новых записей.</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">setfacl</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-R</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-m</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">u:deployer:rwx,g:www-data:rwx,o::---</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/shared/storage</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">setfacl</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-R</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-d</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-m</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">u:deployer:rwx,g:www-data:rwx,o::---</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/shared/storage</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">clone</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--depth</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--branch</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">develop</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REPO</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ln</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-sfn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$SHARED</span><span style="color: #A3BE8C">/.env</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #A3BE8C">/.env</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">rm</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-rf</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #A3BE8C">/storage</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ln</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-sfn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$SHARED</span><span style="color: #A3BE8C">/storage</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #A3BE8C">/storage</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># на проде добавить --no-dev</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">bash</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-c</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">cd &#39;</span><span style="color: #D8DEE9">$REL</span><span style="color: #A3BE8C">&#39; &amp;&amp; composer install --optimize-autoloader --no-interaction</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #A3BE8C">/artisan</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">storage:link</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #A3BE8C">/artisan</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">config:cache</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #A3BE8C">/artisan</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">route:cache</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #A3BE8C">/artisan</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">view:cache</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #A3BE8C">/artisan</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">event:cache</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #A3BE8C">/artisan</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">migrate</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--force</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">rm</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-rf</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/current.new</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ln</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-sfn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/current.new</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mv</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-Tf</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/current.new</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/current</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Что делает скрипт: </p>



<ul class="wp-block-list">
<li>Создает структуру проекта</li>



<li>Копирует shared-файлы из текущего проекта</li>



<li>Создает первый релиз с префиксом manual</li>



<li>Устанавливает права </li>
</ul>



<p class="wp-block-paragraph">Перед запуском проверяем текущую версию CLI PHP, она должна соответствовать требованиям пакетов в composer.lock, иначе возникнет ошибка при установке пакетов.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>php -v</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-v</span></span></code></pre></div>



<p class="wp-block-paragraph">При необходимости, переключаем на нужную версию:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>sudo update-alternatives --set php /usr/bin/php8.5</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">update-alternatives</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--set</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">/usr/bin/php8.5</span></span></code></pre></div>



<p class="wp-block-paragraph">Запускаем скрипт</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>cd ~/deploy
bash first_deploy.sh</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">cd</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">~/deploy</span></span>
<span class="line"><span style="color: #88C0D0">bash</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">first_deploy.sh</span></span></code></pre></div>



<p class="wp-block-paragraph">В результате в папке <code>/var/www/my-project.com/cicd-deploy</code> создается структура ciccd-проекта, но он пока не работает, так как Nginx отправляет запросы на старый проект.</p>



<h2 id="step-6" class="wp-block-heading"><strong>Шаг 6. Переключаем Nginx на новый проект</strong></h2>



<p class="wp-block-paragraph">6.1 В файле конфигурации Nginx меняем путь для root на <br><code>/var/www/my-project.com/cicd-deploy/current/public</code></p>



<p class="wp-block-paragraph">Перезагружаем Nginx </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>nginx -t
systemctl reload nginx </textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">nginx</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-t</span></span>
<span class="line"><span style="color: #88C0D0">systemctl</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">reload</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">nginx</span><span style="color: #D8DEE9FF"> </span></span></code></pre></div>



<p class="wp-block-paragraph">6.2 Меняем конфигурацию supervisor</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>nano /etc/supervisor/conf.d/horizon.conf

## меняем пути  
command=php /var/www/my-project.com/cicd-deploy/current/artisan horizon
directory=/var/www/my-project.com/cicd-deploy/current
stdout_logfile=/var/www/my-project.com/cicd-deploy/shared/storage/logs/horizon.log

supervisorctl reread &amp;&amp; supervisorctl update
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">nano</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">/etc/supervisor/conf.d/horizon.conf</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">## меняем пути  </span></span>
<span class="line"><span style="color: #D8DEE9">command</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">/var/www/my-project.com/cicd-deploy/current/artisan</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">horizon</span></span>
<span class="line"><span style="color: #D8DEE9">directory</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">/var/www/my-project.com/cicd-deploy/current</span></span>
<span class="line"><span style="color: #D8DEE9">stdout_logfile</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">/var/www/my-project.com/cicd-deploy/shared/storage/logs/horizon.log</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">supervisorctl</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">reread</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&amp;&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">supervisorctl</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">update</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph"><strong>6.3 Важно: не забыть проверить и заменить пути в заданиях cron, пути (если есть) в файле .env, в файле конфигураций.</strong></p>



<p class="wp-block-paragraph">Теперь проект работает из новой папки, релиз -manual.</p>



<p class="wp-block-paragraph">Проверяем, что все работает, адрес /health/ready возвращает JSON с 200</p>



<h2 id="step-7" class="wp-block-heading"><strong>Шаг 7. Делаем первый деплой через Gitlab CICD Pipeline.</strong></h2>



<p class="wp-block-paragraph">7.1 На локальном сервере добавляем в корневую проекта файл пайплайна <strong>.gitlab-ci.yaml</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>stages:
  - test
  - deploy

# ──────────────── ТЕСТЫ ────────────────
test:
  stage: test
  image: php:8.5-cli
  cache:
    key:
      files: &#91;composer.lock&#93;
    paths: &#91;vendor/&#93;
  variables:
    DB_CONNECTION: sqlite
    DB_DATABASE: ":memory:"
  before_script:
    - apt-get update -qq &amp;&amp; apt-get install -y -qq git unzip libicu-dev libzip-dev libsqlite3-dev libpng-dev libjpeg-dev libfreetype6-dev libwebp-dev
    - docker-php-ext-configure gd --with-jpeg --with-freetype --with-webp
    - docker-php-ext-install intl pcntl zip pdo_sqlite gd
    - curl -sS https://getcomposer.org/installer | php
    - php composer.phar install --prefer-dist --no-ansi --no-progress
    - cp .env.example .env
    - php artisan key:generate
  script:
    - php artisan test --compact
  rules:
    - if: $CI_COMMIT_BRANCH == "main" # запускаем тест при пуше в main
    - if: $CI_COMMIT_BRANCH == "develop" # запускаем тест при пуше в develop
    - if: $CI_PIPELINE_SOURCE == "merge_request_event" # запускаем тест при PR
# тесты не запускаются при пуше в feature-ветки

# ──────────────── ДЕПЛОЙ НА DEV ────────────────
deploy:dev:
  stage: deploy
  needs: &#91;test&#93;
  image: alpine:latest
  variables:
    KEEP_RELEASES: "5"
    HEALTH_URL: "${DEV_APP_URL}/health/ready"
  script:
    - apk add --no-cache openssh-client curl
    - mkdir -p ~/.ssh &amp;&amp; chmod 700 ~/.ssh
    - echo "$DEV_SSH_PRIVATE_KEY_B64" | base64 -d > ~/.ssh/id_deploy
    - chmod 600 ~/.ssh/id_deploy
    - ssh-keyscan -H "$DEV_HOST" >> ~/.ssh/known_hosts
    - |
      ssh -i ~/.ssh/id_deploy "$DEV_USER@$DEV_HOST" \
        "BASE='$DEV_BASE' SHA='$CI_COMMIT_SHORT_SHA' FULL_SHA='$CI_COMMIT_SHA' REPO='$CI_REPOSITORY_URL' KEEP_RELEASES='$KEEP_RELEASES' bash -se" &lt;&lt;'EOSSH'
        set -euo pipefail
        umask 0002
        TS=$(date +%Y%m%d-%H%M)
        REL="$BASE/releases/${TS}-${SHA}"
        SHARED="$BASE/shared"

        git config --global --add safe.directory "$REL"

        # 1. Клонируем релиз (shallow) от develop
        git clone --depth 1 --branch develop "$REPO" "$REL"
        cd "$REL"
        echo "$FULL_SHA" > RELEASE

        # 2. Гарантируем структуру shared (на случай первого деплоя/чистого shared)
        mkdir -p "$SHARED/storage/app/public" \
                 "$SHARED/storage/framework/cache/data" \
                 "$SHARED/storage/framework/sessions" \
                 "$SHARED/storage/framework/testing" \
                 "$SHARED/storage/framework/views" \
                 "$SHARED/storage/logs"

        # 2.1 Default ACL на shared/storage: файлы/директории, создаваемые php-fpm
        # (www-data) во время работы приложения (sources/*, логи и т.п.), должны
        # оставаться доступны deployer и наоборот — setgid/umask этого не
        # гарантируют для новых записей, поэтому фиксируем через ACL при каждом деплое.
        sudo setfacl -R -m u:deployer:rwx,g:www-data:rwx,o::--- "$SHARED/storage"
        sudo setfacl -R -d -m u:deployer:rwx,g:www-data:rwx,o::--- "$SHARED/storage"

        # 3. Линкуем shared
        ln -sfn "$SHARED/.env" .env
        rm -rf storage &amp;&amp; ln -sfn "$SHARED/storage" storage

        # 3. Установка зависимостей (на новом релизе, не трогая current)
        composer install --optimize-autoloader --no-interaction --no-progress

        # 4. Symlink public/storage -> storage/app/public
        php artisan storage:link

        # 5. Кеши конфигов/роутов/вью — в новом релизе
        php artisan config:cache
        php artisan route:cache
        php artisan view:cache
        php artisan event:cache

        # 5.1 Генерация Swagger-документации (актуализируем api-docs.json в shared/storage)
        php artisan l5-swagger:generate

        # 5. Миграции (один раз на релиз; делаем ДО переключения)
        php artisan migrate --force

        # 6. Атомарное переключение symlink current
        ln -sfn "$REL" "$BASE/current.new"
        mv -Tf "$BASE/current.new" "$BASE/current"

        # 7. Перезапуск Horizon (graceful) — supervisor поднимет его на новом релизе
        php artisan horizon:terminate

        # 8. Reload php-fpm для сброса opcache (graceful, без downtime)
        sudo systemctl reload php8.5-fpm

        # 9. Чистим старые релизы (всего храним KEEP_RELEASES, включая current)
        cd "$BASE/releases" || exit 1

        KEEP_ARGS=(-e "$(basename "$REL")")
        if &#91; -L "$BASE/current" &#93;; then
          KEEP_ARGS+=(-e "$(basename "$(readlink -f "$BASE/current")")")
        fi

        ls -1dt -- */ \
          | sed 's:/$::' \
          | tail -n +"$((KEEP_RELEASES + 1))" \
          | { grep -vx "${KEEP_ARGS&#91;@&#93;}" || true; } \
          | xargs -r rm -rf
      EOSSH
    # 10. Health check после деплоя
    - |
      echo "Health check: $HEALTH_URL"
      for i in 1 2 3 4 5; do
        code=$(curl -s -o /tmp/health.out -w "%{http_code}" "$HEALTH_URL" || echo "000")
        echo "attempt $i: HTTP $code"
        if &#91; "$code" = "200" &#93;; then
          cat /tmp/health.out; echo
          exit 0
        fi
        sleep 3
      done
      echo "Health check failed"; cat /tmp/health.out 2>/dev/null || true
      exit 1
  rules:
    - if: $CI_COMMIT_BRANCH == "develop"

# ──────────────── ДЕПЛОЙ НА PROD ────────────────
deploy:prod:
  stage: deploy
  when: manual
  needs: &#91;test&#93;
  image: alpine:latest
  variables:
    KEEP_RELEASES: "5"
    HEALTH_URL: "${PROD_APP_URL}/health/ready"
  script:
    - apk add --no-cache openssh-client curl
    - mkdir -p ~/.ssh &amp;&amp; chmod 700 ~/.ssh
    - echo "$PROD_SSH_PRIVATE_KEY_B64" | base64 -d > ~/.ssh/id_deploy
    - chmod 600 ~/.ssh/id_deploy
    - ssh-keyscan -H "$PROD_HOST" >> ~/.ssh/known_hosts
    - |
      ssh -i ~/.ssh/id_deploy "$PROD_USER@$PROD_HOST" \
        "BASE='$PROD_BASE' SHA='$CI_COMMIT_SHORT_SHA' FULL_SHA='$CI_COMMIT_SHA' REPO='$CI_REPOSITORY_URL' KEEP_RELEASES='$KEEP_RELEASES' bash -se" &lt;&lt;'EOSSH'
        set -euo pipefail
        umask 0002
        TS=$(date +%Y%m%d-%H%M)
        REL="$BASE/releases/${TS}-${SHA}"
        SHARED="$BASE/shared"

        git config --global --add safe.directory "$REL"

        # 1. Клонируем релиз (shallow) от main
        git clone --depth 1 --branch main "$REPO" "$REL"
        cd "$REL"
        echo "$FULL_SHA" > RELEASE

        # 2. Гарантируем структуру shared (на случай первого деплоя/чистого shared)
        mkdir -p "$SHARED/storage/app/public" \
                 "$SHARED/storage/framework/cache/data" \
                 "$SHARED/storage/framework/sessions" \
                 "$SHARED/storage/framework/testing" \
                 "$SHARED/storage/framework/views" \
                 "$SHARED/storage/logs"

        # 2.1 Default ACL на shared/storage: файлы/директории, создаваемые php-fpm
        # (www-data) во время работы приложения (sources/*, логи и т.п.), должны
        # оставаться доступны deployer и наоборот — setgid/umask этого не
        # гарантируют для новых записей, поэтому фиксируем через ACL при каждом деплое.
        sudo setfacl -R -m u:deployer:rwx,g:www-data:rwx,o::--- "$SHARED/storage"
        sudo setfacl -R -d -m u:deployer:rwx,g:www-data:rwx,o::--- "$SHARED/storage"

        # 3. Линкуем shared
        ln -sfn "$SHARED/.env" .env
        rm -rf storage &amp;&amp; ln -sfn "$SHARED/storage" storage

        # 3. Установка зависимостей (на новом релизе, не трогая current)
        composer install --no-dev --optimize-autoloader --no-interaction --no-progress

        # 4. Symlink public/storage -> storage/app/public
        php artisan storage:link

        # 5. Кеши конфигов/роутов/вью — в новом релизе
        php artisan config:cache
        php artisan route:cache
        php artisan view:cache
        php artisan event:cache

        # 5.1 Генерация Swagger-документации (актуализируем api-docs.json в shared/storage)
        php artisan l5-swagger:generate

        # 5. Миграции (один раз на релиз; делаем ДО переключения)
        php artisan migrate --force

        # 6. Атомарное переключение symlink current
        ln -sfn "$REL" "$BASE/current.new"
        mv -Tf "$BASE/current.new" "$BASE/current"

        # 7. Перезапуск Horizon (graceful) — supervisor поднимет его на новом релизе
        php artisan horizon:terminate

        # 8. Reload php-fpm для сброса opcache (graceful, без downtime)
        sudo systemctl reload php8.5-fpm

        # 9. Чистим старые релизы (всего храним KEEP_RELEASES, включая current)
        cd "$BASE/releases" || exit 1

        KEEP_ARGS=(-e "$(basename "$REL")")
        if &#91; -L "$BASE/current" &#93;; then
          KEEP_ARGS+=(-e "$(basename "$(readlink -f "$BASE/current")")")
        fi

        ls -1dt -- */ \
          | sed 's:/$::' \
          | tail -n +"$((KEEP_RELEASES + 1))" \
          | { grep -vx "${KEEP_ARGS&#91;@&#93;}" || true; } \
          | xargs -r rm -rf
      EOSSH
    # 10. Health check после деплоя
    - |
      echo "Health check: $HEALTH_URL"
      for i in 1 2 3 4 5; do
        code=$(curl -s -o /tmp/health.out -w "%{http_code}" "$HEALTH_URL" || echo "000")
        echo "attempt $i: HTTP $code"
        if &#91; "$code" = "200" &#93;; then
          cat /tmp/health.out; echo
          exit 0
        fi
        sleep 3
      done
      echo "Health check failed"; cat /tmp/health.out 2>/dev/null || true
      exit 1
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #8FBCBB">stages</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">test</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deploy</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># ──────────────── ТЕСТЫ ────────────────</span></span>
<span class="line"><span style="color: #8FBCBB">test</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">stage</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">test</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php:8.5-cli</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">cache</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">key</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">files</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#91;</span><span style="color: #A3BE8C">composer.lock</span><span style="color: #ECEFF4">&#93;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">paths</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#91;</span><span style="color: #A3BE8C">vendor/</span><span style="color: #ECEFF4">&#93;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">variables</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">DB_CONNECTION</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">sqlite</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">DB_DATABASE</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">:memory:</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">before_script</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">apt-get update -qq &amp;&amp; apt-get install -y -qq git unzip libicu-dev libzip-dev libsqlite3-dev libpng-dev libjpeg-dev libfreetype6-dev libwebp-dev</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">docker-php-ext-configure gd --with-jpeg --with-freetype --with-webp</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">docker-php-ext-install intl pcntl zip pdo_sqlite gd</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">curl -sS https://getcomposer.org/installer | php</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php composer.phar install --prefer-dist --no-ansi --no-progress</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">cp .env.example .env</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php artisan key:generate</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">script</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php artisan test --compact</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">rules</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">if</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">$CI_COMMIT_BRANCH == &quot;main&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88"># запускаем тест при пуше в main</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">if</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">$CI_COMMIT_BRANCH == &quot;develop&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88"># запускаем тест при пуше в develop</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">if</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">$CI_PIPELINE_SOURCE == &quot;merge_request_event&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88"># запускаем тест при PR</span></span>
<span class="line"><span style="color: #616E88"># тесты не запускаются при пуше в feature-ветки</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># ──────────────── ДЕПЛОЙ НА DEV ────────────────</span></span>
<span class="line"><span style="color: #8FBCBB">deploy:dev</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">stage</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deploy</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">needs</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#91;</span><span style="color: #A3BE8C">test</span><span style="color: #ECEFF4">&#93;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">alpine:latest</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">variables</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">KEEP_RELEASES</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">5</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">HEALTH_URL</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">${DEV_APP_URL}/health/ready</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">script</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">apk add --no-cache openssh-client curl</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mkdir -p ~/.ssh &amp;&amp; chmod 700 ~/.ssh</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">echo &quot;$DEV_SSH_PRIVATE_KEY_B64&quot; | base64 -d &gt; ~/.ssh/id_deploy</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">chmod 600 ~/.ssh/id_deploy</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ssh-keyscan -H &quot;$DEV_HOST&quot; &gt;&gt; ~/.ssh/known_hosts</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #A3BE8C">      ssh -i ~/.ssh/id_deploy &quot;$DEV_USER@$DEV_HOST&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">        &quot;BASE=&#39;$DEV_BASE&#39; SHA=&#39;$CI_COMMIT_SHORT_SHA&#39; FULL_SHA=&#39;$CI_COMMIT_SHA&#39; REPO=&#39;$CI_REPOSITORY_URL&#39; KEEP_RELEASES=&#39;$KEEP_RELEASES&#39; bash -se&quot; &lt;&lt;&#39;EOSSH&#39;</span></span>
<span class="line"><span style="color: #A3BE8C">        set -euo pipefail</span></span>
<span class="line"><span style="color: #A3BE8C">        umask 0002</span></span>
<span class="line"><span style="color: #A3BE8C">        TS=$(date +%Y%m%d-%H%M)</span></span>
<span class="line"><span style="color: #A3BE8C">        REL=&quot;$BASE/releases/${TS}-${SHA}&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        SHARED=&quot;$BASE/shared&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        git config --global --add safe.directory &quot;$REL&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 1. Клонируем релиз (shallow) от develop</span></span>
<span class="line"><span style="color: #A3BE8C">        git clone --depth 1 --branch develop &quot;$REPO&quot; &quot;$REL&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        cd &quot;$REL&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        echo &quot;$FULL_SHA&quot; &gt; RELEASE</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 2. Гарантируем структуру shared (на случай первого деплоя/чистого shared)</span></span>
<span class="line"><span style="color: #A3BE8C">        mkdir -p &quot;$SHARED/storage/app/public&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">                 &quot;$SHARED/storage/framework/cache/data&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">                 &quot;$SHARED/storage/framework/sessions&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">                 &quot;$SHARED/storage/framework/testing&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">                 &quot;$SHARED/storage/framework/views&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">                 &quot;$SHARED/storage/logs&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 2.1 Default ACL на shared/storage: файлы/директории, создаваемые php-fpm</span></span>
<span class="line"><span style="color: #A3BE8C">        # (www-data) во время работы приложения (sources/*, логи и т.п.), должны</span></span>
<span class="line"><span style="color: #A3BE8C">        # оставаться доступны deployer и наоборот — setgid/umask этого не</span></span>
<span class="line"><span style="color: #A3BE8C">        # гарантируют для новых записей, поэтому фиксируем через ACL при каждом деплое.</span></span>
<span class="line"><span style="color: #A3BE8C">        sudo setfacl -R -m u:deployer:rwx,g:www-data:rwx,o::--- &quot;$SHARED/storage&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        sudo setfacl -R -d -m u:deployer:rwx,g:www-data:rwx,o::--- &quot;$SHARED/storage&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 3. Линкуем shared</span></span>
<span class="line"><span style="color: #A3BE8C">        ln -sfn &quot;$SHARED/.env&quot; .env</span></span>
<span class="line"><span style="color: #A3BE8C">        rm -rf storage &amp;&amp; ln -sfn &quot;$SHARED/storage&quot; storage</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 3. Установка зависимостей (на новом релизе, не трогая current)</span></span>
<span class="line"><span style="color: #A3BE8C">        composer install --optimize-autoloader --no-interaction --no-progress</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 4. Symlink public/storage -&gt; storage/app/public</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan storage:link</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 5. Кеши конфигов/роутов/вью — в новом релизе</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan config:cache</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan route:cache</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan view:cache</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan event:cache</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 5.1 Генерация Swagger-документации (актуализируем api-docs.json в shared/storage)</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan l5-swagger:generate</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 5. Миграции (один раз на релиз; делаем ДО переключения)</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan migrate --force</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 6. Атомарное переключение symlink current</span></span>
<span class="line"><span style="color: #A3BE8C">        ln -sfn &quot;$REL&quot; &quot;$BASE/current.new&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        mv -Tf &quot;$BASE/current.new&quot; &quot;$BASE/current&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 7. Перезапуск Horizon (graceful) — supervisor поднимет его на новом релизе</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan horizon:terminate</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 8. Reload php-fpm для сброса opcache (graceful, без downtime)</span></span>
<span class="line"><span style="color: #A3BE8C">        sudo systemctl reload php8.5-fpm</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 9. Чистим старые релизы (всего храним KEEP_RELEASES, включая current)</span></span>
<span class="line"><span style="color: #A3BE8C">        cd &quot;$BASE/releases&quot; || exit 1</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        KEEP_ARGS=(-e &quot;$(basename &quot;$REL&quot;)&quot;)</span></span>
<span class="line"><span style="color: #A3BE8C">        if &#91; -L &quot;$BASE/current&quot; &#93;; then</span></span>
<span class="line"><span style="color: #A3BE8C">          KEEP_ARGS+=(-e &quot;$(basename &quot;$(readlink -f &quot;$BASE/current&quot;)&quot;)&quot;)</span></span>
<span class="line"><span style="color: #A3BE8C">        fi</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        ls -1dt -- */ \</span></span>
<span class="line"><span style="color: #A3BE8C">          | sed &#39;s:/$::&#39; \</span></span>
<span class="line"><span style="color: #A3BE8C">          | tail -n +&quot;$((KEEP_RELEASES + 1))&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">          | { grep -vx &quot;${KEEP_ARGS&#91;@&#93;}&quot; || true; } \</span></span>
<span class="line"><span style="color: #A3BE8C">          | xargs -r rm -rf</span></span>
<span class="line"><span style="color: #A3BE8C">      EOSSH</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88"># 10. Health check после деплоя</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #A3BE8C">      echo &quot;Health check: $HEALTH_URL&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">      for i in 1 2 3 4 5; do</span></span>
<span class="line"><span style="color: #A3BE8C">        code=$(curl -s -o /tmp/health.out -w &quot;%{http_code}&quot; &quot;$HEALTH_URL&quot; || echo &quot;000&quot;)</span></span>
<span class="line"><span style="color: #A3BE8C">        echo &quot;attempt $i: HTTP $code&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        if &#91; &quot;$code&quot; = &quot;200&quot; &#93;; then</span></span>
<span class="line"><span style="color: #A3BE8C">          cat /tmp/health.out; echo</span></span>
<span class="line"><span style="color: #A3BE8C">          exit 0</span></span>
<span class="line"><span style="color: #A3BE8C">        fi</span></span>
<span class="line"><span style="color: #A3BE8C">        sleep 3</span></span>
<span class="line"><span style="color: #A3BE8C">      done</span></span>
<span class="line"><span style="color: #A3BE8C">      echo &quot;Health check failed&quot;; cat /tmp/health.out 2&gt;/dev/null || true</span></span>
<span class="line"><span style="color: #A3BE8C">      exit 1</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">rules</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">if</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">$CI_COMMIT_BRANCH == &quot;develop&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># ──────────────── ДЕПЛОЙ НА PROD ────────────────</span></span>
<span class="line"><span style="color: #8FBCBB">deploy:prod</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">stage</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deploy</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">when</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">manual</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">needs</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#91;</span><span style="color: #A3BE8C">test</span><span style="color: #ECEFF4">&#93;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">alpine:latest</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">variables</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">KEEP_RELEASES</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">5</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">HEALTH_URL</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">${PROD_APP_URL}/health/ready</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">script</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">apk add --no-cache openssh-client curl</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mkdir -p ~/.ssh &amp;&amp; chmod 700 ~/.ssh</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">echo &quot;$PROD_SSH_PRIVATE_KEY_B64&quot; | base64 -d &gt; ~/.ssh/id_deploy</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">chmod 600 ~/.ssh/id_deploy</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ssh-keyscan -H &quot;$PROD_HOST&quot; &gt;&gt; ~/.ssh/known_hosts</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #A3BE8C">      ssh -i ~/.ssh/id_deploy &quot;$PROD_USER@$PROD_HOST&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">        &quot;BASE=&#39;$PROD_BASE&#39; SHA=&#39;$CI_COMMIT_SHORT_SHA&#39; FULL_SHA=&#39;$CI_COMMIT_SHA&#39; REPO=&#39;$CI_REPOSITORY_URL&#39; KEEP_RELEASES=&#39;$KEEP_RELEASES&#39; bash -se&quot; &lt;&lt;&#39;EOSSH&#39;</span></span>
<span class="line"><span style="color: #A3BE8C">        set -euo pipefail</span></span>
<span class="line"><span style="color: #A3BE8C">        umask 0002</span></span>
<span class="line"><span style="color: #A3BE8C">        TS=$(date +%Y%m%d-%H%M)</span></span>
<span class="line"><span style="color: #A3BE8C">        REL=&quot;$BASE/releases/${TS}-${SHA}&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        SHARED=&quot;$BASE/shared&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        git config --global --add safe.directory &quot;$REL&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 1. Клонируем релиз (shallow) от main</span></span>
<span class="line"><span style="color: #A3BE8C">        git clone --depth 1 --branch main &quot;$REPO&quot; &quot;$REL&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        cd &quot;$REL&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        echo &quot;$FULL_SHA&quot; &gt; RELEASE</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 2. Гарантируем структуру shared (на случай первого деплоя/чистого shared)</span></span>
<span class="line"><span style="color: #A3BE8C">        mkdir -p &quot;$SHARED/storage/app/public&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">                 &quot;$SHARED/storage/framework/cache/data&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">                 &quot;$SHARED/storage/framework/sessions&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">                 &quot;$SHARED/storage/framework/testing&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">                 &quot;$SHARED/storage/framework/views&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">                 &quot;$SHARED/storage/logs&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 2.1 Default ACL на shared/storage: файлы/директории, создаваемые php-fpm</span></span>
<span class="line"><span style="color: #A3BE8C">        # (www-data) во время работы приложения (sources/*, логи и т.п.), должны</span></span>
<span class="line"><span style="color: #A3BE8C">        # оставаться доступны deployer и наоборот — setgid/umask этого не</span></span>
<span class="line"><span style="color: #A3BE8C">        # гарантируют для новых записей, поэтому фиксируем через ACL при каждом деплое.</span></span>
<span class="line"><span style="color: #A3BE8C">        sudo setfacl -R -m u:deployer:rwx,g:www-data:rwx,o::--- &quot;$SHARED/storage&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        sudo setfacl -R -d -m u:deployer:rwx,g:www-data:rwx,o::--- &quot;$SHARED/storage&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 3. Линкуем shared</span></span>
<span class="line"><span style="color: #A3BE8C">        ln -sfn &quot;$SHARED/.env&quot; .env</span></span>
<span class="line"><span style="color: #A3BE8C">        rm -rf storage &amp;&amp; ln -sfn &quot;$SHARED/storage&quot; storage</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 3. Установка зависимостей (на новом релизе, не трогая current)</span></span>
<span class="line"><span style="color: #A3BE8C">        composer install --no-dev --optimize-autoloader --no-interaction --no-progress</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 4. Symlink public/storage -&gt; storage/app/public</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan storage:link</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 5. Кеши конфигов/роутов/вью — в новом релизе</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan config:cache</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan route:cache</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan view:cache</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan event:cache</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 5.1 Генерация Swagger-документации (актуализируем api-docs.json в shared/storage)</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan l5-swagger:generate</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 5. Миграции (один раз на релиз; делаем ДО переключения)</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan migrate --force</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 6. Атомарное переключение symlink current</span></span>
<span class="line"><span style="color: #A3BE8C">        ln -sfn &quot;$REL&quot; &quot;$BASE/current.new&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        mv -Tf &quot;$BASE/current.new&quot; &quot;$BASE/current&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 7. Перезапуск Horizon (graceful) — supervisor поднимет его на новом релизе</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan horizon:terminate</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 8. Reload php-fpm для сброса opcache (graceful, без downtime)</span></span>
<span class="line"><span style="color: #A3BE8C">        sudo systemctl reload php8.5-fpm</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 9. Чистим старые релизы (всего храним KEEP_RELEASES, включая current)</span></span>
<span class="line"><span style="color: #A3BE8C">        cd &quot;$BASE/releases&quot; || exit 1</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        KEEP_ARGS=(-e &quot;$(basename &quot;$REL&quot;)&quot;)</span></span>
<span class="line"><span style="color: #A3BE8C">        if &#91; -L &quot;$BASE/current&quot; &#93;; then</span></span>
<span class="line"><span style="color: #A3BE8C">          KEEP_ARGS+=(-e &quot;$(basename &quot;$(readlink -f &quot;$BASE/current&quot;)&quot;)&quot;)</span></span>
<span class="line"><span style="color: #A3BE8C">        fi</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        ls -1dt -- */ \</span></span>
<span class="line"><span style="color: #A3BE8C">          | sed &#39;s:/$::&#39; \</span></span>
<span class="line"><span style="color: #A3BE8C">          | tail -n +&quot;$((KEEP_RELEASES + 1))&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">          | { grep -vx &quot;${KEEP_ARGS&#91;@&#93;}&quot; || true; } \</span></span>
<span class="line"><span style="color: #A3BE8C">          | xargs -r rm -rf</span></span>
<span class="line"><span style="color: #A3BE8C">      EOSSH</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88"># 10. Health check после деплоя</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #A3BE8C">      echo &quot;Health check: $HEALTH_URL&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">      for i in 1 2 3 4 5; do</span></span>
<span class="line"><span style="color: #A3BE8C">        code=$(curl -s -o /tmp/health.out -w &quot;%{http_code}&quot; &quot;$HEALTH_URL&quot; || echo &quot;000&quot;)</span></span>
<span class="line"><span style="color: #A3BE8C">        echo &quot;attempt $i: HTTP $code&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        if &#91; &quot;$code&quot; = &quot;200&quot; &#93;; then</span></span>
<span class="line"><span style="color: #A3BE8C">          cat /tmp/health.out; echo</span></span>
<span class="line"><span style="color: #A3BE8C">          exit 0</span></span>
<span class="line"><span style="color: #A3BE8C">        fi</span></span>
<span class="line"><span style="color: #A3BE8C">        sleep 3</span></span>
<span class="line"><span style="color: #A3BE8C">      done</span></span>
<span class="line"><span style="color: #A3BE8C">      echo &quot;Health check failed&quot;; cat /tmp/health.out 2&gt;/dev/null || true</span></span>
<span class="line"><span style="color: #A3BE8C">      exit 1</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">rules</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">if</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">$CI_COMMIT_BRANCH == &quot;main&quot;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph"><strong>Необходимо убедится, что адрес /health/ready доступен, возвращает JSON с 200.</strong></p>



<p class="wp-block-paragraph">7<strong>.2 Тестируем пайплайн</strong></p>



<p class="wp-block-paragraph">Сначала проверяем дев-сервер.</p>



<p class="wp-block-paragraph">На локальной машине  в ветке develop делаем правки, коммитим  и пушим в репозиторий Gitlab, в удаленную ветку develop.<br>Gitlab запускает пайплайн для develop, посмотреть можно в резделе Project-&gt;Build-&gt;Pipelines.<br>Сначала выполняется задача с тестами, затем задача с деплоем на дев-сервер. Если все успешно &#8211; появляется зеленая метка Passed. Проверяем на дев-сервере, что обновление загрузилось.</p>



<p class="wp-block-paragraph">Если какая-либо задача в пайплайне завершилась с ошибкой &#8211; появится метка Failed, нажав на нее можно увидеть какая именно задача зафейлилась, нажав на задачу можно посмотреть лог, в чем именно ошибка. Там же можно перезапустить задачу, если исправление ошибки не требует  нового коммита.</p>



<p class="wp-block-paragraph"><strong>Теперь проверяем деплой на production.</strong></p>



<p class="wp-block-paragraph">Текущее состояние production перед тестом:</p>



<ul class="wp-block-list">
<li>две папки проектов: /classic-deploy и /cicd-deploy</li>



<li>проект работает из папки /cicd-deploy, current указывает на релиз &#8216;-manual&#8217;</li>
</ul>



<p class="wp-block-paragraph">Для запуска пайплайна в удаленную ветку main необходимо загрузить коммит слияния с веткой develop.</p>



<p class="wp-block-paragraph">Есть 2 варианта, можно выбрать любой:<br>a. Смерджить в локальном репозитории develop в main и затем запушить main в удаленный.<br>b. Смерджить ветку develop в main в самом Gitlab.</p>



<p class="wp-block-paragraph">Выбираем вариант b. <br>Переходим Project-&gt;Code-&gt;Merge request, нажимаем Create merge request<br>Появляется надпись Ready to merge,  перед слиянием запускается задача пайплайна с тестом. <br></p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="214" src="https://mcodex.ru/wp-content/uploads/2026/05/merge-1024x214.png" alt="" class="wp-image-209" srcset="https://mcodex.ru/wp-content/uploads/2026/05/merge-1024x214.png 1024w, https://mcodex.ru/wp-content/uploads/2026/05/merge-300x63.png 300w, https://mcodex.ru/wp-content/uploads/2026/05/merge-768x161.png 768w, https://mcodex.ru/wp-content/uploads/2026/05/merge-1536x321.png 1536w, https://mcodex.ru/wp-content/uploads/2026/05/merge.png 1635w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Если тесты проходят, ветки сливаются, &#8216;Ready to merge&#8217; меняется на &#8216;mered by &#8230; &#8216; и запускается заново пайплайн. <br>Снова проходит задача с тестом, а задача деплоя на прод, в отличие от задачи деплоя на  dev-сервер, не запускается, а пропускается, становится статус &#8216;passed&#8217;.  Это защита от случайного пуша в main, в пайплайне в задаче деплоя на прод стоит опция when:manual, то есть нужен ручной запуск. Заходим в задачу и нажимаем кнопку Run.</p>



<p class="wp-block-paragraph">После завершения задач на проде появляется новый релиз, current переключен на него.</p>



<p class="wp-block-paragraph">Проверяем, что проект работает, после этого папку со старым вариантом деплоя (classic-deploy) можно удалить.</p>



<h2 id="step-8" class="wp-block-heading"><strong>Шаг 8. Проверяем откат</strong></h2>



<p class="wp-block-paragraph">Что бы вернутся на предыдущий релиз, необходимо переключить ссылку current на папку предыдущего релиза. </p>



<p class="wp-block-paragraph">Для этого можно использовать следующий скрипт, </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>#!/bin/bash
set -euo pipefail
BASE=//var/www/my-project.com/cicd-deploy

cd "$BASE/releases"

PREV=$(ls -1t | sed -n '2p')   # второй сверху = предыдущий
&#91; -z "$PREV" &#93; &amp;&amp; { echo "No previous release"; exit 1; }

echo "Rolling back to: $PREV"
ln -sfn "$BASE/releases/$PREV" "$BASE/current.new"
mv -Tf "$BASE/current.new" "$BASE/current"

sudo supervisorctl signal SIGTERM horizon:* || true
sudo systemctl reload php8.5-fpm

echo "Done."
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">#!/bin/bash</span></span>
<span class="line"><span style="color: #88C0D0">set</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-euo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">pipefail</span></span>
<span class="line"><span style="color: #D8DEE9">BASE</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">//var/www/my-project.com/cicd-deploy</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">cd</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/releases</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">PREV</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">$(</span><span style="color: #88C0D0">ls</span><span style="color: #A3BE8C"> -1t </span><span style="color: #81A1C1">|</span><span style="color: #A3BE8C"> </span><span style="color: #88C0D0">sed</span><span style="color: #A3BE8C"> -n </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">2p</span><span style="color: #ECEFF4">&#39;)</span><span style="color: #D8DEE9FF">   </span><span style="color: #616E88"># второй сверху = предыдущий</span></span>
<span class="line"><span style="color: #ECEFF4">&#91;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-z</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$PREV</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#93;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&amp;&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">No previous release</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">exit</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Rolling back to: </span><span style="color: #D8DEE9">$PREV</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #88C0D0">ln</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-sfn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/releases/</span><span style="color: #D8DEE9">$PREV</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/current.new</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #88C0D0">mv</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-Tf</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/current.new</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/current</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">supervisorctl</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">signal</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">SIGTERM</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">horizon:</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">||</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">true</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">systemctl</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">reload</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php8.5-fpm</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Done.</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Файл скрипта rollback.sh можно положить в папку ~/home/deployer/deploy_scripts/my-project/ и запускать командой </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>bash rollback.sh</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">bash</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">rollback.sh</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>Данный скрипт не выполняет откаты миграции, поэтому, при необходимости их нужно выполнить вручную, в папке последнего релиза.</strong></p>
<p>Сообщение <a href="https://mcodex.ru/deploy-cicd-gitlab/">Zero-downtime деплой через CICD Gitlab</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Что такое PHP? Какие основные области применения?</title>
		<link>https://mcodex.ru/what-is-php/</link>
		
		<dc:creator><![CDATA[mcodex]]></dc:creator>
		<pubDate>Mon, 27 Apr 2026 16:59:34 +0000</pubDate>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Собеседование]]></category>
		<category><![CDATA[Базовый уровень]]></category>
		<guid isPermaLink="false">https://mcodex.ru/?p=150</guid>

					<description><![CDATA[<p>Аббревиатура PHP расшифровывается как &#8220;PHP Hypertext Preprocessor&#8221;, в названии заложена рекурсия, но изначально язык назывался проще &#8211; &#8220;Personal Home Page Tools&#8221;.Язык появился в середине 90-х годов 20-го века, является серверным языком программирования общего назначения, преимущественно используемый в веб-разработке.Изначально PHP был создан для работы с веб-страницами, а конкретнее &#8211; для вывода на статических html-страницах динамических данных: ... <a title="Что такое PHP? Какие основные области применения?" class="read-more" href="https://mcodex.ru/what-is-php/" aria-label="Read more about Что такое PHP? Какие основные области применения?">Read more</a></p>
<p>Сообщение <a href="https://mcodex.ru/what-is-php/">Что такое PHP? Какие основные области применения?</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Аббревиатура PHP расшифровывается как &#8220;PHP Hypertext Preprocessor&#8221;, в названии заложена рекурсия, но изначально язык назывался  проще &#8211; &#8220;Personal Home Page Tools&#8221;.<br>Язык появился в середине 90-х годов 20-го века, является серверным языком программирования общего назначения, преимущественно используемый в веб-разработке.<br>Изначально PHP был создан для работы с веб-страницами, а конкретнее &#8211; для вывода на статических html-страницах динамических данных: результатов выполнения функций, данных полученных из базы данных и т.д.<br>В настоящее время основной областью применения PHP остаются веб-технологии:</p>



<ul class="wp-block-list">
<li>фреймворки (например Laravel, Symfony, Yii)</li>



<li>СMS (например, WordPress, Drupal, Joomla)</li>



<li>веб-сервисы SaaS</li>



<li>API для фронтенда, мобильных приложений</li>



<li>динамические веб-страницы,</li>



<li>консольные CLI-утилиты (например, Deployer, Composer)</li>
</ul>



<p class="wp-block-paragraph">Основные особенности языка:</p>



<ul class="wp-block-list">
<li>язык является интерпретируемым, но начиная с версии 8.0 у него появился JIT-компилятор</li>



<li>выполнение на сервере, хотя, к слову, технология WebAssembly позволяет выполнять код PHP в браузере, но этот подход имеет узкую специализацию, в основном для<br>создания онлайн-песочниц</li>



<li>нет нативной реализации EventLoop из коробки для реализации асинхронности, но решается через сторонние библиотеки, например, ReactPHP, Swoole</li>



<li>нет многопоточности из коробки, как у Java или Golang, решается через расширения, например, parallel</li>



<li>в традиционном использовании 1 http-запрос = 1 процесс, решается через менеджер процессов PHP-FPM</li>



<li>слабая типизация, но начиная с 7-й версии появилась возможность установить режим строгой типизации</li>
</ul>



<p class="wp-block-paragraph">Сильные стороны языка:</p>



<ul class="wp-block-list">
<li>OPCache &#8211; in-memory хранилище байт-кода. Код приложения преобразовывается интерпретатором в инструкции для виртуальной машины Zend (байт-код, опкод),<br>сохраняется в памяти и при последующих запусках Zend сразу берет готовый байт-код, минуя этапы чтения и преобразования кода приложения.<br>Начиная с версии 5.5 входит в ядро, но для работы необходимо установить опцию в файле php.ini</li>



<li>JIT-компилятор. Работает поверх OPCache, преобразовывает байт-код в машинные инструкции для процессора.</li>



<li>Composer. Менеджер пакетов, сторонняя утилита, позволяет устанавливать в проект пакеты, контролирует зависимости и многое другое.</li>



<li>PSR. Набор стандартов для разработчиков, делающий код проектов удобным для поддержки и использования.</li>
</ul>



<p class="wp-block-paragraph">Области, где PHP не является оптимальным выбором: асинхронные операции, хайлоад, обработка больших объемов данных, но многие проблемы языка можно<br>современными решениями.</p>
<p>Сообщение <a href="https://mcodex.ru/what-is-php/">Что такое PHP? Какие основные области применения?</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
