<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Denis</title>
    <description>The latest articles on DEV Community by Denis (@deniskorbakov).</description>
    <link>https://dev.to/deniskorbakov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3328433%2F0fd52de5-1959-4545-81a2-567aceb3a846.jpeg</url>
      <title>DEV Community: Denis</title>
      <link>https://dev.to/deniskorbakov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/deniskorbakov"/>
    <language>en</language>
    <item>
      <title>We are deploying a PHP project on the prod using Ansible</title>
      <dc:creator>Denis</dc:creator>
      <pubDate>Wed, 19 Nov 2025 20:18:29 +0000</pubDate>
      <link>https://dev.to/deniskorbakov/we-are-deploying-a-php-project-on-the-prod-using-ansible-3kam</link>
      <guid>https://dev.to/deniskorbakov/we-are-deploying-a-php-project-on-the-prod-using-ansible-3kam</guid>
      <description>&lt;p&gt;In this article, we will get to know you at a basic level with Ansible, and deploy a project using it on a PHP server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting to know Ansible
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What kind of miracle tool is this?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ansible&lt;/strong&gt; is a tool for every YAML champion, with which you can deploy applications, configure configurations, and automate tasks via ssh.&lt;/p&gt;

&lt;p&gt;You may have heard of it along with the phrase "Infrastructure as Code (IaC)", because most of the infrastructure is set up using it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Basic concepts:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Playbook&lt;/strong&gt; is &lt;a href="https://ru.wikipedia.org/wiki/YAML" rel="noopener noreferrer"&gt;yaml&lt;/a&gt; a file that contains a set of tasks for their sequential execution&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update web servers&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webservers&lt;/span&gt;
  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;...&lt;/span&gt;


&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update db servers&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;databases&lt;/span&gt;
  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example shows the scenario of updating the web server and database&lt;/p&gt;

&lt;p&gt;In each scenario, we describe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt; - script name&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hosts&lt;/code&gt; - the name of the host/hosts from the inventory file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tasks&lt;/code&gt; - a set of tasks, you can specify instructions directly or connect individual files with the task itself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a detailed introduction, go to &lt;a href="https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_intro.html#ansible-playbooks" rel="noopener noreferrer"&gt;link&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Task is &lt;a href="https://ru.wikipedia.org/wiki/YAML" rel="noopener noreferrer"&gt;yaml&lt;/a&gt; a file with a certain set of commands that is responsible for its area (installing packages, configuring a web server, etc.)&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update web servers&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webservers&lt;/span&gt;
  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ensure apache is at the latest version&lt;/span&gt;
    &lt;span class="na"&gt;ansible.builtin.yum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;httpd&lt;/span&gt;
      &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Write the apache config file&lt;/span&gt;
    &lt;span class="na"&gt;ansible.builtin.template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/srv/httpd.j2&lt;/span&gt;
      &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/httpd.conf&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update db servers&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;databases&lt;/span&gt;
  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ensure postgresql is at the latest version&lt;/span&gt;
    &lt;span class="na"&gt;ansible.builtin.yum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql&lt;/span&gt;
      &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ensure that postgresql is started&lt;/span&gt;
    &lt;span class="na"&gt;ansible.builtin.service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql&lt;/span&gt;
      &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;started&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are already clearly looking at completing tasks in the playbook&lt;/p&gt;

&lt;p&gt;In the problem, we can describe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt; - task name&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ansible.builtin.&lt;/code&gt; - the module and its parameters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The module in the task facilitates the execution of operations by preparing a script inside the module, and the interaction takes place by passing arguments to the module.&lt;/p&gt;

&lt;p&gt;For more information, go to &lt;a href="https://docs.ansible.com/projects/ansible/2.9/modules/list_of_all_modules.html" rel="noopener noreferrer"&gt;link&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Vars is &lt;a href="https://ru.wikipedia.org/wiki/YAML" rel="noopener noreferrer"&gt;yaml&lt;/a&gt; a file that contains a set of variables that we can use in task and template files&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;  
&lt;span class="na"&gt;repo_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://github.com/deniskorbakov/laravel-12-frankenphp-docker.git"&lt;/span&gt;  
&lt;span class="na"&gt;path_to_remote_directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/var/www/laravel"&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the file, we describe the variables that we want to use in our task and template files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app_servers&lt;/span&gt;
  &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;path_to_remote_directory&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/22"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To use variables, we open and close the birds, and in them we specify the name of our variable.&lt;/p&gt;

