<?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>Forem: Jhonatan Henkel</title>
    <description>The latest articles on Forem by Jhonatan Henkel (@jhonhenkel).</description>
    <link>https://forem.com/jhonhenkel</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%2F1145323%2F3f3dc4a7-c98f-4a2e-8f96-5bc5e339a9db.jpeg</url>
      <title>Forem: Jhonatan Henkel</title>
      <link>https://forem.com/jhonhenkel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jhonhenkel"/>
    <language>en</language>
    <item>
      <title>Minha arquitetura API no Laravel</title>
      <dc:creator>Jhonatan Henkel</dc:creator>
      <pubDate>Tue, 25 Mar 2025 12:45:12 +0000</pubDate>
      <link>https://forem.com/jhonhenkel/minha-arquitetura-no-laravel-26nj</link>
      <guid>https://forem.com/jhonhenkel/minha-arquitetura-no-laravel-26nj</guid>
      <description>&lt;p&gt;Faz um tempo que não escrevo mais aqui, mas vamos lá, hoje vamos conversar sobre a arquitetura que eu uso no Laravel. &lt;/p&gt;

&lt;p&gt;A estrutura de pastas e arquivos que eu desenvolvi ao longo do tempo me ajudou a ganhar agilidade desenvolvendo no dia a dia.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estrutura de pasta
&lt;/h2&gt;

&lt;p&gt;projeto&lt;br&gt;
|--app&lt;br&gt;
|  |--Infra&lt;br&gt;
|  |--Models&lt;br&gt;
|  |--Modules&lt;br&gt;
|--config&lt;br&gt;
|  |--develop&lt;br&gt;
|  |--prod&lt;/p&gt;

&lt;p&gt;Fora do padrão que temos do Laravel ao dar o start, tenho essa estrutura. &lt;/p&gt;

&lt;p&gt;Em Infra, deixo tudo que se aplica a escopo global da aplicação, como por exemplo, controllers a serem estendidos, validadores de requisições, enums, abstrações uteis (email, sentry), middle wares e outras coisas mais...&lt;/p&gt;

&lt;p&gt;Em Models, fica os models do Laravel, organizados por módulo, então se eu tenho um model de usuário e outro de endereço do usuário, ambos ficam na pasta User.&lt;/p&gt;

&lt;p&gt;Em módules fica todos os módulos separados por pasta, de forma clara a qual módulo estamos trabalhando. Dentro de cada módulo tenho as pastas referente a controllers, enum, use cases e demais itens específicos para esse módulo.&lt;/p&gt;

&lt;p&gt;As pastas develop e prod, eu deixo os docker files, docker compose, configurações do opcache, etc...&lt;/p&gt;
&lt;h2&gt;
  
  
  Controllers
&lt;/h2&gt;

&lt;p&gt;Eu gosto de trabalhar com controllers basic para fazer todo o trabalho repetitivo. Dentro de Infra/Controllers tenho um controller basic para cada ação do CRUD. &lt;/p&gt;