&lt;p&gt;For more information, go to &lt;a href="https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_variables.html" rel="noopener noreferrer"&gt;link&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Template is &lt;a href="https://jinja.palletsprojects.com/en/stable%20/" rel="noopener noreferrer"&gt;j2&lt;/a&gt; a file that we can reuse in task files, for example, to copy configuration files with prepared variables&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {  
    listen 80;  
    listen [::]:80;  
    server_name {{ domain }};  
    server_tokens off;  
    root {{ path_to_remote_directory }}/public;  
    ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file contains variables that will be determined during the execution of the playbook, thereby allowing flexible configuration of various files.&lt;/p&gt;

&lt;p&gt;For more information, go to &lt;a href="https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_templating.html" rel="noopener noreferrer"&gt;link&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Inventory is &lt;a href="https://ru.wikipedia.org/wiki/INI" rel="noopener noreferrer"&gt;ini&lt;/a&gt; a file that contains a list of hosts that we can manage via Ansible&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[web]&lt;/span&gt;
&lt;span class="err"&gt;host1&lt;/span&gt;
&lt;span class="err"&gt;host2&lt;/span&gt; &lt;span class="py"&gt;ansible_port&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;222 # defined inline, interpreted as an integer&lt;/span&gt;

&lt;span class="nn"&gt;[web:vars]&lt;/span&gt;
&lt;span class="py"&gt;http_port&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;8080 # all members of 'web' will inherit these&lt;/span&gt;
&lt;span class="py"&gt;myvar&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;23 # defined in a :vars section, interpreted as a string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using these files, we describe our hosts, which in the future we will be able to specify in the playbook.&lt;/p&gt;

&lt;p&gt;For more information, go to &lt;a href="https://docs.ansible.com/projects/ansible/latest/collections/ansible/builtin/ini_inventory.html" rel="noopener noreferrer"&gt;link&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Conclusion on Ansible:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With these concepts, we can describe task scenarios in playbooks, add reusable variables and template files, and be able to perform tasks on multiple hosts.&lt;/p&gt;

&lt;h2&gt;
  
  
  About the PHP project
&lt;/h2&gt;

&lt;p&gt;I took my own template for the project.: &lt;br&gt;
&lt;a href="https://github.com/deniskorbakov/laravel-12-frankenphp-docker" rel="noopener noreferrer"&gt;https://github.com/deniskorbakov/laravel-12-frankenphp-docker&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This template contains frankenphp, docker-compose environment, web sockets via centrifugo, Open Api Doc and ready authorization.&lt;/p&gt;

&lt;p&gt;It already describes in advance the playbook for deployment on the prod&lt;/p&gt;
&lt;h2&gt;
  
  
  Writing a Playbook
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Description of what needs to be done:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On the prod, we will need to install the necessary packages, configure nginx to proxy our project, issue certificates for the domain, deploy and configure the project itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setting up inventory:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[webservers]&lt;/span&gt;  
&lt;span class="err"&gt;144.124.249.213&lt;/span&gt;  

&lt;span class="nn"&gt;[all:vars]&lt;/span&gt;  
&lt;span class="py"&gt;ansible_connection&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ssh  &lt;/span&gt;
&lt;span class="py"&gt;ansible_user&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;root&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We fill in ssh access for the server on which we will deploy the project&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We specify variables:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;  
&lt;span class="na"&gt;repo_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://github.com/deniskorbakov/laravel-12-frankenphp-docker.git"&lt;/span&gt;  
&lt;span class="na"&gt;path_to_remote_directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/var/www/laravel"&lt;/span&gt;  
&lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v543323.hosted-by-vdsina.com"&lt;/span&gt;  
&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;domain&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;  
&lt;span class="na"&gt;os_environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;APP_URL&lt;/span&gt;  
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;  
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;APP_ENV&lt;/span&gt;  
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;production"&lt;/span&gt;  
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;APP_DEBUG&lt;/span&gt;  
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;  
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OCTANE_HTTPS&lt;/span&gt;  
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We fill in the following variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;repo_url&lt;/code&gt; - the url of our project in github&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;path_to_remote_directory&lt;/code&gt; - the path where our project will be located on the server&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;domain&lt;/code&gt; - specify which is linked to our server&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;url&lt;/code&gt; - generated independently from domain&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;os_environment&lt;/code&gt; - fill in the variables for env, which we will replace with the product&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Creating a Playbook:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Expand the environment&lt;/span&gt;  
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webservers&lt;/span&gt;  
  &lt;span class="na"&gt;vars_files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;../vars/default.yml&lt;/span&gt;  
  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Init Packages&lt;/span&gt;  
      &lt;span class="na"&gt;ansible.builtin.include_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../tasks/packages/init.yml&lt;/span&gt;  

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Docker&lt;/span&gt;  
      &lt;span class="na"&gt;ansible.builtin.include_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../tasks/docker/setup.yml&lt;/span&gt;  

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Clone Project&lt;/span&gt;  
      &lt;span class="na"&gt;ansible.builtin.include_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../tasks/sync/copy.yml&lt;/span&gt;  

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Init App&lt;/span&gt;  
      &lt;span class="na"&gt;ansible.builtin.include_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../tasks/app/init.yml&lt;/span&gt;  

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure Nginx&lt;/span&gt;  
      &lt;span class="na"&gt;ansible.builtin.include_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../tasks/system/nginx.yml&lt;/span&gt;  

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Produce Certificates&lt;/span&gt;  
      &lt;span class="na"&gt;ansible.builtin.include_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../tasks/system/cert.yml&lt;/span&gt;  

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Rebuild App&lt;/span&gt;  
      &lt;span class="na"&gt;ansible.builtin.include_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../tasks/app/rebuild.yml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we specify the alias of our hosts from &lt;code&gt;inventory.ini&lt;/code&gt; to &lt;code&gt;hosts&lt;/code&gt;, add a file with variables and specify the tasks to be performed in turn at startup&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We describe Tasks:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next, let's look at each task in order.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Init Packages&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;  
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install required packages&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.apt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apt-transport-https&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ca-certificates&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;software-properties-common&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gnupg&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;make&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;socat&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;certbot&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;python3-certbot-nginx&lt;/span&gt;  
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;  
    &lt;span class="na"&gt;update_cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we install all the packages we need to work with.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Setup Docker&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;  
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Add Docker GPG key&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.apt_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://download.docker.com/linux/ubuntu/gpg&lt;/span&gt;  
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;  

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Add Docker repository&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.apt_repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable&lt;/span&gt;  
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;  
    &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker&lt;/span&gt;  

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Docker&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.apt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker-ce&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker-ce-cli&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;containerd.io&lt;/span&gt;  
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;  
    &lt;span class="na"&gt;update_cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;  

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Start and enable Docker service&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker&lt;/span&gt;  
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;started&lt;/span&gt;  
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;  

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Add user to docker group&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ansible_user&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;default('ansible')&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;  
    &lt;span class="na"&gt;groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker&lt;/span&gt;  
    &lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;  

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Docker Compose&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.get_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/docker/compose/releases/download/v2.29.2/docker-compose-linux-x86_64&lt;/span&gt;  
    &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/usr/local/bin/docker-compose&lt;/span&gt;  
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0755'&lt;/span&gt;  

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create symbolic link for Docker Compose&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/usr/local/bin/docker-compose&lt;/span&gt;  
    &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/usr/bin/docker-compose&lt;/span&gt;  
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;link&lt;/span&gt;  

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Verify Docker Compose installation&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker-compose --version&lt;/span&gt;  
  &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker_compose_version&lt;/span&gt;  
  &lt;span class="na"&gt;changed_when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this task, we install &lt;code&gt;docker&lt;/code&gt; and &lt;code&gt;docker-compose&lt;/code&gt; for further work.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Clone Project&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;  
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check if directory exists&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.stat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;path_to_remote_directory&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;  
  &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;project_dir_stat&lt;/span&gt;  

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create project dir&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;path_to_remote_directory&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;  
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;directory&lt;/span&gt;  
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0755&lt;/span&gt;  
  &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;not project_dir_stat.stat.exists&lt;/span&gt;  

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Git clone&lt;/span&gt;  
  &lt;span class="na"&gt;block&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Clone repository&lt;/span&gt;  
      &lt;span class="na"&gt;ansible.builtin.git&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
        &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;repo_url&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;  
        &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;path_to_remote_directory&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;  
        &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;branch&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;default('main')&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;  
      &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;clone_result&lt;/span&gt;  
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3&lt;/span&gt;  
      &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5&lt;/span&gt;  
      &lt;span class="na"&gt;until&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;clone_result is succeeded&lt;/span&gt;  
      &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;not project_dir_stat.stat.exists&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we create a directory for the project, if it has not yet been created, and clone our project, which we specified in the variables file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Init App&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;  
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create Storage Public Dir&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;path_to_remote_directory&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/storage/app/public"&lt;/span&gt;  
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;directory&lt;/span&gt;  
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0755&lt;/span&gt;  

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Copy env.example&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.copy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;path_to_remote_directory&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/.env.example"&lt;/span&gt;  
    &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;path_to_remote_directory&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/.env"&lt;/span&gt;  
    &lt;span class="na"&gt;remote_src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;  

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set vars in ENV&lt;/span&gt;  
  &lt;span class="na"&gt;lineinfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;path_to_remote_directory&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/.env"&lt;/span&gt;  
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;  
    &lt;span class="na"&gt;regexp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;item.key&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}="&lt;/span&gt;  
    &lt;span class="na"&gt;line&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;item.key&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}={{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;item.value}}"&lt;/span&gt;  
  &lt;span class="na"&gt;with_items&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;os_environment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;  
  &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;  

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Init Project&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;make init-prod&lt;/span&gt;  
    &lt;span class="na"&gt;chdir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;path_to_remote_directory&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;  
  &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;command_result&lt;/span&gt;  
  &lt;span class="na"&gt;failed_when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;'FAILED'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;command_result.stderr"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we create a public directory, copy &lt;code&gt;env.example&lt;/code&gt; to &lt;code&gt;env&lt;/code&gt; and replace certain variables that we explicitly specify in the file with variables for &lt;code&gt;ansible&lt;/code&gt; and run &lt;code&gt;make init-prod&lt;/code&gt; to initialize the project on the prod&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Configure Nginx&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;  
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Delete default dir&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;absent&lt;/span&gt;  
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/www/html&lt;/span&gt;  

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Copy config&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../templates/nginx_conf.j2&lt;/span&gt;  
    &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/etc/nginx/sites-enabled/{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;domain&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;  

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Reload Nginx&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.systemd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reloaded&lt;/span&gt;  
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this task, delete the default directory, copy the config for nginx, and restart the nginx process.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Produce Certificates&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;  
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Obtain SSL certificate with certbot&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;  
    &lt;span class="s"&gt;certbot \  &lt;/span&gt;
    &lt;span class="s"&gt;--force-renewal \  &lt;/span&gt;
    &lt;span class="s"&gt;--nginx \  &lt;/span&gt;
    &lt;span class="s"&gt;--noninteractive \  &lt;/span&gt;
    &lt;span class="s"&gt;--agree-tos \  &lt;/span&gt;
    &lt;span class="s"&gt;--cert-name {{ domain }} \  &lt;/span&gt;
    &lt;span class="s"&gt;-d {{ domain }} \  &lt;/span&gt;
    &lt;span class="s"&gt;-m test@gmail.com \  &lt;/span&gt;
    &lt;span class="s"&gt;--verbose  &lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;creates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/etc/letsencrypt/live/{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;domain&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/cert.pem"&lt;/span&gt;  
  &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;  
  &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;certbot_result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we already issue certificates for our domain through &lt;code&gt;certbot&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Rebuild App&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;  
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pause for 2 min&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.pause&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2&lt;/span&gt;  

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Restart app&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;make restart&lt;/span&gt;  
    &lt;span class="na"&gt;chdir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;path_to_remote_directory&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;  
  &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;  
  &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;restart_result&lt;/span&gt;  

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pause for 2 min&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.pause&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2&lt;/span&gt;  

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update project&lt;/span&gt;  
  &lt;span class="na"&gt;ansible.builtin.command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;make update-project&lt;/span&gt;  
    &lt;span class="na"&gt;chdir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;path_to_remote_directory&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;  
  &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;  
  &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;update_result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this task, we are restarting our containers and updating these projects so that everything works for sure!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The result of the work done:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now you and I have written your first playbook and got acquainted with the wonderful tool &lt;code&gt;Ansible&lt;/code&gt; for YAML champions&lt;/p&gt;

&lt;p&gt;I'm waiting for comments under the post about what can be improved or how you would write this playbook ;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;Today, we learned a little about Ansible, studied its basic concepts, wrote a Playbook with you, and deployed the project on the prod&lt;/p&gt;

&lt;p&gt;Thank you for reading this article.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>ansible</category>
      <category>php</category>
    </item>
    <item>
      <title>How to create a TUI utility on GO</title>
      <dc:creator>Denis</dc:creator>
      <pubDate>Mon, 06 Oct 2025 06:46:41 +0000</pubDate>
      <link>https://dev.to/deniskorbakov/how-to-create-a-tui-utility-on-go-1la5</link>
      <guid>https://dev.to/deniskorbakov/how-to-create-a-tui-utility-on-go-1la5</guid>
      <description>&lt;p&gt;Here is the translated text with all formatting preserved:&lt;/p&gt;

&lt;p&gt;In this article, I will explain how to create a console TUI utility in Go using ready-made components, show examples of interfaces, and build an application using &lt;a href="https://goreleaser.com/" rel="noopener noreferrer"&gt;Goreleaser&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In other words, we will go through the entire cycle of creating an application up to its release.&lt;/p&gt;

&lt;p&gt;Everything described in this article is collected in this repository:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/deniskorbakov" rel="noopener noreferrer"&gt;https://github.com/deniskorbakov&lt;/a&gt;  &lt;/p&gt;
&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;I really like TUI; with it, terminal utilities come to life with new colors, and the pleasure of using these programs increases significantly.&lt;/p&gt;

&lt;p&gt;Recently, with the team at https://жыбийрыр.рф/, we participated in a hackathon where we created a utility (Go + Ansible) for automated deployment and building of all possible applications on a server (a dystopian task).&lt;/p&gt;

&lt;p&gt;For this solution, we also used TUI components for the utility, which helped us stand out among the participants and ultimately win.&lt;/p&gt;
&lt;h2&gt;
  
  
  What we will use to create the utility
&lt;/h2&gt;

&lt;p&gt;1) Cobra - &lt;a href="https://github.com/spf13/cobra" rel="noopener noreferrer"&gt;https://github.com/spf13/cobra&lt;/a&gt;&lt;br&gt;&lt;br&gt;
2) Fang - &lt;a href="https://github.com/charmbracelet/fang" rel="noopener noreferrer"&gt;https://github.com/charmbracelet/fang&lt;/a&gt;&lt;br&gt;&lt;br&gt;
3) Huh - &lt;a href="https://github.com/charmbracelet/huh" rel="noopener noreferrer"&gt;https://github.com/charmbracelet/huh&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/spf13/cobra" rel="noopener noreferrer"&gt;Cobra&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This library is used for creating console applications in Go.&lt;/p&gt;

&lt;p&gt;It provides a necessary set of tools for building utilities:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A convenient API for creating commands and defining logic
&lt;/li&gt;
&lt;li&gt;Support for flags
&lt;/li&gt;
&lt;li&gt;Suggestions for incorrect command input, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/charmbracelet/fang" rel="noopener noreferrer"&gt;Fang&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This library enhances the description of the utility, making it more informative and vibrant, and integrates seamlessly with &lt;a href="https://github.com/spf13/cobra" rel="noopener noreferrer"&gt;Cobra&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;By adding &lt;a href="https://github.com/charmbracelet/fang" rel="noopener noreferrer"&gt;Fang&lt;/a&gt;, you get the following interface:&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiwc029kfjkrm8zh5ecnr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiwc029kfjkrm8zh5ecnr.png" alt=" " width="800" height="939"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/charmbracelet/huh" rel="noopener noreferrer"&gt;Huh&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This library provides a set of ready-made TUI components and fits perfectly into the color palette of &lt;a href="https://github.com/charmbracelet/fang" rel="noopener noreferrer"&gt;Fang&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here are the components you can get by using this library:&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjth7qb9as1tf2gx0ojrj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjth7qb9as1tf2gx0ojrj.gif" alt=" " width="1000" height="700"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating the Utility
&lt;/h2&gt;

&lt;p&gt;Clone the template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/deniskorbakov/skeleton-cli-go.git  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigate to the project folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd skeleton-cli-go  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build our application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make build  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the template utility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./cli  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Application Architecture&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4udrts0n50knnmttu4t3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4udrts0n50knnmttu4t3.png" alt=" " width="800" height="786"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cmd/cli&lt;/code&gt; - contains a directory with the name of our application, which includes the main file that initializes &lt;a href="https://github.com/spf13/cobra" rel="noopener noreferrer"&gt;cobra&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main  

import "github.com/deniskorbakov/skeleton-cli-go/internal/command"  

func main() {  
    command.Run()  
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;configs/constname&lt;/code&gt; - contains constants that describe (name, short, and long descriptions of commands).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;example.go&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package constname  

const (  
    // UseExampleCmd Name example command    
    UseExampleCmd = `example`  

    // ShortExampleCmd Short description example command
    ShortExampleCmd = `Example test command`  
    ...
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;root.go&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package constname  

const (  
    // UseRootCmd App name and default name command    
    UseRootCmd = `cli`  

    // LongRootCmd Long description root command    
    LongRootCmd = `cli is a example util`  
    ...
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;internal/command&lt;/code&gt; - contains the application’s commands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;example.go&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package command  

import (  
    "github.com/deniskorbakov/skeleton-cli-go/configs/constname"  
    "github.com/deniskorbakov/skeleton-cli-go/internal/component/form"  
    "github.com/deniskorbakov/skeleton-cli-go/internal/component/output"  
    "github.com/spf13/cobra"  
)  

// exampleCmd Command for build pipeline 
var exampleCmd = &amp;amp;cobra.Command{  
    Use:   constname.UseExampleCmd,  
    Short: constname.ShortExampleCmd,  
    Long:  constname.LongExampleCmd,  
    RunE: func(cmd *cobra.Command, args []string) error {  
       fields, err := form.Run()  
       if err != nil {  
          return err  
       }  

       output.Green("Success green output: ", fields.ExampleInput)  
       output.Red("This command will be run successfully")  

       return nil  
    },  
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is an example of a command that we will need to add to root.go.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;root.go&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package command  

import (  
    "context"  
    "os"  
    "github.com/charmbracelet/fang"  
    "github.com/deniskorbakov/skeleton-cli-go/configs/constname"  
    "github.com/deniskorbakov/skeleton-cli-go/internal/component/output"  
    "github.com/deniskorbakov/skeleton-cli-go/internal/version"  
    "github.com/spf13/cobra"  
)  

// Run Start app with cobra cmd
func Run() {  
    cmd := &amp;amp;cobra.Command{  
       Use:     constname.UseRootCmd,  
       Long:    constname.LongRootCmd,  
       Example: constname.ExampleRootCmd,  
    }  

    // Disable default options cmd  
    cmd.Root().CompletionOptions.DisableDefaultCmd = true  

    // Add all command in your app  
    cmd.AddCommand(  
       exampleCmd,  
    )  

    if err := fang.Execute(  
       context.Background(),  
       cmd,  
       fang.WithVersion(version.Get()),  
    ); err != nil {  
       output.Red("The app is broken")  
       os.Exit(1)  
    }  
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file contains the logic for describing the main command, configuring the utility, listing added commands, and initializing &lt;a href="https://github.com/spf13/cobra" rel="noopener noreferrer"&gt;Cobra&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;internal/component&lt;/code&gt; - contains the application’s components.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;internal/component/form/form.go&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package form  

import "github.com/charmbracelet/huh"  

// Run Main function that runs an interactive form
func Run() (*Fields, error) {  
    fields := &amp;amp;Fields{}  

    err := huh.NewForm(  
       huh.NewGroup(  
          huh.NewInput().  
             Title("Example Title").  
             Description("Example description input").  
             Value(&amp;amp;fields.ExampleInput),  
       ),  
    ).WithShowHelp(true).Run()  
    if err != nil {  
       return nil, err  
    }  

    return fields, nil  
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we create a form using &lt;a href="https://github.com/charmbracelet/huh" rel="noopener noreferrer"&gt;huh&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;internal/component/form/fields.go&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package form  

// Fields struct for huh form
type Fields struct {  
    ExampleInput string  
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure contains the fields we specify when creating the form.&lt;/p&gt;

&lt;p&gt;In your application, you can remove this implementation if you consider it unnecessary and use any other output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;internal/component/output/output.go&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package output  

import (  
    "fmt"  

    "github.com/charmbracelet/lipgloss/v2")  

var (  
    green = lipgloss.Color("#04B575")  
    red   = lipgloss.Color("#D4634C")  
)  

func output(colorText string) {  
    fmt.Println(colorText)  
}  

func Green(str ...string) {  
    output(lipgloss.NewStyle().Foreground(green).Render(str...))  
}  

func Red(str ...string) {  
    output(lipgloss.NewStyle().Foreground(red).Render(str...))  
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This component is used for outputting text to the console.&lt;/p&gt;

&lt;p&gt;You can also easily extend this component by adding your own colors using &lt;a href="https://github.com/charmbracelet/lipgloss" rel="noopener noreferrer"&gt;lipgloss&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;internal/version&lt;/code&gt; - contains the logic for parsing the application version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;internal/version/version.go&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package version  

import (  
    "regexp"  
    "runtime/debug")  

var (  
    regexVersion = `^v?(\d+\.\d+\.\d+)`  
    emptyVersion = "none version"  
)  

// Get return version from debug main version
func Get() string {  
    if info, ok := debug.ReadBuildInfo(); ok {  
       version := info.Main.Version  

       re := regexp.MustCompile(regexVersion)  
       if matches := re.FindStringSubmatch(version); len(matches) &amp;gt; 1 {  
          return matches[1]  
       }  
       return emptyVersion  
    } else {  
       return emptyVersion  
    }  
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We retrieve the specified version during the application build and use a regular expression to get the version value; if it’s not found, we indicate that the version was not found.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Changing the utility name and its description&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Currently, our project’s utility is called &lt;code&gt;cli&lt;/code&gt; - let’s replace it with the name of your utility.&lt;/p&gt;

&lt;p&gt;Change the path to your application to the name of your utility: &lt;code&gt;cmd/cli&lt;/code&gt; -&amp;gt; &lt;code&gt;cmd/your_name_cli&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;.goreleaser.yaml&lt;/code&gt; file, you need to change the name &lt;code&gt;cli&lt;/code&gt; to the name of your utility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: 2  

env:  
  - GO111MODULE=on  
project_name: your_name_cli  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, replace the application name in the &lt;code&gt;Makefile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;build:  
    go mod vendor    
    go build -ldflags "-w -s" -o your_name_cli cmd/your_name_cli/main.go  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change the application description in the root command - &lt;code&gt;configs/constname/root.go&lt;/code&gt;.  &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://goreleaser.com/" rel="noopener noreferrer"&gt;Goreleaser&lt;/a&gt;
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a tool for automating the process of creating releases for projects across different distributions and OS.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The project already includes a &lt;code&gt;.goreleaser.yaml&lt;/code&gt; file and a configured GitHub Action.&lt;/p&gt;

&lt;p&gt;You only need to add a secret to the repository named &lt;code&gt;GO_RELEASER&lt;/code&gt;, which will contain your personal GitHub token.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, I showed you how to create your own TUI utility in Go using a template.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/deniskorbakov" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; - I’d be happy if you follow me.&lt;/p&gt;

&lt;p&gt;Thank you for reading this article!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>go</category>
      <category>cli</category>
      <category>tui</category>
    </item>
    <item>
      <title>Emoji php - emoticons in your project</title>
      <dc:creator>Denis</dc:creator>
      <pubDate>Mon, 15 Sep 2025 13:17:52 +0000</pubDate>
      <link>https://dev.to/deniskorbakov/emoji-php-emoticons-in-your-project-346a</link>
      <guid>https://dev.to/deniskorbakov/emoji-php-emoticons-in-your-project-346a</guid>
      <description>&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;p&gt;There is a need to use emoticons, as implemented in Tg:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Grouping emoticons by type&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A collection of emoticons for displaying a list&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Correct storage of emoticons in the database&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Emoji search (search by tags, name, etc.)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When searching the Internet, I realized that there is no such library in PHP that would solve the problems I needed and decided to write my own library.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Emoji PHP&lt;/strong&gt; - Link to GitHub: &lt;a href="https://github.com/deniskorbakov/emoji-php" rel="noopener noreferrer"&gt;https://github.com/deniskorbakov/emoji-php &lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the problem
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lack of a ready-made tool for this task&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Where to get actual emoticons from&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storing Unicode emoticons in the database&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search for emoticons in different languages&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I managed to solve all these problems, not in the best way, but I'm quite satisfied with it.&lt;/p&gt;

&lt;p&gt;Therefore, if you have something to add, I am waiting for your comments under the post.&lt;/p&gt;

&lt;p&gt;Next, let's look at how it was possible to solve everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem solving
&lt;/h3&gt;

&lt;p&gt;1) &lt;strong&gt;Lack of a ready-made tool for this task&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To do this, I had to write my own library, below I will show you what it can offer you in terms of functionality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simple implementation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In order to install the package, you need PHP (version 8.4 or higher) and composer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require deniskorbakov/emoji-php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installation, you can work with the class for emoticons.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use DenisKorbakov\EmojiPhp\Emojis;

new Emojis();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Support for 25 languages&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To use the support for different languages, I added 25 languages to the project, which provided the repository from the second point, in order to get multilingual grouping and search by emoticons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A list with a grouping&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In order to get a list of emoticons grouped by type, you need to call the following method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use DenisKorbakov\EmojiPhp\Emojis;
use DenisKorbakov\EmojiPhp\Locale;

new Emojis()-&amp;gt;list(Locale::EN);
// return ['smileys &amp;amp; emotion' =&amp;gt; ['😀' =&amp;gt; ':grinning_face:', ...]]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on the specified Locale, you will get a grouping by the selected language.&lt;/p&gt;

&lt;p&gt;The group will contain Unicode (smiley face) and a Short Code, which we will insert into the text or we can use a smiley face in the text, and using the method below we will replace it when saving data to the database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Replacement of Emoticons and Short Code in the text&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In order to replace Unicode in the text with Short Code and vice versa, we use the following methods:&lt;/p&gt;

&lt;p&gt;Here we are replacing Unicode with Short Code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use DenisKorbakov\EmojiPhp\Emojis;

$text = 'Hello, world! 🌍️'

new Emojis()-&amp;gt;toCode($text);
// return 'Hello, world! :globe_showing_europe_africa:'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are replacing the Short Code with Unicode&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use DenisKorbakov\EmojiPhp\Emojis;

$text = 'Hello :waving_hand:';

new Emojis()-&amp;gt;toEmoji($text);
// return 'Hello 👋'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Keyword search&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To search by keywords, we use the following method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use DenisKorbakov\EmojiPhp\Emojis;
use DenisKorbakov\EmojiPhp\Locale;

$searchText = 'shoe'

new Emojis()-&amp;gt;search(Locale::EN, $searchText);
// return ['👞' =&amp;gt; ':mans_shoe:', ...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this method, we specify the language we want to use to perform the search and the text we are looking for.&lt;/p&gt;

&lt;p&gt;The result is obtained by the usual coincidence of words in the tags of the searched word.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In the future, I want to improve the search, as it is not perfect, but it is quite suitable for searching by matching keywords.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;2) &lt;strong&gt;Where to get actual emoticons from&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The source of the current emoticons, I chose this &lt;a href="https://github.com/milesj/emojibase" rel="noopener noreferrer"&gt;repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It already has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Support for more than 25 languages&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLDR Short Codes&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collection of relevant emoticons&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3) &lt;strong&gt;Storing Unicode emoticons in the database&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Emoticons can be stored in the database by changing the encoding to &lt;code&gt;utf8mb4&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I read somewhere that Discord does not store the Unicode of the smiley face and a certain Short Code in the database, and then converts it to Unicode.&lt;/p&gt;

&lt;p&gt;After studying the topic, I realized that it is possible to use standardized Short Codes according to the CLDR specification.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CLDR (Common Locale Data Repository) is an extensive repository of local data that is used by software around the world to correctly display and work with emojis, ensuring their consistency and correct meaning in different language environments&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the second paragraph, I specified a repository that already contains the CLDR Short Code for emoticons.&lt;/p&gt;

&lt;p&gt;With this solution, we were able to solve the problem of saving emoticons in the database without changing the encoding of the database itself.&lt;/p&gt;

&lt;p&gt;4) &lt;strong&gt;Search for emoticons in different languages&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For my library, I wanted to implement a keyword search in both Russian and English.&lt;/p&gt;

&lt;p&gt;Also, because I chose to make this solution an open library, it was also worth thinking about other languages.&lt;/p&gt;

&lt;p&gt;The second point specifies a repository that supports more than 25 languages, and therefore this process took a little time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Unfortunately, at this stage I had to upload all 25 languages to the project, which increases the package size, in the future I want to solve this problem by adding a command to add and remove languages.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;I told you about the problem I faced, showed you solutions to various nuances, and showed you the functionality of the library I created for this purpose.&lt;/p&gt;

&lt;p&gt;I will also be glad to see suggestions for future features or bug reports in the issue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/deniskorbakov" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; - I will be glad to receive your subscription to me&lt;/p&gt;

&lt;p&gt;Thank you for reading this article.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>webdev</category>
      <category>php</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Misha - console SSH client</title>
      <dc:creator>Denis</dc:creator>
      <pubDate>Sat, 06 Sep 2025 11:38:56 +0000</pubDate>
      <link>https://dev.to/deniskorbakov/misha-console-ssh-client-2i7k</link>
      <guid>https://dev.to/deniskorbakov/misha-console-ssh-client-2i7k</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;I had a desire to learn go and write some kind of open-source project on it, initially there were no ideas and problems that I could solve in my head.&lt;/p&gt;

&lt;p&gt;At work, I often had to connect to several different machines during the working day - access to them lay in different chats, files, etc.&lt;/p&gt;

&lt;p&gt;This situation was very annoying for me, that I had to spend a lot of time trying to find the right access points, somewhere there was a connection by password, somewhere by key, and because of this, this process could take even more time.&lt;/p&gt;

&lt;p&gt;I did not want to use desktop ssh clients due to the habit of working over ssh through the terminal.&lt;/p&gt;

&lt;p&gt;And eventually I realized that I wanted to create my own console ssh client, a short search led me to the fact that console ssh clients are not so popular and not very beautiful - so I started creating &lt;strong&gt;Mikhail&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Description of Michael
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/misha-ssh/cli" rel="noopener noreferrer"&gt;Misha&lt;/a&gt; is a console SSH client that will grow into a full-fledged utility for convenient interaction with a remote computer via SSH in the future.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Installation:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS or Linux  &lt;/span&gt;
brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; misha
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What can Misha do:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1) Knows how to make connections&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create connections&lt;/strong&gt; - &lt;a href="https://asciinema.org/a/734430" rel="noopener noreferrer"&gt;video link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhtdhfqe1sa9cjkvw0al1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhtdhfqe1sa9cjkvw0al1.png" alt="img" width="800" height="753"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update connections&lt;/strong&gt; - &lt;a href="https://asciinema.org/a/734431" rel="noopener noreferrer"&gt;link to video&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fql5xv4limy47z4jkuuk7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fql5xv4limy47z4jkuuk7.png" alt="img" width="800" height="753"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delete connections&lt;/strong&gt; - &lt;a href="https://asciinema.org/a/734051" rel="noopener noreferrer"&gt;link to video&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnbrropptlvptkbtf0gmv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnbrropptlvptkbtf0gmv.png" alt="img" width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2) Can connect to a created connection&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connection process&lt;/strong&gt; - &lt;a href="https://asciinema.org/a/734047" rel="noopener noreferrer"&gt;video link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frmiaw3f1wm4i2a63o96j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frmiaw3f1wm4i2a63o96j.png" alt="img" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Future features:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1) SCP operations&lt;/p&gt;

&lt;p&gt;I want to make a tool kit for ssh out of this utility, so I want to add a future feature for convenient SCP interaction - since I often have to drag something from the server or to the server and constantly Google which arguments scp accepts, it also takes time.&lt;/p&gt;

&lt;p&gt;2) Project configuration&lt;/p&gt;

&lt;p&gt;I also want to implement a configuration utility where we can set the connection shell theme, the visual theme of the application, the location of various files, and so on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;In this article, I shared with you my project, which I think someone might like and solve the same problem as mine.&lt;/p&gt;

&lt;p&gt;I would really appreciate it if you put a star on the repository and also try this utility in practice, because I want to know people's opinions.&lt;/p&gt;

&lt;p&gt;I will also be glad to see suggestions for future features or bug reports in the issue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/deniskorbakov" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; - I will be glad to receive your subscription to me&lt;/p&gt;

&lt;p&gt;Thank you for reading this article.&lt;/p&gt;

</description>
      <category>go</category>
      <category>opensource</category>
      <category>ssh</category>
      <category>cli</category>
    </item>
    <item>
      <title>Connecting the Centrifugo in laravel</title>
      <dc:creator>Denis</dc:creator>
      <pubDate>Fri, 05 Sep 2025 14:46:33 +0000</pubDate>
      <link>https://dev.to/deniskorbakov/connecting-the-centrifugo-in-laravel-2l78</link>
      <guid>https://dev.to/deniskorbakov/connecting-the-centrifugo-in-laravel-2l78</guid>
      <description>&lt;p&gt;In this article, we will look at the integration of the Centrifugo real-time server with the Laravel framework, the basic settings and nuances of operation.&lt;/p&gt;

&lt;p&gt;This article will be more about the implementation on the framework itself than the description of &lt;strong&gt;Centrifugo&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can also find an example of interaction in this &lt;a href=""&gt;template&lt;/a&gt;, which was described in an article about &lt;a href="https://habr.com/ru/articles/902066%20/" rel="noopener noreferrer"&gt;frankenphp+laravel&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Centrifugo&lt;/strong&gt; is a real–time server that supports various transports for connecting clients, including WebSocket, HTTP streaming, Server-Sent Events (SSE), and others. It uses the publish-subscribe pattern for messaging&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It acts as an intermediary (proxy) between your backend and clients (web, mobile applications). Its main task is to deliver messages from the server to clients and between clients instantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What he can do:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;One-way mailing (Push notifications):&lt;/strong&gt; The server can send a message to all subscribed clients or specific users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two-way communication (Pub/Sub):&lt;/strong&gt; Clients can subscribe to channels (topics) and publish messages in them that all other subscribers will receive (if they have rights).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling:&lt;/strong&gt; Easily runs in a cluster (via Redis or Tarantool) to handle the load of millions of connections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliability:&lt;/strong&gt; Restores lost connections and delivers messages that were sent while the client was offline (persistent channels).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checking access rights:&lt;/strong&gt; Connection and publication requests always go through your backend for authorization, which ensures security.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Key Philosophy:&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Centrifugo&lt;/strong&gt; does not replace your backend and database. It takes care of the most difficult and resource—intensive part - maintaining millions of constant connections and efficiently sending messages, while the application logic remains on your server.&lt;/p&gt;

&lt;p&gt;You can find the documentation &lt;a href="https://centrifugal.dev" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploying the Centrifugo:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We will install it via docker&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;dockerfile example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;centrifugo/centrifugo:v6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Base  &lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;dev  &lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; .docker/centrifugo/config-test.json /centrifugo/config.json  &lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["centrifugo", "-c",  "config.json"]  &lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;prod  &lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; .docker/centrifugo/config.json /centrifugo/config.json  &lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["centrifugo", "-c",  "config.json"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we are lifting the container with our service and adding a config with settings.&lt;/p&gt;

&lt;p&gt;we use multistaging by separating the configs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;we store the test config in the repository&lt;/li&gt;
&lt;li&gt;the second one is on the server or in env github/gitlab&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this separation is necessary because it contains the encryption keys and the password from the admin panel.&lt;/p&gt;

&lt;p&gt;example of a test configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"token_hmac_secret_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-secret-here"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"admin_password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"strong-password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"admin_secret"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"admin-secret"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"api_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"channels"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"news"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"publish"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;clients&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;publish&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"subscribe"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;clients&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;subscribe&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"history_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Store&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;last&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;messages&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"history_ttl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5m"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Keep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;history&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;minutes&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user:$user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Personal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;channel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;user)&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"subscribe"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"publish"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Only&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;publish&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chat:room-#rooms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Channel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;rooms&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"presence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;presence&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;tracking&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"join_leave"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Send&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;input/output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;events&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to generate your config, you must go into the container and enter the command - &lt;code&gt;centrifugo genconfig&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;or you can take an example from &lt;a href="https://centrifugal.dev/docs/getting-started/quickstart" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;token_hmac_secret_key&lt;/strong&gt;: Mandatory secret for signing clients' JWT tokens.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;admin_password&lt;/strong&gt; / &lt;strong&gt;admin_secret&lt;/strong&gt;: The password for entering the admin panel and the secret for the admin API.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;api_key&lt;/strong&gt;: The key for calling the Server API (for publishing from the backend).  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;engine&lt;/strong&gt;: Select an engine for data storage (memory, redis, tarantool). The default value is memory.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;presence&lt;/strong&gt;: Enables global channel presence tracking.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;history_meta_ttl&lt;/strong&gt;: How long to store the meta information of the message history.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;namespaces&lt;/strong&gt;: A more advanced alternative to channels for grouping channel settings.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next, we add the service to docker compose (your description of the services may vary):&lt;/p&gt;

&lt;p&gt;dev - stage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;centrifugo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
      &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
        &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.docker/centrifugo/Dockerfile&lt;/span&gt;  
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;  
      &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;centrifugo.${APP_NAMESPACE}&lt;/span&gt;  
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;8089:8000'&lt;/span&gt;  
      &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;  
      &lt;span class="na"&gt;ulimits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
        &lt;span class="na"&gt;nofile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
          &lt;span class="na"&gt;soft&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;65535&lt;/span&gt;  
          &lt;span class="na"&gt;hard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;65535&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;prod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;centrifugo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
      &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
        &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.docker/centrifugo/Dockerfile&lt;/span&gt;  
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod&lt;/span&gt;  
      &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;centrifugo.${APP_NAMESPACE}&lt;/span&gt;  
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;8089:8000'&lt;/span&gt;  
      &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;  
      &lt;span class="na"&gt;ulimits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
        &lt;span class="na"&gt;nofile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
          &lt;span class="na"&gt;soft&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;65535&lt;/span&gt;  
          &lt;span class="na"&gt;hard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;65535&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This service operates on the internal 8000 port, so we open the port available to you for external access.&lt;/p&gt;

&lt;p&gt;Next, we launch our docker compose and start adding variables to env and installing the sdk library for Laravel.&lt;/p&gt;

&lt;p&gt;Installing the sdk that is listed in the dock - at this stage it is officially recommended to install this &lt;a href="https://github.com/denis660/laravel-centrifugo" rel="noopener noreferrer"&gt;repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow the installation instructions from the README file&lt;/p&gt;

&lt;p&gt;After installation, we check that we have correctly specified the variables in env&lt;/p&gt;

&lt;p&gt;After installation, we check that we have correctly specified the variables in env&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BROADCAST_DRIVER=centrifugo  
BROADCAST_CONNECTION=centrifugo

CENTRIFUGO_TOKEN_HMAC_SECRET_KEY="your_secret_key"  
CENTRIFUGO_API_KEY="your_api_key"

CENTRIFUGO_URL=http://centrifugo:8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;BROADCAST_DRIVER &amp;amp; BROADCAST_CONNECTION&lt;/strong&gt; - the driver and connection that we generated based on the README library&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CENTRIFUGO_TOKEN_HMAC_SECRET_KEY&lt;/strong&gt; - token from the config &lt;strong&gt;token_hmac_secret_key&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CENTRIFUGO_API_KEY&lt;/strong&gt; - token from the config &lt;strong&gt;api_key&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CENTRIFUGO_URL&lt;/strong&gt; - specify our service and its internal port&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now we can start writing events.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;you can find out how events and broadcast work from &lt;a href="https://laravel.com/docs/12.x/broadcasting" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;An example of using the Centrifugo:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1) An example of sending an event via cron&lt;/p&gt;

&lt;p&gt;In this example, we will send the current date to the public channel every 5 seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example command:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;  

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Console\Commands&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Events\ExampleEvent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Console\Command&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Console\Attribute\AsCommand&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;AsCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'example:run'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;  
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleCommand&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;  
&lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nc"&gt;ExampleEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we trigger an event to send a date.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example of sending directly via the sdk:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;  

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Console\Commands&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Console\Attribute\AsCommand&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;denis660\Centrifugo\Centrifugo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Console\Command&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;AsCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'centrifugo:run'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;  
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CentrifugoCommand&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;  
&lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Centrifugo&lt;/span&gt; &lt;span class="nv"&gt;$centrifugo&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nv"&gt;$centrifugo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'example'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'time'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()]);&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We register the Centrifugo class through the command argument and call the &lt;code&gt;publish&lt;/code&gt; method&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;publish&lt;/strong&gt; - takes the name of the channel as the first argument, and the second argument is an array with the data that we want to transfer to the channel.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now we can register the command and specify at what time it will be executed - &lt;a href="https://laravel.com/docs/12.x/scheduling" rel="noopener noreferrer"&gt;example of working with cron in Laravel&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An example of registering a team in kron:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;// routes/console.php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;  

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Console\Commands\ExampleCommand&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Schedule&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

&lt;span class="nc"&gt;Schedule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ExampleCommand&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;everyFiveSeconds&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we describe the event that will be executed in our team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example of an event:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;  

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Events&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Enums\ChannelName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Carbon\Carbon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Bus\Queueable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Contracts\Broadcasting\ShouldBroadcast&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Broadcasting\InteractsWithSockets&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Foundation\Events\Dispatchable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Broadcasting\Channel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleEvent&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ShouldBroadcast&lt;/span&gt;  
&lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Dispatchable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;InteractsWithSockets&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Queueable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;broadcastOn&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="c1"&gt;// using enum instead of the magic value  &lt;/span&gt;
                &lt;span class="nc"&gt;ChannelName&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Example&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;  
            &lt;span class="p"&gt;),&lt;/span&gt;  
        &lt;span class="p"&gt;];&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;broadcastWith&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  
            &lt;span class="s1"&gt;'date'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Carbon&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Y-m-d H:i:s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
        &lt;span class="p"&gt;];&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logic for sending events to the channel is ready&lt;/p&gt;