&lt;p&gt;Exemplo de controller basic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;abstract class BaseCreateController extends Controller
{
    abstract protected function getUseCase(): ICreateUseCase;
    abstract protected function getRules(): array;
    abstract protected function getModelName(): string;

    public function __invoke(Request $request): JsonResponse
    {
        ForbiddenException::validatePolicy(GatesAbilityEnum::Create, $this-&amp;gt;getModelName());
        Validator::validateRequest($request, $this-&amp;gt;getRules());
        $result = $this-&amp;gt;getUseCase()-&amp;gt;execute($request-&amp;gt;json()-&amp;gt;all());
        if ($result) {
            return ResponseApi::renderCreated($result);
        }
        return ResponseApi::renderInternalServerError('Erro ao inserir item.');
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por esse controller, já temos todas as tratativas para exceptions de request inválida e falta de acesso ao endpoint, tratados no middle ware do Laravel.&lt;/p&gt;

&lt;p&gt;O controller de um módulo fica assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class ClientCreateController extends BaseCreateController
{
    public function __construct(protected ClientCreateUseCase $useCase)
    {
    }

    protected function getUseCase(): ICreateUseCase
    {
        return $this-&amp;gt;useCase;
    }

    protected function getRules(): array
    {
        return [
            // regras do json de request
        ];
    }

    protected function getModelName(): string
    {
        return Client::class;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;p&gt;Todo use case implementa uma interface, para garantir a comunicação correta com o controller. &lt;/p&gt;

&lt;p&gt;O use case do controller acima, ficaria assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class ClientCreateUseCase implements ICreateUseCase
{
    public function execute(array $data): array
    {
        $client = Client::create([
            // campos
        ]);
        return $client-&amp;gt;toArray();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dessa forma, com poucos arquivos, temos uma estrutura robusta e fácil de se trabalhar, já que não precisarei me preocupar com padrão de response, validar request e acesso do usuário. Isso tudo já fica abstraído no controller base. &lt;/p&gt;

&lt;p&gt;O máximo que terei que fazer é criar e registrar policy para o módulo.&lt;/p&gt;

&lt;p&gt;Por hoje é só pessoal. Até a próxima.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>architecture</category>
      <category>php</category>
      <category>programming</category>
    </item>
    <item>
      <title>Usando o local storage a seu favor</title>
      <dc:creator>Jhonatan Henkel</dc:creator>
      <pubDate>Sun, 03 Dec 2023 18:25:14 +0000</pubDate>
      <link>https://forem.com/jhonhenkel/usando-o-local-storage-a-seu-favor-4e4k</link>
      <guid>https://forem.com/jhonhenkel/usando-o-local-storage-a-seu-favor-4e4k</guid>
      <description>&lt;p&gt;Olá, imagine a seguinte situação, você tem que implementar os dados das taxas básicas do Brasil (Selic, CDB, IPCA, etc...) em uma dashboard, em algum momento você vai precisar bater em um endpoint externo. Chamar esse endpoint a cada carregamento dessa página não é uma opção...&lt;/p&gt;

&lt;p&gt;Colocar em local storagem é uma ótima opção, mas para renovar esse item no local storage temos que ter um certo controle sobre esse item.&lt;/p&gt;

&lt;p&gt;para isso em minha aplicação eu desenvolvi uma classe que fica responsável por colocar e recuperar itens do local storage.&lt;/p&gt;

&lt;p&gt;Vamos ao código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import CalendarTools from './calendarTools'

const LocalStorageTools = {
    storage: {
        getStorageItem: function(key) {
            const itemInStorage = localStorage.getItem(key)
            if (itemInStorage) {
                const itemParsed = JSON.parse(itemInStorage)
                const now = CalendarTools.getToday()
                if (now.getTime() &amp;lt; itemParsed.expiry) {
                    return itemParsed.value
                }
                this.removeStorageItems(key)
            }
            return null
        },
        setStorageItem: function(key, value, expireTimeMs) {
            const expiry = expireTimeMs ?? CalendarTools.threeHoursInMs()
            localStorage.setItem(key, JSON.stringify({
                value,
                expiry: CalendarTools.getToday().getTime() + expiry
            }))
        },
        removeStorageItems: function(...keys) {
            keys.forEach((key) =&amp;gt; {
                localStorage.removeItem(key)
            })
        }
    }
}

export default LocalStorageTools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vamos a explicação, como controle eu uso uma string que armazena o tempo que o item irá expirar, que sempre será o tempo atual somado com um tempo de expiração em ms, com isso, no local storage sempre teremos o item como objeto com dois atributos, um &lt;strong&gt;expiry&lt;/strong&gt; e um &lt;strong&gt;value&lt;/strong&gt;, que é onde vai ficar o que queremos armazenar.&lt;/p&gt;

&lt;p&gt;Sendo assim a função de salvar o item em local storage vai ficar responsável por salvar no formato mencionado acima. A função de buscar vai ter que validar se o item está expirado, se não tiver devolve o atributo &lt;strong&gt;value&lt;/strong&gt;, se tiver, retorna null e apaga o item do storage.&lt;/p&gt;

&lt;p&gt;Com isso temos um cache em local storage para coisas simples como por exemplo esses dados das taxas, já que é algo que não muda toda hora e não queremos bater na API externa a todo momento.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>api</category>
      <category>typescript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Subindo aplicação Go na AWS</title>
      <dc:creator>Jhonatan Henkel</dc:creator>
      <pubDate>Wed, 01 Nov 2023 22:53:44 +0000</pubDate>
      <link>https://forem.com/jhonhenkel/subindo-aplicacao-go-na-aws-142b</link>
      <guid>https://forem.com/jhonhenkel/subindo-aplicacao-go-na-aws-142b</guid>
      <description>&lt;p&gt;Olá dev, ja faz temo que não nos falamos, não é mesmo?&lt;/p&gt;

&lt;p&gt;Hoje a nossa conversa vai ser sobre hospedagem para aplicação em linguagem Go. Tentei algumas alternativas que não deram certo, como por exemplo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kinghost: Não aceitam hospedagem em Go.&lt;/li&gt;
&lt;li&gt;Heroku: Consegui hospedar, porem não consegui usar devido ao Heroku não deixar eu configurar o .env, e não, não devemos comitar o .env.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dando uma pesquisada e trocando ideia com algumas pessoas, cheguei a AWS, aqui da de usar, não sei como fica ao certo quanto a custos, mas acredito que não vamos ultrapassar o limite do plano gratuito. É desejável que você tenha um pouco de experiencia na AWS para compreender bem esse post.&lt;/p&gt;

&lt;p&gt;Primeiramente acesse o serviço de EC2 da AWS, no menu lateral selecione a opção &lt;strong&gt;instances&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%2Fercmh82qlp80qxv5rqyc.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%2Fercmh82qlp80qxv5rqyc.png" alt="instances" width="556" height="604"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Na tela que se abrir, clique no botão superior direito em amarelo escrito &lt;strong&gt;Launch instances&lt;/strong&gt;, é aqui que vamos criar nossa instância na AWS para rodar nossa aplicação em Go.&lt;/p&gt;

&lt;p&gt;Inicialmente, vamos definir um nome, selecionar a opção &lt;strong&gt;Amazon Linux&lt;/strong&gt;, escolher a AMI &lt;strong&gt;Amazon Linux 2 AMI&lt;/strong&gt; e por fim a arquitetura 64 bits.&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%2Fmrhhp4lbzauw8qrbo1i6.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%2Fmrhhp4lbzauw8qrbo1i6.png" alt="initial config" width="800" height="690"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Em tipo de instância, recomendo escolher a t2.micro, e em &lt;strong&gt;key pair&lt;/strong&gt; clique em &lt;strong&gt;create new key pair&lt;/strong&gt;, é aqui que vamos gerar o certificado para conexão via SSH.&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%2Frmz9lvllr7vj4pyjcopx.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%2Frmz9lvllr7vj4pyjcopx.png" alt="key pair" width="800" height="831"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Defina um nome, e o resto deixe nas opções padrão, ao criar, salve o arquivo em um local seguro e não perca, vamos precisar dele para acessar o SSH.&lt;/p&gt;

&lt;p&gt;Em &lt;strong&gt;network settings&lt;/strong&gt;, clique em &lt;strong&gt;Edit&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%2F4hhscp0al3x9iv45n8p1.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%2F4hhscp0al3x9iv45n8p1.png" alt="network edit" width="800" height="117"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clique em &lt;strong&gt;Add security group rule&lt;/strong&gt; e defina a porta que sua aplicação Go vai usar, se for a 8000 por exemplo, vai ficar mais ou menos assim:&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%2F0l3w5ptwf1vn5x3zyma6.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%2F0l3w5ptwf1vn5x3zyma6.png" alt="security group" width="800" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Agora é só clicar em &lt;strong&gt;launch instance&lt;/strong&gt; e aguardar um pouco.&lt;/p&gt;

&lt;p&gt;Agora com a instância criada e iniciada, acesse seu terminal, vá até a pasta que você salvou o certificado mencionado anteriormente, lembra? aquele que eu pedi para salvar e não perder. &lt;/p&gt;

&lt;p&gt;O endereço para acessar sua instancia vai ser algo parecido com isso: ec2-XX-XXX-XX-XX.XX-XX-X.compute.amazonaws.com, onde os X's são equivalentes ao DNS da sua máquina.&lt;/p&gt;

&lt;p&gt;para pegar o endereço correto, basta ir até os detalhes da sua instância na AWS e pegar o endereço contido no campo &lt;strong&gt;Public IPv4 DNS&lt;/strong&gt;. Dica, esse endereço não é estático, ou seja, fica mudando.&lt;/p&gt;

&lt;p&gt;O comando para acessar via SSH pelo terminal, o comando vai ser o seguinte:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo ssh -i chave.pem ec2-user@ec2-XX-XXX-XX-XX.XX-XX-X.compute.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com isso, você já deve ver a tela inicial da sua maquina. Agora vamos a parte legal, configurar o Go.&lt;/p&gt;

&lt;p&gt;Use os seguintes comandos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo yum update -y
sudo yum install -y golang
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para garantir que ficou certo, rode:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Você deve ter algo parecido com isso:&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%2Fyoeed5hd9x7xq40w98wt.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%2Fyoeed5hd9x7xq40w98wt.png" alt="Go version" width="526" height="36"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Caso precise atualizar o Go para uma versão mais recente, como eu por exemplo, precisava da 1.21, rode esses comandos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wget https://go.dev/dl/go1.21.0.linux-amd64.tar.gz
tar -xvf go1.21.0.linux-amd64.tar.gz
mv go /usr/local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure as variáveis de ambiente do Go com os seguintes comandos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export GOROOT=/usr/local/go
export GOPATH=$HOME/Apps/app1
export PATH=$GOPATH/bin:$GOROOT/bin:$PATH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com isso rode novamente o go version para ver se ficou conforme desejado.&lt;/p&gt;

&lt;p&gt;Agora já temos tudo pronto para rodar a aplicação, basta clonar seu repo para a AWS via git clone no SSH, criar seu .env caso necessário, rodar o &lt;strong&gt;go mod tidy&lt;/strong&gt; para atualizar as dependências do seu projeto e por fim rodar o &lt;strong&gt;go run main.go&lt;/strong&gt; no diretório do seu main.&lt;/p&gt;

&lt;p&gt;Caso tenha problemas de permissão, rode os comandos com o sudo.&lt;/p&gt;

&lt;p&gt;para testar, basta usar a url, seguido pa porta, como por exemplo:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://ec2-XX-XXX-XX-XXX.XX-XXX-X.compute.amazonaws.com:8080/mainPage" rel="noopener noreferrer"&gt;http://ec2-XX-XXX-XX-XXX.XX-XXX-X.compute.amazonaws.com:8080/mainPage&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;E por hoje é só pessoal, até a próxima ;)&lt;/p&gt;

</description>
      <category>aws</category>
      <category>go</category>
      <category>backend</category>
      <category>devops</category>
    </item>
    <item>
      <title>Programas que uso no MacOS como Desenvolvedor</title>
      <dc:creator>Jhonatan Henkel</dc:creator>
      <pubDate>Sat, 07 Oct 2023 00:22:03 +0000</pubDate>
      <link>https://forem.com/jhonhenkel/programas-que-uso-no-macos-como-desenvolvedor-2di7</link>
      <guid>https://forem.com/jhonhenkel/programas-que-uso-no-macos-como-desenvolvedor-2di7</guid>
      <description>&lt;p&gt;Olá, ando meio sumido e sem ideias, mas hoje resolvi trazer um conjunto de programas que utilizo no meu dia a dia como desenvolvedor web no meu MacBook.&lt;/p&gt;

&lt;p&gt;O foco desses programas é ter desempenho e ser fácil de usar, desempenho por que meu Mac já é mais antigo (MacBook Pro 2017 com 8GB de memória ram).&lt;/p&gt;

&lt;p&gt;Vamos lá, sem mais enrolação.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://orbstack.dev/" rel="noopener noreferrer"&gt;OrbStack&lt;/a&gt;
&lt;/h4&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%2Faipd60iopp1dmn1kf3cb.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%2Faipd60iopp1dmn1kf3cb.png" alt="OrbStack" width="800" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Esse uso como substituto ao Docker Desktop, como o Docker desktop acaba sendo pesado na minha maquina, uso esse carinha para rodar meus containers e assim ter mais desempenho.&lt;/p&gt;

&lt;p&gt;Notei uma grande economia de memória ram e processamento ao usar o OrdStack, que é justamente a proposta dele.&lt;/p&gt;

&lt;p&gt;Sem contar que não perco os comandos do Docker no terminal, já que ele usa o CLI do Docker.&lt;/p&gt;

&lt;p&gt;Fiz um post falando sobre ele &lt;a href="https://dev.to/jhonhenkel/alternativa-ao-docker-desktop-para-macos-31c8"&gt;aqui&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://ohmyz.sh/" rel="noopener noreferrer"&gt;Oh My Zsh&lt;/a&gt;
&lt;/h4&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%2Fb2vgnati07ee1ycek0ox.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%2Fb2vgnati07ee1ycek0ox.png" alt="Oh My Zsh" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Essa é uma forma de "tunar" meu terminal, com ele uso tema para mostrar a pasta do diretório atual, e alguns plugins para auto complete de comandos e sugestões de comandos Docker.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://www.notion.so/" rel="noopener noreferrer"&gt;Notion&lt;/a&gt;
&lt;/h4&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%2Fq9nibgopf7ggevjk5sc8.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%2Fq9nibgopf7ggevjk5sc8.png" alt="Notion" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Quem hoje em dia consegue se organizar sem Notion? Poisé, eu sou uma dessas pessoas que não fica mais sem o Notion.&lt;/p&gt;

&lt;p&gt;Esse despensa comentários.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;VS Code&lt;/a&gt;
&lt;/h4&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%2Ffqvrlrryvwn5rn4kt2jq.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%2Ffqvrlrryvwn5rn4kt2jq.png" alt="VS Code" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Você que me conhece, eu sei, nesse momento deve estar rindo de min. &lt;/p&gt;

&lt;p&gt;Sou desenvolvedor backend PHP, logo, uso muito o PHP Storm na minha maquina de desenvolvimento do local onde trabalho, e para min é a melhor IDE que tem, mas como estamos falando de desempenho, no meu Mac uso o VS Code.&lt;/p&gt;

&lt;p&gt;Isso por que enquanto o VS Code usa cerca de 200 Mb de Ram, o PHP Storm utiliza entre 3 e 4 Gb. Isso acaba deixando inviável o uso dele no meu Mac.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://sequel-ace.com/" rel="noopener noreferrer"&gt;Sequel Ace&lt;/a&gt;
&lt;/h4&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%2Fcssdlcdtkaqi45eq7kqj.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%2Fcssdlcdtkaqi45eq7kqj.png" alt="Sequel Ace" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Como utilizo banco de dados relacional, utilizo o Sequel Ace, foi um dos poucos sistemas de banco de dados que achei leve e fácil de usar, antes utilizava o DBeaver.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://insomnia.rest/" rel="noopener noreferrer"&gt;Insomnia&lt;/a&gt;
&lt;/h4&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%2F0l1bsqmlnmhtmd4r6vv6.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%2F0l1bsqmlnmhtmd4r6vv6.png" alt="Insomnia" width="800" height="577"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Não, não sou fã do Postman, para testar as minhas API`s e fazer requisições, utilizo o Insomnia, acho uma ótima ferramenta para REST.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://termius.com/" rel="noopener noreferrer"&gt;Termius&lt;/a&gt;
&lt;/h4&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%2Fgle34aer0afjld2xkesq.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%2Fgle34aer0afjld2xkesq.png" alt="Termius" width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Uso o Termius para acessar os dados no meu servidor via FSTP e SSH quando preciso, ótima ferramenta também, facilita muito o uso, pois ela permite deixar salvo a conexão com o servidor, sem a necessidade de ficar digitando usuário e senha a cada login.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://desktop.github.com/" rel="noopener noreferrer"&gt;GitHub Desktop&lt;/a&gt;
&lt;/h4&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%2Ffrt3k854xylspx0b6ayg.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%2Ffrt3k854xylspx0b6ayg.png" alt="GitHub Desktop" width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Uso ele para fazer os meus commits, acho super leve, intuitivo e simples. &lt;/p&gt;

&lt;p&gt;No meu serviço utilizo o Sublime Merge, que também acho excelente.&lt;/p&gt;

&lt;p&gt;Acho que mencionei todas as minhas ferramentas aqui, mas e ai? quais você usa? deixe aqui nos comentários ;)&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Multi usuários no Laravel</title>
      <dc:creator>Jhonatan Henkel</dc:creator>
      <pubDate>Thu, 07 Sep 2023 14:46:06 +0000</pubDate>
      <link>https://forem.com/jhonhenkel/multi-usuarios-no-laravel-46jg</link>
      <guid>https://forem.com/jhonhenkel/multi-usuarios-no-laravel-46jg</guid>
      <description>&lt;p&gt;Olá dev, hoje o assunto vai ser um pouco mais longo, vamos conversar um pouco sobre multi usuários no Laravel, essa arquitetura é chamada de multi tenant, ou multi tenancy.&lt;/p&gt;

&lt;p&gt;Tenant vem de hóspede, pois cada usuário é equivalente a um hóspede nessa arquitetura.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tipos de multi tenant
&lt;/h3&gt;

&lt;p&gt;Segundo meus estudos, existem três principais formas para aplicar essa arquitetura em seu projeto:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Por mais dos diversos lugares que eu li e vi, geralmente se resumia nessas três abordagens.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;1-&lt;em&gt;Com banco de dados separado&lt;/em&gt;: em alguns casos, podemos ter até mesmo uma instância inteira da aplicação por cliente. Isso seria o mais seguro, se considerar o isolamento dos dados, porém acaba que a manutenção, o gerenciamento e o custo dessa abordagem tornam-se caras e difíceis.&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%2F53yvak5dh0y2k5lbe6ip.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%2F53yvak5dh0y2k5lbe6ip.png" alt="Com banco de dados separado" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2-&lt;em&gt;Uma única instância de banco com esquemas separados por cliente&lt;/em&gt;: nessa abordagem para o meu uso, na minha aplicação (vou detalhar mais a frente), não parecia ser muito viável, pois para cada novo usuário, eu teria que fazer um &lt;strong&gt;create database tenantX&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%2Fnvc70p6g6clr8ofti98e.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%2Fnvc70p6g6clr8ofti98e.png" alt="_Uma única instância de banco com esquemas separados por cliente" width="800" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3-&lt;em&gt;Um único banco de dados para todos os tenants&lt;/em&gt;: essa foi a abordagem que escolhi. Nessa modalidade, é feito o vinculo dos usuários por um id, uma "chave de locação". Aqui, assim como nas outras abordagens, um usuário não vê os dados dos outros usuários, obviamente. A não ser que você defina um mesmo tenant_id em outro usuário.&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%2Fxou2bpsj5pv6rdny80kr.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%2Fxou2bpsj5pv6rdny80kr.png" alt="Um único banco de dados para todos os tenants" width="800" height="655"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Sobre minha aplicação
&lt;/h3&gt;

&lt;p&gt;Antes de irmos para o código, vamos bater mais um papo, sobre a minha aplicação.&lt;/p&gt;

&lt;p&gt;No momento em que escrevo, minha aplicação está desenvolvida com o backend em Laravel e o frontend em Vue.Js. A comunicação do Laravel com o Vue está totalmente sendo feita por API, ou seja, o backend está bem separado do frontend.&lt;/p&gt;

&lt;p&gt;Para fazer a autenticação (Login) na aplicação, estou usando JTW, no momento que o response do login é feito para o frontend, o token e usuário é salvo no gerenciador de estado do Vue, o Pinia.&lt;/p&gt;

&lt;p&gt;Isso garante que caso o usuário troque os dados no localStorage do navegador, não afete a aplicação, acessando dados que não deve.&lt;/p&gt;

&lt;p&gt;Ainda não terei tela de registro, por questões de que ainda não quero liberar a aplicação para todos usarem, então os usuários vão ser inseridos manualmente no banco.&lt;/p&gt;

&lt;p&gt;Sem mais enrolação, vamos ao que interessa…&lt;/p&gt;

&lt;h3&gt;
  
  
  Preparando o Banco
&lt;/h3&gt;

&lt;p&gt;Neste passo vamos ter que criar três migrations:&lt;/p&gt;

&lt;p&gt;1-Criando a tabela de &lt;em&gt;tenants&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('tenants', function (Blueprint $table) {
            $table-&amp;gt;id();
            $table-&amp;gt;unsignedBigInteger('user_id')-&amp;gt;nullable()-&amp;gt;index();
            $table-&amp;gt;timestamp('created_at')-&amp;gt;default(DB::raw('CURRENT_TIMESTAMP'));
        });
    }
    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('tenants');
    }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2-Alterar a tabela de usuário para ter o &lt;em&gt;tenant_id&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('users', function(Blueprint $table) {
            $table-&amp;gt;unsignedBigInteger('tenant_id')-&amp;gt;nullable()-&amp;gt;after('name')-&amp;gt;index();
            $table-&amp;gt;foreign('tenant_id')-&amp;gt;references('id')-&amp;gt;on('tenants');
        });
    }
    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::table('users', function($table) {
            $table-&amp;gt;dropColumn('tenant_id');
        });
    }
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3-Alterar demais tabelas para ter o &lt;em&gt;tenant_id&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('tabela_1', function(Blueprint $table) {
            $table-&amp;gt;unsignedBigInteger('tenant_id')-&amp;gt;nullable()-&amp;gt;after('description')-&amp;gt;index();
            $table-&amp;gt;foreign('tenant_id')-&amp;gt;references('id')-&amp;gt;on('tenants');
        });
        Schema::table('tabela_2', function(Blueprint $table) {
            $table-&amp;gt;unsignedBigInteger('tenant_id')-&amp;gt;nullable()-&amp;gt;after('next_installment')-&amp;gt;index();
            $table-&amp;gt;foreign('tenant_id')-&amp;gt;references('id')-&amp;gt;on('tenants');
        });
    }
    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::table('tabela_1', function(Blueprint $table) {
            $table-&amp;gt;dropColumn('tenant_id');
        });
        Schema::table('tabela_2', function(Blueprint $table) {
            $table-&amp;gt;dropColumn('tenant_id');
        });
    }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Da parte de preparar o banco é isso, vamos ao próximo passo, que é na aplicação&lt;/p&gt;