&lt;p&gt;The photo below describes the authorization and subscriptions to the channel, below we can observe the responses from the channel according to the described logic - that every 5 seconds we get the current time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I will describe about channel authorization and testing through postman below&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb6j3u529zbea0662ex5v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb6j3u529zbea0662ex5v.png" alt="photo" width="800" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2) An example of sending a message to a chat&lt;/p&gt;

&lt;p&gt;In this example, we will analyze sending events for messenger messages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example of a controller for creating a message:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;  

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Controllers\Api\Message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MessageController&lt;/span&gt;  
&lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;MessageService&lt;/span&gt; &lt;span class="nv"&gt;$messageService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;StoreDTO&lt;/span&gt; &lt;span class="nv"&gt;$storeDTO&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nv"&gt;$chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Chat&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;findOrFail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chatId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
        &lt;span class="nc"&gt;Gate&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'show'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$chat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;messageService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$storeDTO&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the controller, we search for a chat from the database and check if we have access, then we return a response from the service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An example of a message service:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;  

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Services\Message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MessageService&lt;/span&gt;  
&lt;span class="p"&gt;{&lt;/span&gt;    
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Chat&lt;/span&gt; &lt;span class="nv"&gt;$chat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;StoreDTO&lt;/span&gt; &lt;span class="nv"&gt;$storeDTO&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nv"&gt;$message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;  
                &lt;span class="s1"&gt;'chat_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$chat&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                &lt;span class="s1"&gt;'user_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  
                &lt;span class="s1"&gt;'message'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$storeDTO&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
            &lt;span class="p"&gt;]);&lt;/span&gt;  

        &lt;span class="nc"&gt;MessageCreated&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ShowDTO&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we create chat messages, call a reply to create messages, and return data about the created message.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example of an event for a created message:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;  

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Events\Message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\DTO\Event\Message\CreateDTO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Enums\Channel\ChannelName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Enums\Event\MessageEventName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\Message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Broadcasting\Channel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Broadcasting\InteractsWithSockets&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Contracts\Broadcasting\ShouldBroadcast&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Foundation\Events\Dispatchable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Queue\SerializesModels&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MessageCreated&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ShouldBroadcast&lt;/span&gt;  
&lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Dispatchable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;InteractsWithSockets&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;SerializesModels&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;Message&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;  
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;broadcastAs&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="c1"&gt;//enum for the event name&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;MessageEventName&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;MessageCreated&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;broadcastOn&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Channel&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="c1"&gt;//enum through which we create the chat channel name.11  &lt;/span&gt;
            &lt;span class="nc"&gt;ChannelName&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Chat&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;byId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
        &lt;span class="p"&gt;);&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;broadcastWith&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;CreateDTO&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this event, we accept the message model and output the array to the channel.&lt;/p&gt;

&lt;p&gt;It is also mandatory to use a public channel and not register channel authorization, I will explain why below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An example that we will receive when sending a message:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6611tva3dm9rqj58ysvl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6611tva3dm9rqj58ysvl.png" alt="photo" width="800" height="583"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When sending an event, it was received by all chat participants.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nuances that are worth clarifying:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Centrifugo does not integrate with Laravel's channel authorization mechanism out of the box. Instead, it uses its own, more flexible authorization mechanism based on JWT tokens that are generated by your backend - &lt;strong&gt;that is, the code below will not work.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Broadcast&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'orders.{orderId}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$orderId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;findOrNew&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$orderId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Private channels in Centrifugo are implemented not through the Laravel mechanism, but by sending a request to your backend (subscription process) or pre-generating a token with access rights.&lt;/p&gt;

&lt;p&gt;We also cannot send through events to all users of the channel except the sender himself, based on the conclusion above - &lt;strong&gt;the code below will not work&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Events\OrderShipmentStatusUpdated&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OrderShipmentStatusUpdated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$update&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toOthers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to implement this logic, you will have to write it yourself.&lt;/p&gt;

&lt;p&gt;If we do not want to implement this logic, we will have to process the event skipping at the sender on the client.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WS testing&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;You can do this using the sdk for the client - &lt;a href=""&gt;examples&lt;/a&gt; or using Postman or its analogues - &lt;a href=""&gt;example from the documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For testing via Postman, specify the parameter in the url &lt;code&gt;cf_ws_frame_ping_pong=true&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The link will look like this:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ws://localhost:8089/connection/websocket?cf_ws_frame_ping_pong=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we will describe the interaction using json.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example of authorization in the Centrifugo:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"connect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your_auth_token"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;id&lt;/strong&gt; - record identifier&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;connect&lt;/strong&gt; - the type of action that we specify when connecting to the Centrifugo&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;token&lt;/strong&gt; - a token for user authorization (in the example below I will show an example of creating an authorization token)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Example of channel subscription:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here we subscribe to the public channel &lt;code&gt;public-channel&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"subscribe"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"channel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"public-channel"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To subscribe to a private channel, you must specify another channel authorization token.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"subscribe"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"channel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"private-channel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your_channel_token"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;id&lt;/strong&gt; - record identifier&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;subscribe&lt;/strong&gt; - the type of action specified when subscribing to the channel in the Centrifugo&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;channel&lt;/strong&gt; - the key that contains the name of the channel we want to subscribe to&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;token&lt;/strong&gt; - a token for user authorization (in the example below I will show an example of creating an authorization token)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the end, you should get the following&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwtb45fzaqx1soklvz80w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwtb45fzaqx1soklvz80w.png" alt="photo" width="800" height="176"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we need to click the &lt;code&gt;Connect&lt;/code&gt; button&lt;/p&gt;

&lt;p&gt;After we connect, we need to send our instructions by clicking on the &lt;code&gt;send&lt;/code&gt; button&lt;/p&gt;

&lt;p&gt;After all the steps, we should see the given answers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7bypp9hlb3y0ba8krm5b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7bypp9hlb3y0ba8krm5b.png" alt="photo" width="800" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After sending the events, he authorizes the user and subscribes to the channel.&lt;/p&gt;

&lt;p&gt;In case of errors, you can safely find their description by error code &lt;a href="https://centrifugal.dev/docs/server/codes" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion about the Centrifugo&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As a result, we have that the sdk gives us event support - we send event data to the specified channel - and all the rest of the logic falls on the centrifugo.&lt;/p&gt;

&lt;p&gt;We will need to write the authorization methods for our backend application and the centrifugo ourselves - it can support authorization both through sessions and through a token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example of creating a token for authorization in the Centrifugo:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example of an authorization controller:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;  

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Controllers\Api&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthController&lt;/span&gt;  
&lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;AuthService&lt;/span&gt; &lt;span class="nv"&gt;$authService&lt;/span&gt;  
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;WSAuth&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;findOrFail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;  

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;authService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;WSAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example of the AuthService Service:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;  

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Services\Auth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthService&lt;/span&gt;  
&lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;Centrifugo&lt;/span&gt; &lt;span class="nv"&gt;$centrifugo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;WSAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nv"&gt;$token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;centrifugo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;generateConnectionToken&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TokenDTO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we use the &lt;code&gt;Centrifugo&lt;/code&gt; class, which provides us with the sdk and calls the &lt;code&gt;generateConnectionToken&lt;/code&gt; method from it, which accepts the string UserId, at the end I output the token itself.&lt;/p&gt;

&lt;p&gt;After that, we can process this handle for the client and use the generated token for authorization in the &lt;code&gt;Centrifugo&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Private channels:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Based on the fact that we cannot use private laravel channels, we must protect them in another way.&lt;/p&gt;

&lt;p&gt;To do this, Centrifugo provides an API to authorize the client to a private channel through a token - we will generate a token in our backend application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An example of creating a token for authorization in a private channel:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;generatePrivateToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="nv"&gt;$token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;centrifugo&lt;/span&gt;  
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;generatePrivateChannelToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
            &lt;span class="s1"&gt;'your_private_channel'&lt;/span&gt;  
        &lt;span class="p"&gt;);&lt;/span&gt;  

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TokenDTO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By analogy with user authorization in the Centrifugo, we can generate a token for a private channel.&lt;/p&gt;

&lt;p&gt;Also, Centrifugo is able to subscribe an authorized user to channels and unsubscribe (it works for both public and private channels), which allows us to manage the process more flexibly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example of channel subscription&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'll show you an example of a user subscribing to all their chats.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example of a controller:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;  

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Controllers\Api\WS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WSController&lt;/span&gt;  
&lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;WSService&lt;/span&gt; &lt;span class="nv"&gt;$wsService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;wsService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;noContent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we get an authorized user and call "WSService"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example of the WSService service:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;  

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Services\WS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WSService&lt;/span&gt;  
&lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;Centrifugo&lt;/span&gt; &lt;span class="nv"&gt;$centrifugo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;ChannelService&lt;/span&gt; &lt;span class="nv"&gt;$channelService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$currentUser&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nv"&gt;$chatChannels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;channelService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;chats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$currentUser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chatChannels&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$channel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;centrifugo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                &lt;span class="nv"&gt;$channel&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$currentUser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;  
            &lt;span class="p"&gt;);&lt;/span&gt;  
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the &lt;code&gt;ChannelService&lt;/code&gt; we get his current chats in the already in the loop we subscribe each chat to the user using the &lt;code&gt;subscribe&lt;/code&gt; method&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example of unsubscribing from a channel&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here in the example, we will unsubscribe when each participant's group is removed from the Centrifugo&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Chat&lt;/span&gt; &lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Collection&lt;/span&gt; &lt;span class="nv"&gt;$members&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;  
&lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="nv"&gt;$members&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$member&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;centrifugo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
            &lt;span class="nc"&gt;ChannelName&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Chat&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;byId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$member&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;  
        &lt;span class="p"&gt;);&lt;/span&gt;  
    &lt;span class="p"&gt;});&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The result:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We have set up and deployed Centrifugo in a laravel project, shown you examples of how to work with it, and also added it to the &lt;a href="https://dev.toinsert%20link"&gt;template&lt;/a&gt; so that you can easily use it and start your wonderful projects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/deniskorbakov" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; - I will be glad to receive your subscription to me in github&lt;/p&gt;

&lt;p&gt;Thank you for reading this article.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>laravel</category>
      <category>php</category>
    </item>
    <item>
      <title>Peck is a utility for your PHP projects</title>
      <dc:creator>Denis</dc:creator>
      <pubDate>Wed, 27 Aug 2025 14:32:30 +0000</pubDate>
      <link>https://dev.to/deniskorbakov/peck-is-a-utility-for-your-php-projects-557c</link>
      <guid>https://dev.to/deniskorbakov/peck-is-a-utility-for-your-php-projects-557c</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/peckphp/peck" rel="noopener noreferrer"&gt;Peck&lt;/a&gt;&lt;/strong&gt; is a powerful CLI tool designed to identify wording or writing errors in your codebase: file names, class names, method names, property names, documents, and more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It often happens that we can incorrectly name a class name, variable name, or file name. It is not always possible to notice this due to various factors, and then there may be typos in the project, which can lead to code complexity and confusion in reading.&lt;/p&gt;

&lt;p&gt;Therefore, such a tool will be able to solve this problem, as it can be easily implemented into your pipline, which will allow you to transfer the spell-checking process to a machine, making your life easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  The installation process
&lt;/h2&gt;