&lt;h3&gt;
  
  
  Classe JWT
&lt;/h3&gt;

&lt;p&gt;Antes de iniciarmos com o código do multi-tenant, vamos dar uma olhada na minha classe do JWT.&lt;br&gt;
&lt;/p&gt;

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

namespace App\Tools\Auth;

use App\Enums\DateEnum;
use App\Models\User;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

class JwtTools
{
    public static function createJWT(User $data): string
    {
        $payload = array(
            'exp' =&amp;gt; time() + DateEnum::TREE_HOUR_IN_SECONDS,
            'iat' =&amp;gt; time(),
            'data' =&amp;gt; $data
        );
        return JWT::encode($payload, env('SECRET_HASH'), 'HS256');
    }

    public static function validateJWT(string $authorization): bool|object
    {
        try {
            $token = str_replace('Bearer ', '', $authorization);
            $key = new Key(env('SECRET_HASH'), 'HS256');
            return JWT::decode($token, $key);
        } catch (\Exception $e) {
            return false;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No createJWT, basicamente estou gerando o token com o model User, com isso, quando eu receber o token do frontend novamente e descriptografar, já tenho o model do User, sem a necessidade de ficar fazendo find no banco.&lt;/p&gt;

&lt;p&gt;Já no validateJWT, vamos pegar esse token gerado anteriormente e descriptografar, podendo retornar o objeto JWT com o model User, ou false, caso não consiga descriptografar.&lt;/p&gt;

&lt;h3&gt;
  
  
  Classe TenantScope
&lt;/h3&gt;

&lt;p&gt;Nessa classe, é onde vamos adicionar ao scope do Laravel a busca pelo parâmetro &lt;strong&gt;&lt;em&gt;tenant_id&lt;/em&gt;&lt;/strong&gt; de cada tabela, sem termos a necessidade de ficar fazendo em cada consulta ao banco.&lt;/p&gt;

&lt;p&gt;A responsabilidade dela é adicionar a toda consulta a condição &lt;strong&gt;&lt;em&gt;where&lt;/em&gt;&lt;/strong&gt; filtrando pelo &lt;strong&gt;&lt;em&gt;tenant_id&lt;/em&gt;&lt;/strong&gt; no &lt;strong&gt;&lt;em&gt;repository&lt;/em&gt;&lt;/strong&gt; pesquisado, evitando assim esquecimentos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php
namespace App\Scope;
use App\Enums\ConfigEnum;
use App\Tools\Auth\JwtTools;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class TenantScope implements Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        $token = $_SERVER[ConfigEnum::X_USER_TOKEN] ?? '';
        $user = JwtTools::validateJWT($token);
        if (! $user) {
            return;
        }
        $builder-&amp;gt;where($model-&amp;gt;getTable() . '.tenant_id', $user-&amp;gt;data-&amp;gt;tenant_id);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aqui, basicamente pego o token da request, aquele feito pelo JWT mencionado anteriormente, caso consiga descriptografar ele, teremos o objeto do user, tendo assim o seu &lt;strong&gt;&lt;em&gt;tenant_id&lt;/em&gt;&lt;/strong&gt;. Caso não tiver, só dá um return, evitando quebras.&lt;/p&gt;

&lt;p&gt;Calma, a obrigação do envio do token na request é responsabilidade de outra classe, vamos ver mais a frente.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trait Tenantable
&lt;/h3&gt;

&lt;p&gt;Essa é a trait que vai ser responsável por injetar o tenant_id no nosso model.&lt;br&gt;
&lt;/p&gt;

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

namespace App\Models\Trait;

use App\Scope\TenantScope;
use App\Models\Tenant;
use App\Tools\Auth\JwtTools;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

trait Tenantable
{
    protected static function bootTenantable(): void
    {
        static::addGlobalScope(new TenantScope);
        $token = $_SERVER['HTTP_X_MFP_USER_TOKEN'] ?? '';
        $user = JwtTools::validateJWT($token);
        if (! $user) {
            return;
        }
        static::creating(function ($model) use ($user) {
            $model-&amp;gt;tenant_id = $user-&amp;gt;data-&amp;gt;tenant_id;
        });
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basicamente a condicional é a mesma da classe anterior, a diferença é que que estamos definindo o tenant_id do model que usar essa trait.&lt;/p&gt;

&lt;p&gt;Com isso, sempre que formos fazer um insert por exemplo, sempre teremos definido no &lt;strong&gt;&lt;em&gt;model&lt;/em&gt;&lt;/strong&gt; o &lt;strong&gt;&lt;em&gt;tenant_id&lt;/em&gt;&lt;/strong&gt;, sem a necessidade de popular manualmente esse atributo.&lt;/p&gt;

&lt;p&gt;Com essa trait criada, vamos adicionar ela a todo model que deva fazer o uso do tenant_id. Basta adicionar o "&lt;strong&gt;&lt;em&gt;use Tenantable;&lt;/em&gt;&lt;/strong&gt;" em cada &lt;strong&gt;&lt;em&gt;model&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validando requests API e model Tenant
&lt;/h3&gt;

&lt;p&gt;Na classe AuthServiceProvider, iremos fazer uma alteração no método &lt;strong&gt;&lt;em&gt;boot&lt;/em&gt;&lt;/strong&gt;. Aqui é onde iremos validar e obrigar o envio do &lt;strong&gt;&lt;em&gt;token API&lt;/em&gt;&lt;/strong&gt; e do &lt;strong&gt;&lt;em&gt;token JWT&lt;/em&gt;&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;public function boot(): void
{
    Auth::viaRequest('authApi', function (Request $request) {
        $userToken = $_SERVER[ConfigEnum::X_USER_TOKEN] ?? '';
        $user = JwtTools::validateJWT($userToken);
        $apiTokenEncrypted = bcrypt(env('SECRET_HASH'));
        $apiToken = $request-&amp;gt;header('API_TOKEN') ?? '';
        return password_verify($apiToken, $apiTokenEncrypted) &amp;amp;&amp;amp; $user ? new User() : null;
    });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Caso esse método retorne o null, vai ser respondido o conteúdo definido dentro do método &lt;strong&gt;&lt;em&gt;unauthenticated&lt;/em&gt;&lt;/strong&gt; dentro da classe &lt;strong&gt;&lt;em&gt;AuthenticateAPI&lt;/em&gt;&lt;/strong&gt;. A minha eu deixei assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;protected function unauthenticated($request, array $guards): ?string
{
    $message = 'Tokens obrigatórios ausentes ou inválidos!';
    abort(response()-&amp;gt;json($message, Response::HTTP_UNAUTHORIZED));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Também temos que criar um model de Tenant. Não precisa de nada especial dentro dele, só ter ele criado mesmo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Tenant extends Model
{
    use HasFactory;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusão
&lt;/h3&gt;

&lt;p&gt;Com isso tudo feito, já deve funcionar multi tenant na sua aplicação. Isso que eu mostrei aqui é algo relativamente super simples, porém senti que falta conteúdo falando sobre. Até achei um vídeo (aqui), porém não funcionou com o Vue devido a comunicação ser por API. Para funcionar na nessa modalidade tive que fazer as alterações mencionadas aqui.&lt;/p&gt;

&lt;p&gt;No fim ficou algo mais ou menos assim:&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%2F0z4s8uiaix8ga7f63u8m.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%2F0z4s8uiaix8ga7f63u8m.png" alt="conclusao" width="800" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Chegamos ao fim, até mais dev.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Lazy Load no Vue</title>
      <dc:creator>Jhonatan Henkel</dc:creator>
      <pubDate>Thu, 07 Sep 2023 14:26:30 +0000</pubDate>
      <link>https://forem.com/jhonhenkel/lazy-load-no-vue-3deo</link>
      <guid>https://forem.com/jhonhenkel/lazy-load-no-vue-3deo</guid>
      <description>&lt;p&gt;Olá dev, hoje o papo vai ser sobre Vue, mais especificamente sobre Vue Router.&lt;/p&gt;

&lt;p&gt;Recentemente descobri um que existe duas formas de se fazer o router, a primeira, que carrega todos os módulos "páginas" no primeiro load e a outra que é onde cada módulo carrega conforme você acessa a página.&lt;/p&gt;

&lt;p&gt;Vamos aos exemplos…&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {createRouter, createWebHistory} from "vue-router";
import LoginView from "../../vue/view/login/LoginView.vue";
import AboutView from "../../vue/view/about/AboutView.vue";

const routes = [
    {
        path: "/sobre",
        name: "about",
        component: AboutView
    },
    {
        path: "/login",
        name: "login",
        component: LoginView
    }
]

const router = createRouter({
    history: createWebHistory(),
    routes
})

export default router
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nesse exemplo, assim que carregar a página no primeiro load, vai ser carregado os módulos LoginView e AboutView, junto com todos os seus componentes internos.&lt;/p&gt;

&lt;p&gt;A desvantagem dessa forma de utilizar essa forma é que o primeiro load fica com toda a carga dos módulos e componentes que estão sendo utilizados em qualquer rota do arquivo.&lt;/p&gt;

&lt;p&gt;Em meus estudos recentes, onde eu estava melhorando o sistema de autenticação da minha aplicação no Vue, e me deparei com uma outra forma de se fazer isso, que basicamente você ira dar o import no atributo component, ficando assim.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {createRouter, createWebHistory} from "vue-router";

const routes = [
    {
        path: "/sobre",
        name: "about",
        component: () =&amp;gt; import("../../vue/view/about/AboutView.vue"),
    },
    {
        path: "/login",
        name: "login",
        component: () =&amp;gt; import("../../vue/view/login/LoginView.vue"),    
    }
]

const router = createRouter({
    history: createWebHistory(),
    routes
})

export default router
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dessa forma, não damos o import no topo do arquivo, e o navegador vai carregar o módulo do LoginView e seus compoentes, somente quando a página de login for acessada.&lt;/p&gt;

&lt;p&gt;Hoje o assunto era esse, algo rápido mesmo, até mais dev!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>vue</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Deploy aplicação Laravel para hospedagem KingHost</title>
      <dc:creator>Jhonatan Henkel</dc:creator>
      <pubDate>Thu, 07 Sep 2023 14:24:24 +0000</pubDate>
      <link>https://forem.com/jhonhenkel/deploy-aplicacao-laravel-para-hospedagem-kinghost-1fbh</link>
      <guid>https://forem.com/jhonhenkel/deploy-aplicacao-laravel-para-hospedagem-kinghost-1fbh</guid>
      <description>&lt;p&gt;Olá galera, hoje vou compartilhar a experiência de fazer deploy da minha aplicação para a KingHost, minha aplicação utiliza as seguintes tecnologias:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apache;&lt;/li&gt;
&lt;li&gt;Laravel (PHP 8.2);&lt;/li&gt;
&lt;li&gt;Vue.Js (fazendo o build com Vite e Node);&lt;/li&gt;
&lt;li&gt;E outras que não vão ser relevantes para esse post.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Temos algumas formas de se fazer esse deploy. Mas antes, como de costume, vamos ao problema:&lt;/p&gt;

&lt;h3&gt;
  
  
  O Problema
&lt;/h3&gt;

&lt;p&gt;Como a KingHost em seu plano mais barato é um servidor compartilhado, temos algumas limitações, como por exemplo, as configurações de Nginx é por crud própria deles e entre outras.&lt;/p&gt;

&lt;p&gt;Meu problema inicial era fazer o build da aplicação no servidor, sim, eu não tinha o conhecimento que dava para fazer isso automaticamente por "fora", na minha cabeça, deveria ser dentro do servidor.&lt;/p&gt;

&lt;p&gt;Então, com esse pensamento, eu precisava do Node no servidor para rodar o bendito comando de build lá dentro. Ai foi onde os problemas começaram a surgir.&lt;/p&gt;

&lt;p&gt;Primeiro problema, o servidor da KingHost com Apache e PHP 8.2 não suporta Node acima da versão 10 e o Vite exige Node acima da versão 16.&lt;/p&gt;

&lt;p&gt;Segundo problema, o servidor que suporta o Node que eu precisava, só tinha Nginx, que como é configurado via crud deles, é super limitado.&lt;/p&gt;

&lt;p&gt;Trocando ideias com um amigo, ele me explicou que o build deve ser feito fora do servidor, logo, eu não preciso de Node no servidor. Basta configurar o pipeline para enviar os dados para lá via RSync, mas adivinhem, depois de perder um tempo indo atrás de como configurar o RSync, descobri que ele é bloqueado na KingHost.&lt;/p&gt;

&lt;p&gt;Isso nos leva ao segundo tópico desse post, que é como eu fazia até então.&lt;/p&gt;

&lt;h3&gt;
  
  
  Como eu fazia o Deploy antes
&lt;/h3&gt;

&lt;p&gt;Na KingHost temos como fazer a publicação via Git, basta configurar a conta do GitHub lá dentro do painel deles e configurar a aplicação, isso é bem simples também. A cada merge o GitHub dispara um WebHook para a KingHost, que por sua vez, faz um git pull na pasta do projeto.&lt;/p&gt;

&lt;p&gt;Como eu precisava do build dos arquivos do Vue.Js, rodava o build local e subia via FTP a todo santo merge com a branch main.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Solução
&lt;/h3&gt;

&lt;p&gt;Por incrível que parece e tenha me dado vontade, a solução não foi migrar de servidor para outra plataforma… Foi algo, em teoria, mais fácil de se fazer.&lt;/p&gt;

&lt;p&gt;A solução foi configurar o pipeline para fazer o build e mandar para a KingHost via FTP pelo próprio pipeline. Simples assim.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Encerramos por aqui, a solução foi essa. Brincadeira, segue o rolo…&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Vamos ao passo a passo desde o zero de como fazer esse deploy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configurando o WebHook
&lt;/h3&gt;

&lt;p&gt;Vamos começar pelo pré suposto que você já tem o vinculo entre a KingHost e a sua conta do GitHub, então aqui neste passo é apenas configurar o WebHook mesmo. Basta ir na área de publicações via Git no painel principal da KingHost e escolher a opção GitHub nas conexões com repositório.&lt;/p&gt;

&lt;p&gt;Aqui, teremos quatro configurações bem simples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repositório: fica em uma listagem, basta selecionar o radio button referente ao seu repositório.&lt;/li&gt;
&lt;li&gt;Branch: aqui deve ser digitado o nome da sua branch principal, um cuidado aqui, no GitHub, o nome padrão da branch é main já na KingHost é master. Se você colocar o nome errado, só não vai funcionar, mas sem nenhum aviso em nenhum lugar que isso está errado.&lt;/li&gt;
&lt;li&gt;Diretório: nesta configuração, deve ser digitado o caminho no qual o ser repositório irá ficar no servidor.&lt;/li&gt;
&lt;li&gt;Configurações: basta selecionar se deseja que a KingHost apenas cadastre o WebHook ou se deve fazer o clone do projeto para a pasta especificada. Se quiser apenas cadastrar o WebHook, tenha em mente que o git (pasta .git) tem que estar na pasta do seu projeto.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Com essas opções configuradas o Deploy ao fazer merge com a main já deve estar funcionando. Caso utilize o composer para algo, terá que conectar via SSH com o seu servidor para rodar o composer update manualmente.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pipeline de Deploy
&lt;/h3&gt;

&lt;p&gt;Vou pular a parte do job de testes, e vamos direto para o job de deploy…&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deploy:
    runs-on: ubuntu-latest
    needs: [ back-end-tests ]

    steps:
      - name: 🔥 Configuring checkout V3
        uses: actions/checkout@v3

      - name: 🔥 Configuring Node
        uses: actions/setup-node@v3
        with:
          node-version: 'latest'

      - name: 🔨 Installing Node dependencies
        run: npm install

      - name: 📄 Making .env
        run: |
          echo "${{ secrets.ENV_DEPLOY }}" &amp;gt; .env

      - name: 🔨 Building application
        run: npm run build

      - name: 📂 Upload files
        uses: SamKirkland/FTP-Deploy-Action@v4.3.4
        with:
          server: ${{ secrets.FTP_ADDRESS }}
          username: ${{ secrets.FTP_USERNAME }}
          password: ${{ secrets.FTP_PASSWORD }}
          local-dir: ./public/build/
          server-dir: ./projects/project-abc/public/build/
          dangerous-clean-slate: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Praticamente esse é o pipeline que faz a magica acontecer. Vamos a explicação dele.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deploy:
    runs-on: ubuntu-latest
    needs: [ back-end-tests ]

    steps:
      - name: 🔥 Configuring checkout V3
        uses: actions/checkout@v3

      - name: 🔥 Configuring Node
        uses: actions/setup-node@v3
        with:
          node-version: 'latest'

      - name: 🔨 Installing Node dependencies
        run: npm install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa parte acima, diz que para esse job vai rodar em uma imagem do Ubuntu e tem que esperar o job back-end-tests acabar com sucesso, caso de algum erro no job, o deploy não ocorre.&lt;/p&gt;

&lt;p&gt;Esses três passos (steps) iniciais está apenas configurando o Checkout e o Node, instalando todas as dependências necessárias para o build acontecer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: 📄 Making .env
  run: |
    echo "${{ secrets.ENV_DEPLOY }}" &amp;gt; .env

- name: 🔨 Building application
  run: npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aqui, estamos gerando o .env, um ponto de atenção aqui, o .env está vindo do secrets do GitHub, caso seu .env tenha variáveis tipo parecida com essa:&lt;/p&gt;

&lt;p&gt;VITE_PUSHER_HOST=”${PUSHER_HOST}”&lt;/p&gt;

&lt;p&gt;Ao fazer o a cópia do secret, ele não irá interpretar o ${PUSHER_HOST} e deixará o VITE_PUSHER_HOST vazio. Sendo assim, no build os locais que usam essa variável, ficará sem valor, podendo dar dor de cabeça para descobrir isso.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Demorei umas 4 horas para descobrir isso, pois meu token api estava dessa forma no .env, para resolver, no .env do secret deixei o valor duplicado mesmo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Em seguida, rodamos o build para gerar os arquivos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: 📂 Upload files
  uses: SamKirkland/FTP-Deploy-Action@v4.3.4
  with:
    server: ${{ secrets.FTP_ADDRESS }}
    username: ${{ secrets.FTP_USERNAME }}
    password: ${{ secrets.FTP_PASSWORD }}
    local-dir: ./public/build/
    server-dir: ./projects/project-abc/public/build/
    dangerous-clean-slate: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nesse passo é onde a mágica acontece, basicamente temos via secret o endereço, login e senha do FTP. Outro ponto de atenção aqui. na KingHost por padrão o acesso ao FTP é bloqueado para ip's de fora do Brasil, o pipeline roda no servidor do GitHub, logo, é de fora do brasil.&lt;/p&gt;

&lt;p&gt;Basta apenas entrar na guia de gerenciar FTP no painel da KingHost e liberar o acessos Global.&lt;/p&gt;

&lt;p&gt;Na opção &lt;strong&gt;local-dir&lt;/strong&gt;, é onde está a origem que deseja copiar, no nosso caso está em &lt;strong&gt;./public/build&lt;/strong&gt;, ou seja, é onde o nosso build foi gerado.&lt;/p&gt;

&lt;p&gt;Na opção &lt;strong&gt;server-dir&lt;/strong&gt;, é onde os arquivos deverão ir, ou seja, ao se conectar via FTP a pasta padrão no qual caímos é a www, então basta apontar onde deve ficar o build do projeto.&lt;/p&gt;

&lt;p&gt;Na opção &lt;strong&gt;dangerous-clean-slate&lt;/strong&gt; é onde vem o pulo do gato, como a cada build os arquivos terão nomes diferentes, se não definirmos ela como true, vai só jogar os arquivos lá, gerando um monte de arquivos desnecessários. Com essa opção ele vai limpar (apagar) tudo no diretório do servidor para então jogar os arquivos novos.&lt;/p&gt;

&lt;p&gt;Com isso temos o nosso build sengo feito para o servidor, sem a necessidade de pagar a mais pelo Node e migrar o Apache para Nginx.&lt;/p&gt;

&lt;p&gt;Ainda temos pendências aqui. O Migration e o update do composer ainda é manual, mas como isso é mais raro de precisar fazer, não vou esquentar a cabeça com isso agora.&lt;/p&gt;

&lt;p&gt;Até poderíamos rodar o composer no pipeline e enviar para o servidor por FTP igual fizemos com o build, mas como é uma pasta com muitos arquivos, a KingHost corta a conexão com o pipeline após certo tempo, sim antes do upload ser completado.&lt;/p&gt;

&lt;p&gt;Penso em futuramente fazer algo que se conecte via SSH e dá os comandos. Mas isso talvez é assunto para um outro post.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resumo
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Configurar o WebHook para dar o git pull;&lt;/li&gt;
&lt;li&gt;Configurar o projeto;&lt;/li&gt;
&lt;li&gt;Liberar acesso para fora do país no FTP;&lt;/li&gt;
&lt;li&gt;Criar pipeline para fazer o build;&lt;/li&gt;
&lt;li&gt;Mandar o build do pipeline para o servidor via FTP;&lt;/li&gt;
&lt;li&gt;Se necessário, conectar via SSH para atualizar o composer e o banco.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Com isso, vou ficando por aqui, até a próximo pessoal ;)&lt;/p&gt;

</description>
      <category>kinghos</category>
      <category>laravel</category>
      <category>deploy</category>
      <category>hosting</category>
    </item>
    <item>
      <title>Lentidão ao rodar o Vite dentro de container Docker em Mac Antigo</title>
      <dc:creator>Jhonatan Henkel</dc:creator>
      <pubDate>Thu, 07 Sep 2023 11:33:17 +0000</pubDate>
      <link>https://forem.com/jhonhenkel/lentidao-ao-rodar-o-vite-dentro-de-container-docker-em-mac-antigo-594</link>
      <guid>https://forem.com/jhonhenkel/lentidao-ao-rodar-o-vite-dentro-de-container-docker-em-mac-antigo-594</guid>
      <description>&lt;p&gt;Olá rede, tudo bem com vocês?&lt;/p&gt;

&lt;p&gt;Recentemente escrevi um post sobre como ganhar desempenho em sua maquina de desenvolvimento MacOS com Docker.&lt;/p&gt;

&lt;p&gt;Recapitulando, meu setup é um pouco mais antigo, então essa é a principal causa do problema.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Meu setup: MacBook Pro 2017 (i5, 8Gb Ram, 120Gb SSD)&lt;br&gt;
Tive a oportunidade de rodar esse mesmo projeto em um Macbook M2 Pro de um amigo e não tive nenhum desses problemas de desempenho, ou seja, o problema é o meu hardware mesmo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Após muitas, muitas, muitas e muitas pesquisas, consegui chegar a uma solução. Mas primeiramente vamos tomar conhecimento do problema, para só então ir para a solução.&lt;/p&gt;

&lt;h3&gt;
  
  
  O Problema
&lt;/h3&gt;

&lt;p&gt;Tenho uma aplicação de finanças &lt;a href="http://my-finances-planner-demo.jhon.dev.br/login" rel="noopener noreferrer"&gt;demo aqui&lt;/a&gt;, onde rodo em máquina local com Docker, utilizo Laravel no back end e Vue.js gerenciado pelo Vite no front end. Para desenvolver nessa aplicação, temos o ritual básico:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;docker-compose start&lt;/li&gt;
&lt;li&gt;docker exec -it my_finances_planner_app bash&lt;/li&gt;
&lt;li&gt;npm run dev&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Após esses passos tenho meu container iniciado, e o rot reload funcionando. Porém, ao rodar essa última etapa o desempenho da aplicação e da minha maquina despencava. Vamos aos números.&lt;/p&gt;

&lt;p&gt;Rodando o npm run dev (Hot reload ativo):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~3 segundos para trazer um registro da API via Insomnia;&lt;/li&gt;
&lt;li&gt;~24 segundos para o primeiro carregamento da aplicação;&lt;/li&gt;
&lt;li&gt;~12 segundos para os demais recarregamentos (F5 ou Ctrl+R);&lt;/li&gt;
&lt;li&gt;~5 segundos para aplicar alterações via hot reload;&lt;/li&gt;
&lt;li&gt;~20% CPU ociosa.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sem rodar o npm run dev (projeto com o front compilado):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~700ms para trazer um registro da API via Insomnia;&lt;/li&gt;
&lt;li&gt;~10 segundos para o primeiro carregamento da aplicação;&lt;/li&gt;
&lt;li&gt;~5 segundos para os demais recarregamentos (F5 ou Ctrl+R);&lt;/li&gt;
&lt;li&gt;~2 segundos para aplicar alterações via hot reload;&lt;/li&gt;
&lt;li&gt;~80% CPU ociosa.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bem lento né? fica praticamente inviável desenvolver assim, e mais inviável ainda ficar compilando o projeto a cada alteração, concorda?&lt;/p&gt;

&lt;h3&gt;
  
  
  O motivo
&lt;/h3&gt;

&lt;p&gt;O código alterado se encontra fora do Docker, ou seja, cada alteração feita vai primeiramente refletir-se dentro do container, que por sua vez reflete na maquina virtual Linux que o Docker usa no MacOs, o Vite fica trabalhando dentro do container observando as alterações, assim que ele detectar a alteração, vai refletir no navegador.&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%2Fdltn6banpyk71s6gqeir.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%2Fdltn6banpyk71s6gqeir.png" alt="Motivo lentidão" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Como o Docker utiliza uma VM Linux, o desempenho dessa VM é bem menor que o da maquina real, e todas essas etapa demandam tempo e recurso.&lt;/p&gt;

&lt;p&gt;Como o meu hardware é um pouco mais antigo, isso acaba demandando muito recurso.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Solução
&lt;/h3&gt;

&lt;p&gt;Como a máquina local é mais rápida que a VM Linux, e a maquina local suporta o Node (Necessário para rodar o build e o hot reload), cheguei a conclusão de encurtar esse processo descrito acima.&lt;/p&gt;

&lt;p&gt;No caso eu instalei o Node localmente, rodo o hot reload (npm run dev) no terminal local. Dessa forma, o hot reload roda na máquina mais rápida (Mac, a maquina de desenvolvimento) sem precisar de toda a virtualização do Linux e consegue fazer o seu serviço bem mais rápido.&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%2Fixpj7zi7p4blbm6t2r71.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%2Fixpj7zi7p4blbm6t2r71.png" alt="Solução Lentidão" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Neste caso o hot reload observa o código local, e não o refletido dentro da VM Linux. Sendo assim, muito mais rápido.&lt;/p&gt;

&lt;p&gt;O Resultado em números foi muito próximo aos apresentados acima sem rodar o npm run dev, só que a diferença é que agora tenho o mesmo resultado rodando o npm run dev.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finalizando...
&lt;/h3&gt;

&lt;p&gt;Alguns podem dizer que essa não é a forma certa, pois estou rodando o node fora do container. Pode até não ser, mas funciona, e melhor, com desempenho bem maior que anteriormente e para quem não tem como comprar um Mac de última geração, é uma ótima alternativa.&lt;/p&gt;

&lt;p&gt;Por hoje é só pessoal, nos vemos em breve!&lt;/p&gt;

</description>
      <category>macboo</category>
      <category>docker</category>
      <category>node</category>
      <category>vite</category>
    </item>
    <item>
      <title>Alternativa ao Docker Desktop para MacOS</title>
      <dc:creator>Jhonatan Henkel</dc:creator>
      <pubDate>Thu, 07 Sep 2023 11:24:02 +0000</pubDate>
      <link>https://forem.com/jhonhenkel/alternativa-ao-docker-desktop-para-macos-31c8</link>
      <guid>https://forem.com/jhonhenkel/alternativa-ao-docker-desktop-para-macos-31c8</guid>
      <description>&lt;p&gt;Olá pessoal, recentemente migrei do bom e velho Ubuntu para o MacOS. Como o MacBook que eu peguei já é um pouco mais antiguinho (MacBook Pro 2017, 8GB Ram e 120 SSD), tive problema com o nosso amigo Docker Desktop.&lt;/p&gt;

&lt;p&gt;O alto consumo de CPU do Docker, junto com o PHP Storm e as outras ferramentas do meu uso, rodar a aplicação estava se tornando um grande desafio.&lt;/p&gt;

&lt;p&gt;Depois de muitas pesquisas (Não achei nada muito interessante), e conversar com o A.J. Meireles, da página Eu sei PHP &lt;a href="https://www.instagram.com/euseiphp/" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt;, fui recomendado a utilizar o &lt;a href="https://orbstack.dev/" rel="noopener noreferrer"&gt;OrbStack&lt;/a&gt;, de inicio duvidei, mas depois de usar um pouco já consegui notar uma enorme melhoria no consumo de CPU. Na questão de consumo de memória, não reduziu muito, mas reduziu.&lt;/p&gt;

&lt;p&gt;Ele funciona de forma parecida com o Docker Desktop em seu visual tendo todos aqueles menus visuais de containers, volumes e imagens.&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%2F9lv5rj9xyjl1hxprm7q2.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%2F9lv5rj9xyjl1hxprm7q2.png" alt="Image description" width="800" height="657"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Já na parte de montar e gerenciar as coisas pelo terminal, é tudo exatamente igual, pois ele usa o Docker engine e o Docker Compose, então nos comandos, vai ficar tudo igual, por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker compose up -d
docker compose start
docker compose stop
docker ps
docker ps -a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Todos esses comandos continuarão funcionando exatamente igual já era com o Docker Desktop, pois no core, ele está usando o Docker.&lt;/p&gt;

&lt;p&gt;Um problema que eu ainda tenho é o hot reload do Vite, isso esse programa não resolve. O problema é que com o "npm run dev" o reload das páginas fica super lento, bem demorado. Mas isso deve-se ao fato de no MacOS o funcionamento ser semelhante ao Windows, que usa o WSL para o container rodar dentro de uma maquina Linux virtualizada.&lt;/p&gt;

&lt;p&gt;Segundo a documentação do Vite, o hot reload é incompatível com o WSL e que quando usado pode ter um alto consumo de CPU e consequentemente de energia, mas enfim, isso é assunto para um outro post.&lt;/p&gt;

&lt;p&gt;Em seu site, a proposta do OrbStack é realmente ser muito mais leve que o Docker Desktop. E assim como o prometido ele cumpre. Não sei se os números apresentados são reais considerando o hardware no qual estou rodando.&lt;/p&gt;

&lt;p&gt;Uma coisa que temos que nos atentar é que em seu site de suporte, ele menciona que ficará gratuito enquanto estiver em sua versão beta. Dependendo o valor de sua assinatura, não valerá a pena.&lt;/p&gt;

&lt;p&gt;Como eu já tinha o Docker Desktop instalado, só desabilitei o inicio automático do Docker Desktop, dando lugar para o OrdStack. Então, talvez se você for instalar esse software, seja uma boa ideia manter o Docker Desktop instalado e apenas deixar ele de lado, como segunda opção, para caso precise.&lt;/p&gt;

&lt;p&gt;Outro ponto que vale ressaltar, é que seus containeres já criados no Docker Desktop, não existirão ainda no OrbStack, sendo necessário finalizar o Docker Desktop e rodar o "docker compose up -d" em seus projetos.&lt;/p&gt;

&lt;p&gt;Enfim, caso você, assim como eu, tem problemas com o consumo do Docker Desktop, fica ai a dica, o OrbStack com certeza vai te ajudar.&lt;/p&gt;

</description>
      <category>macos</category>
      <category>docker</category>
      <category>orbstack</category>
      <category>containers</category>
    </item>
    <item>
      <title>Criando um comando Artisan personalizado para definir valores de variáveis no .env no Laravel</title>
      <dc:creator>Jhonatan Henkel</dc:creator>
      <pubDate>Thu, 07 Sep 2023 11:15:52 +0000</pubDate>
      <link>https://forem.com/jhonhenkel/criando-um-comando-artisan-personalizado-para-definir-valores-de-variaveis-no-env-no-laravel-50k0</link>
      <guid>https://forem.com/jhonhenkel/criando-um-comando-artisan-personalizado-para-definir-valores-de-variaveis-no-env-no-laravel-50k0</guid>
      <description>&lt;p&gt;Olá, dev!&lt;/p&gt;

&lt;p&gt;Em algum momento você já precisou definir algum hash dinâmico no seu arquivo .env? Como por exemplo um authorization api da sua aplicação?Não é nada seguro deixar esse hash no .env.exemple em seu repositório do GitHub e é super chato ter que ficar gerando o hash e colando lá toda vez que você vai configurar o projeto, estou certo?&lt;/p&gt;

&lt;p&gt;Mas em algum momento você, assim como eu, já deve ter pensado a seguinte frase:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Seria ótimo se tivesse um comando igual ao de gerar o app_key para a minha variável…&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;E se eu te disser que tem como?&lt;/p&gt;

&lt;p&gt;Primeiramente defina a variável em .env e no .env.exemple para você não perder ela, deixe igual no meu exemplo a seguir.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Apenas isso já vai garantir que o comando jogue o valor do hash nessa variável no .env, ela somente deve existir sem um valor atribuído.&lt;/p&gt;

&lt;p&gt;Em seguida, use o seguinte comando na pasta raiz do seu projeto, se for contêiner Decker, rode dentro do contêiner.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:command MyVarHash --command=key:my-var-hash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A explicação para o comando acima é a seguinte: php artisan make:command vai gerar um arquivo no diretório app/Console/commands que é o diretório para os arquivos de comandos personalizados do Laravel. MyVarHash será o nome do arquivo que irá salvar no diretório mencionado e por ultimo, — — command=key:my-var-hash, é o comando que irá rodar esse arquivo, que no nosso exemplo ficará php artisan key:my-var-hash.&lt;/p&gt;

&lt;p&gt;Após rodar esse comando, você terá uma classe mais ou menos assim:&lt;br&gt;
&lt;/p&gt;

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

namespace App\Console\Commands;

use Illuminate\Console\Command;

class MyVarHashKey extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'key:my-var-hash';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Generate my env key value';

    /**
     * Execute the console command.
     */
    public function handle(): void
    {

    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Na variável $signature vai ser definido o comando artisan para se rodar, caso queira mudar, aqui é o local. Já a variável $description vai conter a descrição do que o seu comando faz, mostra no php artisan list. No método handle é onde vai ficar o código a ser executado quando rodarmos o comando.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Neste caso em específico o meu post é para alterar uma chave .env, mas se quiser que faça qualquer outra coisa, é só desenvolver dentro do método handle.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Vamos lá, no método handle vamos desenvolver o seguinte código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    public function handle(): void
    {
        $key = md5(uniqid()) . md5(uniqid());
        if (! $this-&amp;gt;setKeyInEnvironmentFile($key)) {
            $this-&amp;gt;info('My-var-hash generate error.');
            return;
        }
        $this-&amp;gt;laravel['config']['my.var.hash'] = $key;
        $this-&amp;gt;info('My-var-hash generate success.');
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Na variável $key tenho um hash gerado dinamicamente, tendo em vista que é dois md5 de um id único concatenando, ou seja, nunca será igual.&lt;/p&gt;

&lt;p&gt;Teremos um método para inserir esse hash no .env que vai retornar um booleano, caso false exibe uma informação de erro e para a execução do método, caso retorne true, define a variável no laravel e exibe uma mensagem de sucesso, também parando a execução do método, já que chegou ao seu fim.&lt;/p&gt;

&lt;p&gt;No método setKeyInEnvironmentFile, que vai ser o responsável por inserir o hash na nossa variável do .env, nele encontraremos o código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    protected function setKeyInEnvironmentFile(string $key): bool
    {
        $filepath = $this-&amp;gt;laravel-&amp;gt;environmentFilePath();
        $input = file_get_contents($filepath);
        $replaced = preg_replace($this-&amp;gt;keyReplacementPattern(), 'MY_VAR=' . $key, $input);
        if ($replaced === $input || $replaced === null) {
            $this-&amp;gt;error('Unable to set my var key. No MY_VAR variable was found in the .env file.');
            return false;
        }
        file_put_contents($filepath, $replaced);
        return true;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Como esse método será executado somente dentro dessa classe, vou defini-lo como protected, recebendo por parâmetro o hash na variável $key. Na variável $filepath iremos trazer a localização do arquivo .env, já na variável $input pegaremos o conteúdo do arquivo .env na variável $replaced vamos fazer o replace da variável vazia que criamos anteriormente, aqui chamaremos outro método para fazer o escape.&lt;/p&gt;

&lt;p&gt;Validamos a variável $replaced está igual o $input, ou se está nula, caso essas condições sejam verdadeiras, retorna um erro na tela e para a execução do código, já que reotna false para o primeiro método que vai cortar a execução.&lt;/p&gt;

&lt;p&gt;Por fim nesse método teremos a gravação do novo arquivo .env com as alerações já feitas, retornando true para o método inicial.&lt;/p&gt;

&lt;p&gt;Assim teremos a classe da seguinte forma:&lt;br&gt;
&lt;/p&gt;

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

namespace App\Console\Commands;

use Illuminate\Console\Command;

class MyVarHashKey extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'key:my-var-hash';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Generate my env key value';

    /**
     * Execute the console command.
     */
    public function handle(): void
    {
        $key = md5(uniqid()) . md5(uniqid());
        if (! $this-&amp;gt;setKeyInEnvironmentFile($key)) {
            $this-&amp;gt;info('My-var-hash generate error.');
            return;
        }
        $this-&amp;gt;laravel['config']['my.var.hash'] = $key;
        $this-&amp;gt;info('My-var-hash generate success.');
    }

    protected function setKeyInEnvironmentFile(string $key): bool
    {
        $filepath = $this-&amp;gt;laravel-&amp;gt;environmentFilePath();
        $input = file_get_contents($filepath);
        $replaced = preg_replace($this-&amp;gt;keyReplacementPattern(), 'MY_VAR=' . $key, $input);
        if ($replaced === $input || $replaced === null) {
            $this-&amp;gt;error('Unable to set my var key. No MY_VAR variable was found in the .env file.');
            return false;
        }
        file_put_contents($filepath, $replaced);
        return true;
    }

    protected function keyReplacementPattern(): string
    {
        $escaped = preg_quote('=' . $this-&amp;gt;laravel['config']['my.var.hash'], '/');
        return "/^MY_VAR{$escaped}/m";
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com isso, basta executar o seu comando artisan e o hash já estará inserido na sua variável dentro do .env, espero que o conteúdo tenha sido útil.&lt;/p&gt;

&lt;p&gt;Até mais, dev ;)&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>terminal</category>
      <category>env</category>
    </item>
    <item>
      <title>Como remover o /build de seu projeto laravel com Vite e Vue.js</title>
      <dc:creator>Jhonatan Henkel</dc:creator>
      <pubDate>Thu, 31 Aug 2023 23:50:15 +0000</pubDate>
      <link>https://forem.com/jhonhenkel/como-remover-o-build-de-seu-projeto-laravel-com-vite-e-vuejs-41kc</link>
      <guid>https://forem.com/jhonhenkel/como-remover-o-build-de-seu-projeto-laravel-com-vite-e-vuejs-41kc</guid>
      <description>&lt;p&gt;Olá rede, tudo bem com vocês? recentemente quebrei bastante a cabeça para remover esse /build de minhas url’s buildadas. Vamos combinar que isso não soa nada profissional e intuitivo de se acessar, sem contar que visualmente fica horrível.&lt;/p&gt;

&lt;p&gt;Após varias e várias pesquisas cheguei a solução, mas antes de dar a solução, vamos a explicação do problema.&lt;/p&gt;

&lt;p&gt;Quando eu rodava o &lt;strong&gt;npm run build&lt;/strong&gt;, para gerar os assets do meu projeto, eu tinha um problema onde as minhas url’s ficavam com o bendito /build, exemplo: &lt;a href="http://localhost/public/build/login" rel="noopener noreferrer"&gt;http://localhost/public/build/login&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enquanto quando rodava o npm run dev a url ficava correta “&lt;a href="http://localhost/login%E2%80%9D" rel="noopener noreferrer"&gt;http://localhost/login”&lt;/a&gt;, sim o public eu resolvi de outra forma, apenas colocando um .htaccess na raiz do projeto.&lt;/p&gt;

&lt;p&gt;Resolver esse problema é muito simples, tão simples que nem acredito que fiquei pesquisando tanto tempo para resolver isso…&lt;/p&gt;

&lt;p&gt;Vamos lá, no seu arquivo de rotas do Vue.js, normalmente chamada de index.js vai ter uma linha assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    routes
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basta apenas remover o import.meta.env.BASE_URL do seu código, ficando assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const router = createRouter({
    history: createWebHistory(),
    routes
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Após isso, basta rodar o build do seu projeto e voialá, problema resolvido.&lt;/p&gt;

</description>
      <category>vue</category>
      <category>buildinpublic</category>
      <category>laravel</category>
      <category>vite</category>
    </item>
    <item>
      <title>Hey dev, saiba como melhorar seu código, fuja dos code smells!</title>
      <dc:creator>Jhonatan Henkel</dc:creator>
      <pubDate>Thu, 24 Aug 2023 21:50:58 +0000</pubDate>
      <link>https://forem.com/jhonhenkel/hey-dev-saiba-como-melhorar-seu-codigo-fuja-dos-code-smells-2m2b</link>
      <guid>https://forem.com/jhonhenkel/hey-dev-saiba-como-melhorar-seu-codigo-fuja-dos-code-smells-2m2b</guid>
      <description>&lt;p&gt;Olá leitor, tudo bem?&lt;/p&gt;

&lt;p&gt;Hoje vamos ver como podemos melhorar seu código com dicas simples, porém nem sempre acabamos vendo esses detalhes.&lt;/p&gt;

&lt;p&gt;Vamos lá, começaremos com um simples código em PHP, que por sinal está bem ruim, iremos melhorar esse código no decorrer desse artigo, então bora lá.&lt;/p&gt;

&lt;p&gt;Vamos começar pelo código em si, segue o ele:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class TotalSmell
{
    public function adminLoginSubmit(){

        if (!isset($_SESSION['admin'])) {

            if ($_SERVER['REQUEST_METHOD'] == 'POST') {

                if (isset($_POST['adminLogin']) || isset($_POST['adminPass'])) {

                    $a = (trim($_POST['adminLogin']));
                    $b = $_POST['adminPass'];

                $new = new AdminModel();
                $VALID = $new-&amp;gt;validateLoginAdmin($a, $b);

                    if ($VALID) {

                            $_SESSION['admin']  = $valid-&amp;gt;id_admin;
                            $_SESSION['user']   = $valid-&amp;gt;usuario_admin;

                        header('location:http://localhost/public/admin/' . '?pagina=' . 'inicio');
                        return;

                    }

                    $_SESSION['error'] = 'Login Inválido';

                    header('location:http://localhost/public/admin/' . '?pagina=' . 'admin-login');
                    return;

                }

                $_SESSION['error'] = 'Login ou senha inválido, tente novamente';

                    header('location:http://localhost/public/admin/' . '?pagina=' . 'admin-login');
                return;
            }

        header('location:http://localhost/public/admin/' . '?pagina=' . 'inicio');
            return;
        }

        header('location:http://localhost/public/admin/' . '?pagina=' . 'inicio');
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basicamente, esse código está validando o login de um usuário admin e salvando em sessão, sim, eu sei, o código está péssimo, mas essa é a intenção neste artigo.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;O que é $a? O que é $b? Para que tanta linha em branco? E essa indentação?ahhhhhhhh vou surtar com tanto code smells!!!!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Melhoraremos um pouco ele, vamos passar por algumas etapas e no final teremos um código bem melhor. Segue o rolo…&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class RemovendoEspacosRenomeandoVariaveis
{
    public function adminLoginSubmit()
    {
        if (!isset($_SESSION['admin'])) {
            if ($_SERVER['REQUEST_METHOD'] == 'POST') {
                if (isset($_POST['adminLogin']) || isset($_POST['adminPass'])) {
                    $adminUser = (trim($_POST['adminLogin']));
                    $adminPass = $_POST['adminPass'];
                    $admin = new AdminModel();
                    $isValid = $admin-&amp;gt;validateLoginAdmin($adminUser, $adminPass);
                    if ($isValid) {
                        $_SESSION['admin']  = $isValid-&amp;gt;id_admin;
                        $_SESSION['user']   = $isValid-&amp;gt;usuario_admin;
                        header('location:http://localhost/public/admin/' . '?pagina=' . 'inicio');
                        return;
                    }
                    $_SESSION['error'] = 'Login Inválido';
                    header('location:http://localhost/public/admin/' . '?pagina=' . 'admin-login');
                    return;
                }
                $_SESSION['error'] = 'Login ou senha inválido, tente novamente';
                header('location:http://localhost/public/admin/' . '?pagina=' . 'admin-login');
                return;
            }
            header('location:http://localhost/public/admin/' . '?pagina=' . 'inicio');
            return;
        }
        header('location:http://localhost/public/admin/' . '?pagina=' . 'inicio');
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perceba que aqui, deixamos esse código um pouco mais limpo, removendo espaços desnecessários, renomeando variáveis e melhorando a indentação. Até aqui, nada de diferente, apenas organização.&lt;/p&gt;

&lt;p&gt;Agora nessa próxima etapa, vamos tirar um pouco da complexidade do código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class TirandoComplexidade
{
    public function adminLoginSubmit()
    {
        if (isset($_SESSION['admin'])) {
            header('location:http://localhost/public/admin/' . '?pagina=' . 'inicio');
        }
        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
            header('location:http://localhost/public/admin/' . '?pagina=' . 'inicio');
            return;
        }
        if (!isset($_POST['adminLogin']) || !isset($_POST['adminPass'])) {
            $_SESSION['error'] = 'Login ou senha inválido, tente novamente';
            header('location:http://localhost/public/admin/' . '?pagina=' . 'admin-login');
            return;
        }
        $adminUser = (trim($_POST['adminLogin']));
        $adminPass = $_POST['adminPass'];
        $admin = new AdminModel();
        $isValid = $admin-&amp;gt;validateLoginAdmin($adminUser, $adminPass);
        if (!$isValid) {
            $_SESSION['error'] = 'Login Inválido';
            header('location:http://localhost/public/admin/' . '?pagina=' . 'admin-login');
            return;
        }
        $_SESSION['admin']  = $isValid-&amp;gt;id_admin;
        $_SESSION['user']   = $isValid-&amp;gt;usuario_admin;
        header('location:http://localhost/public/admin/' . '?pagina=' . 'inicio');
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perceba que sempre que levamos o código mais para a margem (esquerda), mais simples fica de se ler e menos complexo fica de se entender. Nesse exemplo, fica claro o conceito de inversão de condicional, onde invés de fazermos o if pela condição verdadeira, estamos fazendo pela condição falsa e caindo fora do método, deixando assim muito mais simples.&lt;/p&gt;

&lt;p&gt;Exemplo fazendo a condicional em cima da condição verdadeira:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if ($variavel) {
  lógica...
  lógica...
  mais lógica...
  return $resultado;
}
return false;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exemplo fazendo a condicional em cima da condição negativa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (!$variavel) {
  return false;  
}
lógica...
lógica...
mais lógica...
return $resultado;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perceba que se $variavel for falso, nulo, ou não existir, retorna falso e acabou, se não, segue a vida. Claro, em um único if talvez não fique tão claro, mas voltando para o nosso exemplo de código do inicio, onde temos vários if’s um dentro do outro facilita, e muito.&lt;/p&gt;

&lt;p&gt;Podemos melhorar ainda mais esse código do exemplo inicial, segue mais uma melhoria:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Constants
{
    const START_URL = 'http://localhost/public/admin/?pagina=inicio';
    const LOGIN_URL = 'http://localhost/public/admin/?pagina=admin-login';
    const INVALID_LOGIN_MESSAGE = 'Login inválido';

    public function adminLoginSubmit()
    {
        if (isset($_SESSION['admin'])) {
            header('location:' . self::START_URL);
        }
        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
            header('location:' . self::START_URL);
            return;
        }
        if (!isset($_POST['adminLogin']) || !isset($_POST['adminPass'])) {
            $_SESSION['error'] = self::INVALID_LOGIN_MESSAGE;
            header('location:' . self::LOGIN_URL);
            return;
        }
        $adminUser = (trim($_POST['adminLogin']));
        $adminPass = $_POST['adminPass'];
        $admin = new AdminModel();
        $isValid = $admin-&amp;gt;validateLoginAdmin($adminUser, $adminPass);
        if (!$isValid) {
            $_SESSION['error'] = self::INVALID_LOGIN_MESSAGE;
            header('location:' . self::LOGIN_URL);
            return;
        }
        $_SESSION['admin']  = $isValid-&amp;gt;id_admin;
        $_SESSION['user']   = $isValid-&amp;gt;usuario_admin;
        header('location:' . self::START_URL);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nessa etapa a mudança não é tão grande quanto nossa última mudança, mas não deixa de ser importante, as constantes nos garantem a alteração fácil do código. Perceba que, se eu precisar mudar a url de redirecionamento e o código for para produção, será necessário mudar em diversos lugares, gerando um monte de dor de cabeça e um par de bug’s para resolver…&lt;/p&gt;

&lt;p&gt;Vamos melhorar um pouco mais?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Essa é a ultima desse artigo, eu prometo!&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;class SeparandoEmMetodos
{
    const START_URL = 'http://localhost/public/admin/?pagina=inicio';
    const LOGIN_URL = 'http://localhost/public/admin/?pagina=admin-login';
    const INVALID_LOGIN_MESSAGE = 'Login inválido';

    public function adminLoginSubmit():void
    {
        $this-&amp;gt;isAdminLogged();
        $this-&amp;gt;isPostMethod();
        $this-&amp;gt;validatePost();
        $admin = new AdminModel();
        $user = $admin-&amp;gt;validateLoginAdmin(trim($_POST['adminLogin']), $_POST['adminPass']);
        if (!$user) {
            $this-&amp;gt;returnInvalidLogin();
        }
        $this-&amp;gt;returnValidLogin($user);
    }

    public function isAdminLogged(): void
    {
        if (isset($_SESSION['admin'])) {
            $this-&amp;gt;redirect(self::START_URL);
        }
    }

    public function isPostMethod(): void
    {
        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
            $this-&amp;gt;redirect(self::START_URL);
        }
    }

    public function validatePost(): void
    {
        if (!isset($_POST['adminLogin']) || !isset($_POST['adminPass'])) {
            $_SESSION['error'] = self::INVALID_LOGIN_MESSAGE;
            $this-&amp;gt;redirect(self::LOGIN_URL);
        }
    }

    public function returnInvalidLogin(): void
    {
        $_SESSION['error'] = self::INVALID_LOGIN_MESSAGE;
        $this-&amp;gt;redirect(self::LOGIN_URL);
    }

    public function returnValidLogin(stdClass $user): void
    {
        $_SESSION['admin'] = $user-&amp;gt;id_admin;
        $_SESSION['user'] = $user-&amp;gt;usuario_admin;
        $this-&amp;gt;redirect(self::START_URL);
    }

    public function redirect(string $url): void
    {
        header('location:' . $url);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nesse ponto, não deixamos o código pequeno em quantidades de linhas, mas garantimos a reutilização de lógica sem duplicidade. Removemos as duplicidades para métodos, deixando assim mais fácil ainda de se dar manutenção nesse código.&lt;/p&gt;

&lt;p&gt;Ficou bem mais simples, limpo e organizado em comparação ao primeiro exemplo. Perceba que a finalidade do código se manteve a mesma, a lógica principal não mudou, e isso é importante nessa refatoração.&lt;/p&gt;

&lt;p&gt;Perceba também que os nomes dos métodos estão claros e que cada método está com pouca responsabilidade.&lt;/p&gt;

&lt;p&gt;Métodos devem ter preferencialmente uma única responsabilidade, assim você garante a reutilização dos mesmos, facilita fazermos testes unitários, facilita a vida de quem vai fazer o code review e de quem vai dar manutenção nesse código no futuro.&lt;/p&gt;

&lt;p&gt;Com essas dicas que passei aqui, espero que eu tenha conseguido te fazer entender como podemos evoluir nossos códigos. Demorei um tempo para entender esses conceitos, porém depois que entendi isso, meu código evoluiu bastante.&lt;/p&gt;

&lt;p&gt;Espero ter ajudado, até mais ;)&lt;/p&gt;

</description>
      <category>cleancode</category>
      <category>programming</category>
      <category>cleancoding</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