&lt;p&gt;To install the package, you will need php 8.2 and higher, as well as &lt;strong&gt;GNU Aspell&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GNU Aspell&lt;/strong&gt; is a free spell—checking program designed to replace Ispell and serve as a standard spell-checking tool in the GNU operating system&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Adding this utility to our Dockerfile with php:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="se"&gt;\ &lt;/span&gt; 
    &amp;amp;&amp;amp; DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \  
    git \  
    unzip \  
    librabbitmq-dev \  
    libpq-dev \  
    aspell  \  
    aspell-en \  
    supervisor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And already &lt;code&gt;aspell&lt;/code&gt; and &lt;code&gt;aspell-en&lt;/code&gt; have been added to the necessary packages&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;aspell-en&lt;/strong&gt; - contains spelling checker files for English&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Install the package via composer:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require peckphp/peck &lt;span class="nt"&gt;--dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Initialize the configuration for the package:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./vendor/bin/peck &lt;span class="nt"&gt;--init&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After Initialization, the file &lt;code&gt;peck.json&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"preset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"laravel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ignore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"words"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"namespace"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"app/MyFolder"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"app/MyFile.php"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example provided by the developers&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Description of the configuration keys:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;words&lt;/strong&gt; - specify the words that will not be used for spell checking&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;paths&lt;/strong&gt; - specify the paths where folders or files will be ignored&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;in my project, I had to add more words to ignore:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  
  &lt;/span&gt;&lt;span class="nl"&gt;"preset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"laravel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
  &lt;/span&gt;&lt;span class="nl"&gt;"ignore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"words"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="s2"&gt;"php"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="s2"&gt;"dto"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="s2"&gt;"sha"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="s2"&gt;"centrifugo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="s2"&gt;"js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="s2"&gt;"postcss"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="s2"&gt;"phpcs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="s2"&gt;"makefile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="s2"&gt;"cors"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="s2"&gt;"filesystems"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="s2"&gt;"websocket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="s2"&gt;"favicon"&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="s2"&gt;"/public/vendor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="s2"&gt;"/public/fonts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="s2"&gt;"/public/js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="s2"&gt;"/lang/vendor"&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;  
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also move our config to another location and explicitly specify it using the command with the &lt;code&gt;--config&lt;/code&gt; flag, where its argument will be the path to the file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./vendor/bin/peck &lt;span class="nt"&gt;--config&lt;/span&gt; relative/path/to/peck.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Now we can run the verification package:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./vendor/bin/peck
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;I found an error in the file name in the project.:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Misspelling  ./documentation/code_standart.md: &lt;span class="s1"&gt;'standart'&lt;/span&gt;  

Did you mean: standard, standout, standards, stander
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Also, for convenience, add this command to the &lt;code&gt;composer&lt;/code&gt; scripts:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"word-check"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@php vendor/bin/peck"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you can already call this check with the command - &lt;code&gt;composer word-check&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;Although this package does not have a stable release, you can still try to install it in your projects and check for the correctness of expressions in your code base.&lt;/p&gt;

&lt;p&gt;It's up to you to use it or not, the most important thing is to improve the quality of the codebase using the tools that you consider necessary.&lt;/p&gt;

&lt;p&gt;I also implemented this tool in my project - &lt;a href="https://github.com/deniskorbakov/laravel-12-frankenphp-docker" rel="noopener noreferrer"&gt;laravel + frankenphp template&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://github.com/deniskorbakov" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; - I will be glad to receive your subscription to me in github&lt;/p&gt;

&lt;p&gt;Thank you for reading this article.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>php</category>
      <category>laravel</category>
    </item>
    <item>
      <title>Creating our own package in php</title>
      <dc:creator>Denis</dc:creator>
      <pubDate>Sat, 09 Aug 2025 17:43:56 +0000</pubDate>
      <link>https://dev.to/deniskorbakov/creating-our-own-package-in-php-5hdd</link>
      <guid>https://dev.to/deniskorbakov/creating-our-own-package-in-php-5hdd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article is intended for those who have never done package projects in bare PHP for their needs before.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I want to share with you my experience, which I encountered, and provide a template that I wrote for packages/projects.:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/deniskorbakov/skeleton-php-docker" rel="noopener noreferrer"&gt;https://github.com/deniskorbakov/skeleton-php-docker&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I will be very glad to receive an asterisk on GitHub and feedback after reading the article!&lt;/p&gt;
&lt;h3&gt;
  
  
  How to build a project
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Project structure
&lt;/h4&gt;

&lt;p&gt;Classic construction of projects written in php&lt;/p&gt;

&lt;p&gt;Example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;,── src # Directory of your package              
,── Dir1 # Directory for internal logic
│   └── PackageClass.php # A class for external use
,── tests # Directory for tests
│   ├─ Fixtures
│   ├─ Unit  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This format is followed in almost all projects — these are key directories. However, you can create additional folders to bring the logic of individual parts of the application into your layers.&lt;/p&gt;

&lt;p&gt;Folder names can be any, but it's better to follow generally accepted practices to make it easier for other developers to navigate your code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example of possible directories:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;scripts/&lt;/code&gt; — scripts for assembly, deployment, or other tasks.
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;assets/&lt;/code&gt; — static files (styles, scripts, images).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.docker/&lt;/code&gt; — Docker configuration (Dockerfile, Docker Compose).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;public/&lt;/code&gt; is the entry point for the web server (if the package includes a web interface).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;config/&lt;/code&gt;— application configuration files.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Composer
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Basic composer structure
&lt;/h4&gt;

&lt;p&gt;You can read more about the setup &lt;a href="https://habr.com/ru/articles/439200%20/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key parameters when creating a package:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;name/description/keywords:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deniskorbakov/skeleton-php-docker"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
&lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A skeleton php libs with docker"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
&lt;/span&gt;&lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"php"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"skeleton"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"package"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"docker"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;name' is the package name in the&lt;/code&gt;vendor/package` format.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;description&lt;/code&gt; — a brief description of the functionality.&lt;/li&gt;
&lt;li&gt;`keywords' — tags for searching in Packagist.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;require:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"require"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"php"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=8.2"&lt;/span&gt;&lt;span class="w"&gt;  
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Specifies the minimum PHP version and dependencies, without which the package does not work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;require-dev:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"require-dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"pestphp/pest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"phpstan/phpstan"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^2.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"rector/rector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"squizlabs/php_codesniffer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.13"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"slevomat/coding-standard"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^8.20"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"orchestra/testbench"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^10.4"&lt;/span&gt;&lt;span class="w"&gt;  
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dependencies for development (testing, static analysis).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;autoload&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"autoload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"psr-4"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  
        &lt;/span&gt;&lt;span class="nl"&gt;"DenisKorbakov\\SkeletonPhpDocker\\"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/"&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;  
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure auto-upload for your package.&lt;/p&gt;

&lt;h4&gt;
  
  
  Useful commands and scripts
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Commands:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;composer validate&lt;/code&gt; — validating the validity of `composer.json'.&lt;/li&gt;
&lt;li&gt;`composer outdated' — search for outdated dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example scripts:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"tests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pest --stop-on-failure --colors"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"tests-coverage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pest --coverage --min=90"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"tests-mutation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pest --mutate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  
        &lt;/span&gt;&lt;span class="s2"&gt;"@phpstan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
        &lt;/span&gt;&lt;span class="s2"&gt;"@cs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
        &lt;/span&gt;&lt;span class="s2"&gt;"@rector"&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"phpstan"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"phpstan analyse --memory-limit=2G"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"rector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rector"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"cs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"phpcs"&lt;/span&gt;&lt;span class="w"&gt;  
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage: `composer tests' — running tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Static analyzers and linters
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Why are they needed
&lt;/h4&gt;

&lt;p&gt;Each of us wants to stick to good quality from the code, but endless reviews pointing out the same formatting errors can annoy people with each other, and at least linters have become the solution to this problem.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Linter&lt;/strong&gt; is a tool for automatic code analysis for compliance with design standards.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Static analyzers&lt;/strong&gt; help to find errors before running the code, improving its reliability.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Static Analyzer&lt;/strong&gt; is a tool that checks code for errors, vulnerabilities, and antipatterns without executing it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Implementing these tools in older projects can be difficult, so sometimes it's wise to apply them only to modified files.&lt;/p&gt;

&lt;h4&gt;
  
  
  PHPstan
&lt;/h4&gt;

&lt;p&gt;One of the most popular analyzers for PHP. Its main advantage is &lt;strong&gt;strictness levels&lt;/strong&gt; (from 0 to 9). The higher the level, the more thorough the check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Useful plugins:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/TomasVotruba/cognitive-complexity" rel="noopener noreferrer"&gt;cognitive-complexity&lt;/a&gt; — evaluates the complexity of the code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Code complexity&lt;/strong&gt; is a metric that shows how difficult the code is to understand and maintain.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  PHP_CodeSniffer (PHPCS)
&lt;/h4&gt;

&lt;p&gt;Checks the code for compliance with the standards (PSR-12, PSR-2). You can set up your own rules.&lt;/p&gt;

&lt;h4&gt;
  
  
  Rector
&lt;/h4&gt;

&lt;p&gt;A tool for automatic refactoring. For example, it can update the syntax from PHP 7.4 to 8.2.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;You should never trust yourself, much less your code&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Tests help to make sure that the code is working as expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pest
&lt;/h3&gt;

&lt;p&gt;I chose Pest instead of PHPUnit because of the more concise syntax. However, it has a disadvantage: syntax highlighting only works with the &lt;a href="https://pestphp.com/docs/editor-setup" rel="noopener noreferrer"&gt;official plugin&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Actions
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Templates for issue
&lt;/h4&gt;

&lt;p&gt;It is also a very good practice to make templates for which the clients of your application package will be able to compile a bug report or ask about its work.&lt;/p&gt;

&lt;p&gt;Since it is possible to specify the inputs that they should fill in.:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Package version&lt;/li&gt;
&lt;li&gt;Dependency version&lt;/li&gt;
&lt;li&gt;A checkbox to confirm familiarization with other issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There may be many examples, but the most important thing is that there will be no answers from you that the user has provided you with the necessary information to help him&lt;/p&gt;

&lt;p&gt;At the output, we will get this form in the issue:&lt;/p&gt;

&lt;p&gt;An example is a photo&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdd1zwau3giln3e3lux93.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdd1zwau3giln3e3lux93.png" alt=" " width="800" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Release on pakagist
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Description of the process
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://packagist.org/packages/submit" rel="noopener noreferrer"&gt;Packagist&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Provide a link to the repository.&lt;/li&gt;
&lt;li&gt;Wait for moderation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The package will be automatically updated when you push it to &lt;code&gt;main&lt;/code&gt;. For instant updates, use &lt;a href="https://github.com/mnavarrocarter/packagist-update" rel="noopener noreferrer"&gt;Packagist Update&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;An example is a photo&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8p1qy17uhjciv4if54wp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8p1qy17uhjciv4if54wp.png" alt=" " width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Output
&lt;/h3&gt;

&lt;p&gt;I have described the key points of creating the package. I hope this article will help those who have not done this before.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/deniskorbakov/skeleton-php-docker" rel="noopener noreferrer"&gt;Project Template&lt;/a&gt; - you can also improve it by sending PR to it.&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/deniskorbakov" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; - I will be glad to receive your subscription to me and in github&lt;/p&gt;

&lt;p&gt;Thank you for reading this article.&lt;/p&gt;

</description>
      <category>php</category>
      <category>programming</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Laravel + Franken PHP template</title>
      <dc:creator>Denis</dc:creator>
      <pubDate>Sun, 06 Jul 2025 16:11:01 +0000</pubDate>
      <link>https://dev.to/deniskorbakov/laravel-franken-php-template-325p</link>
      <guid>https://dev.to/deniskorbakov/laravel-franken-php-template-325p</guid>
      <description>&lt;h1&gt;
  
  
  php
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Hello everyone, my name is Denis, and I'm a PHP developer.&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;I participate in hackathons with the team &lt;a href="https://%D0%B6%D1%8B%D0%B1%D0%B8%D0%B9%D1%80%D1%8B%D1%80.%D1%80%D1%84/" rel="noopener noreferrer"&gt;жыбийрыр&lt;/a&gt; — and we faced an issue: we didn’t have a ready-made template to work with, forcing us to rewrite the same code repeatedly.  &lt;/p&gt;

&lt;p&gt;This article will cover how I created this template, the challenges I encountered, and my general desire to share the work I’ve done.  &lt;/p&gt;

&lt;p&gt;I want to note that this solution is not ideal, and I welcome constructive criticism in the comments.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/deniskorbakov/laravel-12-frankenphp-docker" rel="noopener noreferrer"&gt;Template repository&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/deniskorbakov" rel="noopener noreferrer"&gt;My GitHub&lt;/a&gt;  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;What’s included in the ready-made template:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configured multi-threaded FrankenPHP server
&lt;/li&gt;
&lt;li&gt;Docker environment setup + multi-stage for local and production
&lt;/li&gt;
&lt;li&gt;Admin panel
&lt;/li&gt;
&lt;li&gt;API documentation for the project
&lt;/li&gt;
&lt;li&gt;System monitoring
&lt;/li&gt;
&lt;li&gt;Basic authentication logic (email verification)
&lt;/li&gt;
&lt;li&gt;WebSocket server
&lt;/li&gt;
&lt;li&gt;Configured pipeline (GitHub Actions)
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why FrankenPHP was chosen:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
I wanted to experiment with a multi-threaded server, so the choice was between RoadRunner and FrankenPHP.  &lt;/p&gt;

&lt;p&gt;FrankenPHP was selected over RoadRunner because it’s easier to deploy and performs comparably in many benchmarks.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech stack:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Laravel 12
&lt;/li&gt;
&lt;li&gt;FrankenPHP
&lt;/li&gt;
&lt;li&gt;Docker/Docker Compose
&lt;/li&gt;
&lt;li&gt;Redis
&lt;/li&gt;
&lt;li&gt;PostgreSQL
&lt;/li&gt;
&lt;li&gt;Laravel Reverb (WebSocket server)
&lt;/li&gt;
&lt;li&gt;Horizon (queue management wrapper)
&lt;/li&gt;
&lt;li&gt;PhpStan / PhpCodeSniffer / Rector (static analyzers)
&lt;/li&gt;
&lt;li&gt;Filament (admin panel)
&lt;/li&gt;
&lt;li&gt;Beszel (lightweight monitoring)
&lt;/li&gt;
&lt;li&gt;Scribe (API documentation)
&lt;/li&gt;
&lt;li&gt;Traefik
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Let’s start with the configured server + Docker:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The server is set up using Laravel Octane + FrankenPHP.  &lt;/p&gt;

&lt;p&gt;Laravel Octane is a provider for running applications on RR, Swoole, and FrankenPHP.  &lt;/p&gt;

&lt;p&gt;It also has excellent documentation for each server, including deployment guides.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here’s the resulting Dockerfile:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;dunglas/frankenphp:1.4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;DEBIAN_FRONTEND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;noninteractive apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    git &lt;span class="se"&gt;\
&lt;/span&gt;    unzip &lt;span class="se"&gt;\
&lt;/span&gt;    librabbitmq-dev &lt;span class="se"&gt;\
&lt;/span&gt;    libpq-dev &lt;span class="se"&gt;\
&lt;/span&gt;    supervisor

&lt;span class="k"&gt;RUN &lt;/span&gt;install-php-extensions &lt;span class="se"&gt;\
&lt;/span&gt;    gd &lt;span class="se"&gt;\
&lt;/span&gt;    pcntl &lt;span class="se"&gt;\
&lt;/span&gt;    opcache &lt;span class="se"&gt;\
&lt;/span&gt;    pdo &lt;span class="se"&gt;\
&lt;/span&gt;    pdo_pgsql &lt;span class="se"&gt;\
&lt;/span&gt;    pgsql &lt;span class="se"&gt;\
&lt;/span&gt;    redis &lt;span class="se"&gt;\
&lt;/span&gt;    zip

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=composer:2.8 /usr/bin/composer /usr/local/bin/composer&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=node:23 /usr/local/lib/node_modules /usr/local/lib/node_modules&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=node:23 /usr/local/bin/node /usr/local/bin/node&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;dev&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./.docker/supervisor/supervisord.dev.conf /etc/supervisor/conf.d/supervisord.conf&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/conf.d/supervisord.conf"]&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;prod&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./.docker/supervisor/supervisord.prod.conf /etc/supervisor/conf.d/supervisord.conf&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/conf.d/supervisord.conf"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Dockerfile is compact, but it can be expanded in the future using multi-stage builds for both local development and production.  &lt;/p&gt;

&lt;p&gt;The server, queues, WebSocket, and cron tasks are managed via Supervisor.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supervisor config:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;supervisord&lt;/span&gt;]
&lt;span class="n"&gt;user&lt;/span&gt;=&lt;span class="n"&gt;root&lt;/span&gt;
&lt;span class="n"&gt;nodaemon&lt;/span&gt;=&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;logfile&lt;/span&gt;=/&lt;span class="n"&gt;dev&lt;/span&gt;/&lt;span class="n"&gt;stdout&lt;/span&gt;
&lt;span class="n"&gt;logfile_maxbytes&lt;/span&gt;=&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;pidfile&lt;/span&gt;=/&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;run&lt;/span&gt;/&lt;span class="n"&gt;supervisord&lt;/span&gt;.&lt;span class="n"&gt;pid&lt;/span&gt;

[&lt;span class="n"&gt;program&lt;/span&gt;:&lt;span class="n"&gt;octane&lt;/span&gt;]
&lt;span class="n"&gt;command&lt;/span&gt;=&lt;span class="n"&gt;php&lt;/span&gt; /&lt;span class="n"&gt;app&lt;/span&gt;/&lt;span class="n"&gt;artisan&lt;/span&gt; &lt;span class="n"&gt;octane&lt;/span&gt;:&lt;span class="n"&gt;frankenphp&lt;/span&gt; --&lt;span class="n"&gt;watch&lt;/span&gt;
&lt;span class="n"&gt;autostart&lt;/span&gt;=&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;autorestart&lt;/span&gt;=&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;stdout_events_enabled&lt;/span&gt;=&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;stderr_events_enabled&lt;/span&gt;=&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;stdout_logfile&lt;/span&gt;=/&lt;span class="n"&gt;dev&lt;/span&gt;/&lt;span class="n"&gt;stdout&lt;/span&gt;
&lt;span class="n"&gt;stdout_logfile_maxbytes&lt;/span&gt;=&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;stderr_logfile&lt;/span&gt;=/&lt;span class="n"&gt;dev&lt;/span&gt;/&lt;span class="n"&gt;stderr&lt;/span&gt;
&lt;span class="n"&gt;stderr_logfile_maxbytes&lt;/span&gt;=&lt;span class="m"&gt;0&lt;/span&gt;

[&lt;span class="n"&gt;program&lt;/span&gt;:&lt;span class="n"&gt;horizon&lt;/span&gt;]
&lt;span class="n"&gt;command&lt;/span&gt;=&lt;span class="n"&gt;php&lt;/span&gt; /&lt;span class="n"&gt;app&lt;/span&gt;/&lt;span class="n"&gt;artisan&lt;/span&gt; &lt;span class="n"&gt;horizon&lt;/span&gt;
&lt;span class="n"&gt;autostart&lt;/span&gt;=&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;autorestart&lt;/span&gt;=&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;stdout_events_enabled&lt;/span&gt;=&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;stderr_events_enabled&lt;/span&gt;=&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;stdout_logfile&lt;/span&gt;=/&lt;span class="n"&gt;dev&lt;/span&gt;/&lt;span class="n"&gt;stdout&lt;/span&gt;
&lt;span class="n"&gt;stdout_logfile_maxbytes&lt;/span&gt;=&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;stderr_logfile&lt;/span&gt;=/&lt;span class="n"&gt;dev&lt;/span&gt;/&lt;span class="n"&gt;stderr&lt;/span&gt;
&lt;span class="n"&gt;stderr_logfile_maxbytes&lt;/span&gt;=&lt;span class="m"&gt;0&lt;/span&gt;

[&lt;span class="n"&gt;program&lt;/span&gt;:&lt;span class="n"&gt;schedule&lt;/span&gt;]
&lt;span class="n"&gt;command&lt;/span&gt;=&lt;span class="n"&gt;php&lt;/span&gt; /&lt;span class="n"&gt;app&lt;/span&gt;/&lt;span class="n"&gt;artisan&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;:&lt;span class="n"&gt;run&lt;/span&gt;
&lt;span class="n"&gt;autostart&lt;/span&gt;=&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;autorestart&lt;/span&gt;=&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;stdout_events_enabled&lt;/span&gt;=&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;stderr_events_enabled&lt;/span&gt;=&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;stdout_logfile&lt;/span&gt;=/&lt;span class="n"&gt;dev&lt;/span&gt;/&lt;span class="n"&gt;stdout&lt;/span&gt;
&lt;span class="n"&gt;stdout_logfile_maxbytes&lt;/span&gt;=&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;stderr_logfile&lt;/span&gt;=/&lt;span class="n"&gt;dev&lt;/span&gt;/&lt;span class="n"&gt;stderr&lt;/span&gt;
&lt;span class="n"&gt;stderr_logfile_maxbytes&lt;/span&gt;=&lt;span class="m"&gt;0&lt;/span&gt;

[&lt;span class="n"&gt;program&lt;/span&gt;:&lt;span class="n"&gt;reverb&lt;/span&gt;]
&lt;span class="n"&gt;command&lt;/span&gt;=&lt;span class="n"&gt;php&lt;/span&gt; /&lt;span class="n"&gt;app&lt;/span&gt;/&lt;span class="n"&gt;artisan&lt;/span&gt; &lt;span class="n"&gt;reverb&lt;/span&gt;:&lt;span class="n"&gt;start&lt;/span&gt;
&lt;span class="n"&gt;autostart&lt;/span&gt;=&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;autorestart&lt;/span&gt;=&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;stdout_events_enabled&lt;/span&gt;=&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;stderr_events_enabled&lt;/span&gt;=&lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;stdout_logfile&lt;/span&gt;=/&lt;span class="n"&gt;dev&lt;/span&gt;/&lt;span class="n"&gt;stdout&lt;/span&gt;
&lt;span class="n"&gt;stdout_logfile_maxbytes&lt;/span&gt;=&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;stderr_logfile&lt;/span&gt;=/&lt;span class="n"&gt;dev&lt;/span&gt;/&lt;span class="n"&gt;stderr&lt;/span&gt;
&lt;span class="n"&gt;stderr_logfile_maxbytes&lt;/span&gt;=&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Locally, Traefik is used as a reverse proxy to handle port forwarding.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;traefik&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik:v2.10&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik.${APP_NAMESPACE}&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--api.insecure=true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--providers.docker=true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--entrypoints.web.address=:80&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
  &lt;span class="na"&gt;php&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.docker/php/Dockerfile&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.enable=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.${APP_NAMESPACE}.rule=Host(`${APP_HOST:-localhost}`)"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.services${APP_NAMESPACE}.loadbalancer.server.port=${APP_PORT:-8000}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Two key nuances to highlight:&lt;/strong&gt;  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Hot reload mode for the server&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alpine images&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;About Hot Reload:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
FrankenPHP can operate in two modes:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Production mode&lt;/strong&gt; – The server maintains state and requires a restart to reflect changes (resource-efficient).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Development mode&lt;/strong&gt; – The server detects changes and updates automatically (slower, suitable for local development).
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In development mode, Node.js must be installed in the container since file watching relies on a JS library.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About Alpine images:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The documentation clearly explains why they should be avoided:  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The static binaries we provide, as well as the Alpine Linux variant of the official Docker images, use the musl libc library.&lt;br&gt;&lt;br&gt;
PHP is known to perform significantly slower with this library compared to the traditional GNU libc, especially when compiled in ZTS mode (thread-safe mode), which is required for FrankenPHP.&lt;br&gt;&lt;br&gt;
Additionally, some bugs only appear when using musl.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Admin Panel:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
We use Filament for projects because of its clear documentation, sleek interface, and rapid admin panel development capabilities.  &lt;/p&gt;

&lt;p&gt;However, it’s not ideal for large-scale projects due to performance issues.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API Documentation:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A crucial tool for us, as we often needed to showcase endpoints to stakeholders and reduce unnecessary questions from frontend developers about request structures.  &lt;/p&gt;

&lt;p&gt;The best solution for Laravel currently is &lt;a href="https://scribe.knuckles.wtf/laravel/" rel="noopener noreferrer"&gt;Scribe&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages of this library:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auto-generates endpoints
&lt;/li&gt;
&lt;li&gt;Auto-generates query/URL/body params
&lt;/li&gt;
&lt;li&gt;Fine-grained config customization
&lt;/li&gt;
&lt;li&gt;Theme customization
&lt;/li&gt;
&lt;li&gt;Explicit parameter specification
&lt;/li&gt;
&lt;li&gt;Attribute support
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Auto-generation works best with Laravel’s built-in &lt;code&gt;Request&lt;/code&gt; and &lt;code&gt;Resource&lt;/code&gt; classes.  &lt;/p&gt;

&lt;p&gt;Since the template uses DTOs from &lt;a href="https://spatie.be/docs/laravel-data/v4/introduction" rel="noopener noreferrer"&gt;Spatie’s Laravel-Data&lt;/a&gt;, manual parameter descriptions are necessary.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monitoring:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Initially, I considered the popular stack: Grafana, Loki, Prometheus, and Promtail.&lt;br&gt;&lt;br&gt;
But I realized it would be overkill for our needs.  &lt;/p&gt;

&lt;p&gt;Instead, I opted for a simpler yet functional solution: &lt;a href="https://github.com/henrygd/beszel" rel="noopener noreferrer"&gt;Beszel&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
It was perfect for me—a clean interface and easy setup.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Basic Authentication:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A recurring headache was rewriting authentication for each hackathon and integrating it with the frontend.  &lt;/p&gt;

&lt;p&gt;So, I decided to include authentication + email verification in the template.&lt;br&gt;&lt;br&gt;
A role system was also added, including a "developer" role for accessing system services like the Horizon dashboard.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example auth service:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Services\Controllers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Validation\ValidationException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Hash&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\DTO\User\UserAuthShowDTO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\DTO\Auth\AuthRegisterDTO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\DTO\Auth\AuthLoginDTO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/** @return array&amp;lt;string, mixed&amp;gt; */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;AuthRegisterDTO&lt;/span&gt; &lt;span class="nv"&gt;$authRegisterDTO&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'name'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$authRegisterDTO&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'role'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$authRegisterDTO&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'email'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$authRegisterDTO&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'password'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Hash&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$authRegisterDTO&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;UserAuthShowDTO&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * @return array&amp;lt;string, mixed&amp;gt;
     * @throws ValidationException
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;AuthLoginDTO&lt;/span&gt; &lt;span class="nv"&gt;$authLoginDTO&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$authLoginDTO&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;firstOrFail&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nc"&gt;Hash&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$authLoginDTO&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;ValidationException&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withMessages&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'bad credentials'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;UserAuthShowDTO&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;WebSocket Server:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
In our last hackathon, we needed WebSockets for real-time race data transmission.  &lt;/p&gt;

&lt;p&gt;So, I added it to the template to save time in the future.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Currently, the best options for WebSocket servers are:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Centrifugo
&lt;/li&gt;
&lt;li&gt;Laravel Reverb
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best PHP solution for WebSockets is &lt;strong&gt;Centrifugo&lt;/strong&gt;—highly performant and great for large projects.&lt;br&gt;&lt;br&gt;
However, I struggled to set it up quickly during the hackathon.  &lt;/p&gt;

&lt;p&gt;Instead, I included &lt;strong&gt;Laravel Reverb&lt;/strong&gt; in the template—a decent choice for smaller services. It can later be scaled using queues for better performance.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Actions:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Configuring pipelines was straightforward, given the abundance of online examples. Here’s what I ended up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEPLOY AND BUILD&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;coding-standard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Coding Standard&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup PHP&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shivammathur/setup-php@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;php-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;8.4'&lt;/span&gt;
          &lt;span class="na"&gt;coverage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;none&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get composer cache directory&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;composer-cache&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo "dir=$(composer config cache-files-dir)" &amp;gt;&amp;gt; $GITHUB_OUTPUT&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cache composer dependencies&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.composer-cache.outputs.dir }}&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}&lt;/span&gt;
          &lt;span class="na"&gt;restore-keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;${{ runner.os }}-composer-&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;composer install --no-progress --no-suggest --prefer-dist --no-interaction --ignore-platform-reqs&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check coding style&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;composer cs-check&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check code rector&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;composer cs-rector&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Perform a static analysis of the code base&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./vendor/bin/phpstan analyse --memory-limit=2G&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;php artisan test&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;ubuntu-latest&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deniskorbakov&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;coding-standard&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/main'&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4.2.2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Push to server&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appleboy/ssh-action@master&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SERVER_IP }}&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SERVER_USERNAME }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SERVER_PASSWORD }}&lt;/span&gt;
          &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;cd ${{ secrets.PROJECT_PATH }}&lt;/span&gt;
            &lt;span class="s"&gt;make update-project&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One job checks code quality using static analyzers, and another deploys if changes are merged into &lt;code&gt;main&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;I moved all necessary commands into a &lt;code&gt;Makefile&lt;/code&gt; to avoid overly long configs and to abstract deployment logic.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Makefile:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="k"&gt;include&lt;/span&gt;&lt;span class="sx"&gt; .env&lt;/span&gt;

&lt;span class="c"&gt;# a set of commands for updating a project in production
&lt;/span&gt;&lt;span class="nl"&gt;update-project&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;pull composer-install db-migrate build-front rm-images build-prod doc-generate restart&lt;/span&gt;

&lt;span class="c"&gt;# a set of commands to initialize a project locally
&lt;/span&gt;&lt;span class="nl"&gt;init&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;build composer-install build-front key-generate storage-link db-migrate seed doc-generate restart build-wait&lt;/span&gt;

&lt;span class="c"&gt;# a set of commands for initializing a project on production
&lt;/span&gt;&lt;span class="nl"&gt;init-prod&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;build-prod composer-install build-front key-generate storage-link db-migrate seed doc-generate restart build-prod&lt;/span&gt;

&lt;span class="nl"&gt;build&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Building containers"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;docker compose &lt;span class="nt"&gt;--env-file&lt;/span&gt; .env up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;span class="nl"&gt;build-wait&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Building containers"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;docker compose &lt;span class="nt"&gt;--env-file&lt;/span&gt; .env up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt; &lt;span class="nt"&gt;--wait&lt;/span&gt;
&lt;span class="nl"&gt;up&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Starting containers"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;docker compose &lt;span class="nt"&gt;--env-file&lt;/span&gt; .env up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--remove-orphans&lt;/span&gt;
&lt;span class="nl"&gt;build-prod&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Building containers"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.prod.yml &lt;span class="nt"&gt;--env-file&lt;/span&gt; .env up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--wait&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;span class="nl"&gt;up-prod&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Starting containers"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.prod.yml &lt;span class="nt"&gt;--env-file&lt;/span&gt; .env up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--wait&lt;/span&gt; &lt;span class="nt"&gt;--remove-orphans&lt;/span&gt;
&lt;span class="nl"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="p"&gt;$$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;php.&lt;span class="p"&gt;${&lt;/span&gt;APP_NAMESPACE&lt;span class="p"&gt;})&lt;/span&gt; /bin/bash
&lt;span class="nl"&gt;code-check&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Perform a static analysis of the code base"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;DOCKER_CLI_HINTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false &lt;/span&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="p"&gt;$$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;php.&lt;span class="p"&gt;${&lt;/span&gt;APP_NAMESPACE&lt;span class="p"&gt;})&lt;/span&gt; vendor/bin/phpstan analyse &lt;span class="nt"&gt;--memory-limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2G
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Perform a code rector"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;DOCKER_CLI_HINTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false &lt;/span&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="p"&gt;$$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;php.&lt;span class="p"&gt;${&lt;/span&gt;APP_NAMESPACE&lt;span class="p"&gt;})&lt;/span&gt; composer cs-rector
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Perform a code style check"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;DOCKER_CLI_HINTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false &lt;/span&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="p"&gt;$$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;php.&lt;span class="p"&gt;${&lt;/span&gt;APP_NAMESPACE&lt;span class="p"&gt;})&lt;/span&gt; composer cs-check
&lt;span class="nl"&gt;rector-fix&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Fix code with rector"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;DOCKER_CLI_HINTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false &lt;/span&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="p"&gt;$$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;php.&lt;span class="p"&gt;${&lt;/span&gt;APP_NAMESPACE&lt;span class="p"&gt;})&lt;/span&gt; composer cs-rector-fix
&lt;span class="nl"&gt;code-baseline&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Perform phpstan generate-baseline"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;DOCKER_CLI_HINTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false &lt;/span&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="p"&gt;$$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;php.&lt;span class="p"&gt;${&lt;/span&gt;APP_NAMESPACE&lt;span class="p"&gt;})&lt;/span&gt; vendor/bin/phpstan analyse &lt;span class="nt"&gt;--generate-baseline&lt;/span&gt; &lt;span class="nt"&gt;--memory-limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2G
&lt;span class="nl"&gt;composer-install&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Running composer install"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="p"&gt;$$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;php.&lt;span class="p"&gt;${&lt;/span&gt;APP_NAMESPACE&lt;span class="p"&gt;})&lt;/span&gt; composer &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--ignore-platform-reqs&lt;/span&gt;
&lt;span class="nl"&gt;db-migrate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Running database migrations"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="p"&gt;$$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;php.&lt;span class="p"&gt;${&lt;/span&gt;APP_NAMESPACE&lt;span class="p"&gt;})&lt;/span&gt; php artisan migrate &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;span class="nl"&gt;build-front&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Building admin frontend for production"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="p"&gt;$$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;php.&lt;span class="p"&gt;${&lt;/span&gt;APP_NAMESPACE&lt;span class="p"&gt;})&lt;/span&gt; npm i
    &lt;span class="p"&gt;@&lt;/span&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="p"&gt;$$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;php.&lt;span class="p"&gt;${&lt;/span&gt;APP_NAMESPACE&lt;span class="p"&gt;})&lt;/span&gt; npm run build
&lt;span class="nl"&gt;pull&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Updating project from git and rebuild"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;git pull
&lt;span class="nl"&gt;rm-images&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Delete extra images"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;docker system prune &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;span class="nl"&gt;key-generate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Key generate"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="p"&gt;$$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;php.&lt;span class="p"&gt;${&lt;/span&gt;APP_NAMESPACE&lt;span class="p"&gt;})&lt;/span&gt; php artisan key:generate
&lt;span class="nl"&gt;storage-link&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Storage Link"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="p"&gt;$$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;php.&lt;span class="p"&gt;${&lt;/span&gt;APP_NAMESPACE&lt;span class="p"&gt;})&lt;/span&gt; php artisan storage:link
&lt;span class="nl"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Db Seed"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="p"&gt;$$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;php.&lt;span class="p"&gt;${&lt;/span&gt;APP_NAMESPACE&lt;span class="p"&gt;})&lt;/span&gt; php artisan db:seed
&lt;span class="nl"&gt;doc-generate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Key generate"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="p"&gt;$$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;php.&lt;span class="p"&gt;${&lt;/span&gt;APP_NAMESPACE&lt;span class="p"&gt;})&lt;/span&gt; php artisan scribe:generate
&lt;span class="nl"&gt;restart&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"restart container"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;docker restart php.&lt;span class="p"&gt;${&lt;/span&gt;APP_NAMESPACE&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
That’s it! This article covered the key points. If you’d like a deeper dive into any topic, let me know in the comments.  &lt;/p&gt;

&lt;p&gt;For a detailed look, check out the repository.  &lt;/p&gt;

&lt;p&gt;Thanks to everyone who read this post. This is my first publication on Habr, so go easy on me—I tried to focus on the most interesting aspects.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>hackathon</category>
      <category>php</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
