Gerenciando Assets do Bootstrap no CodeIgniter 4 com Composer

Ao desenvolver aplicações web com CodeIgniter 4 e gerenciar dependências de frontend como o Bootstrap via Composer, surge a necessidade de disponibilizar esses arquivos (CSS, JavaScript, fontes, etc.) para acesso público através das views. A pasta vendor, onde o Composer instala as dependências, não deve ser diretamente acessível pela web por questões de segurança e organização. A melhor prática para lidar com isso no CodeIgniter 4 é utilizando a biblioteca Publisher.

Por que usar a Biblioteca Publisher?

A biblioteca Publisher do CodeIgniter 4 [1] foi projetada especificamente para resolver o desafio de copiar arquivos de bibliotecas instaladas via Composer (ou de qualquer outra origem) para um diretório acessível publicamente, como a pasta public do seu projeto. Suas principais vantagens incluem:

  • Gerenciamento de Versões: Facilita a atualização de dependências, pois você pode simplesmente reexecutar o comando de publicação após uma atualização do Composer.
  • Organização e Segurança: Mantém a pasta vendor protegida e garante que apenas os assets necessários sejam expostos publicamente.
  • Automação: Permite automatizar o processo de cópia de arquivos, integrando-o ao fluxo de trabalho de desenvolvimento e deploy.
  • Flexibilidade: Oferece controle granular sobre quais arquivos e diretórios devem ser copiados e para onde.

Implementação com a Biblioteca Publisher

A seguir, detalhamos os passos para integrar o Bootstrap 5.3.8, instalado via Composer, em suas views do CodeIgniter 4 usando a biblioteca Publisher.

1. Criar uma Classe Publisher Personalizada

É recomendável criar uma classe Publisher personalizada para o Bootstrap. Isso permite que você defina a origem e o destino dos arquivos de forma organizada. Crie um novo arquivo, por exemplo, app/Publishers/BootstrapPublisher.php:

<?php

namespace App\Publishers;

use CodeIgniter\Publisher\Publisher;

class BootstrapPublisher extends Publisher
{
    /**
     * Define o caminho de origem dos assets do Bootstrap.
     * Normalmente, é o diretório `vendor/twbs/bootstrap`.
     *
     * @var string
     */
    protected $source = ROOTPATH . 'vendor/twbs/bootstrap';

    /**
     * Define o caminho de destino dos assets do Bootstrap.
     * Normalmente, é o diretório `public/assets/bootstrap`.
     *
     * @var string
     */
    protected $destination = FCPATH . 'assets/bootstrap';

    public function publish(): bool
    {
        return $this
            ->addPath('dist') // Copia todo o conteúdo da pasta 'dist' do Bootstrap
            ->merge(true); // Mescla os arquivos, sobrescrevendo se existirem
    }
}

Explicação:

  • $source: Aponta para o diretório raiz do pacote Bootstrap dentro da pasta vendor.
  • $destination: Define o diretório onde os arquivos do Bootstrap serão copiados dentro da sua pasta public. Recomenda-se public/assets/bootstrap para manter a organização.
  • publish(): Este método é onde você define quais arquivos ou diretórios serão copiados. No exemplo, addPath('dist') instrui o Publisher a copiar todo o conteúdo da pasta dist do Bootstrap (que contém CSS, JS, etc.). merge(true) garante que os arquivos sejam mesclados e sobrescritos se já existirem, o que é útil para atualizações.

2. Executar o Comando Spark Publish

Após criar a classe Publisher, você pode executar o comando spark publish no terminal para copiar os assets. O CodeIgniter 4 irá descobrir automaticamente sua classe BootstrapPublisher.

php spark publish

Este comando copiará os arquivos do Bootstrap da pasta vendor/twbs/bootstrap/dist para public/assets/bootstrap/dist.

3. Incluir os Assets nas Views

Com os arquivos do Bootstrap agora disponíveis na pasta public/assets/bootstrap/dist, você pode incluí-los em suas views (por exemplo, app/Views/layout.php ou app/Views/welcome_message.php) da seguinte forma:

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Minha Aplicação CI4 com Bootstrap</title>
    <!-- Bootstrap CSS -->
    <link href="<?= base_url('assets/bootstrap/dist/css/bootstrap.min.css') ?>" rel="stylesheet">
</head>
<body>
    <div class="container">
        <h1>Olá, Bootstrap no CodeIgniter 4!</h1>
        <button class="btn btn-primary">Botão de Exemplo</button>
    </div>

    <!-- Bootstrap JS e dependências (Popper.js) -->
    <script src="<?= base_url('assets/bootstrap/dist/js/bootstrap.bundle.min.js') ?>"></script>
</body>
</html>

Utilize a função base_url() do CodeIgniter para gerar os caminhos corretos para seus assets, garantindo que eles funcionem independentemente da URL base da sua aplicação.

Alternativas (Menos Recomendadas)

Embora existam outras maneiras de lidar com assets de bibliotecas, elas são geralmente menos recomendadas:

  • Symlinks (Links Simbólicos): Criar links simbólicos da pasta vendor para public pode funcionar, mas pode ser problemático em alguns ambientes de hospedagem e menos portável.
  • Acesso Direto (via .htaccess): Tentar configurar o servidor web para acessar diretamente a pasta vendor é uma má prática de segurança e não é recomendado.
  • Cópia Manual: Copiar os arquivos manualmente é propenso a erros e inviável para projetos com muitas dependências ou atualizações frequentes.

Conclusão

A biblioteca Publisher é a solução mais robusta e recomendada pelo CodeIgniter 4 para gerenciar assets de bibliotecas instaladas via Composer. Ela oferece uma abordagem automatizada, segura e organizada para disponibilizar seus arquivos de frontend, como o Bootstrap, em suas views, seguindo as melhores práticas do framework.

Referências

[1] Publisher — CodeIgniter 4.7.0 documentation

Configurar COLLATE e CTYPE no PostgreSQL

Ao instalar um novo serviço de servidor PostgreSQL as opções de COLLATE e CTYPE podem estar fora da configuração ideal.

Não é possível alterar essas opções em bancos já criados. Uma solução é fazer um backup, criar o banco com as opções desejadas e restaurar o banco na sequencia.

Para deixar as opções no estado padrão desejado, execute o script abaixo.

ALTER database template1 is_template=false;

DROP database template1;

CREATE DATABASE template1
WITH OWNER = postgres
   ENCODING = 'UTF8'
   TABLESPACE = pg_default
   LC_COLLATE = 'en_US.UTF-8'
   LC_CTYPE = 'en_US.UTF-8'
   CONNECTION LIMIT = -1
   TEMPLATE template0;

ALTER database template1 is_template=true;

Trocando o dono (OWNER) de todas tabelas de um banco de dados PostgreSQL

Eventualmente podemos nos defrontar uma um banco de dados cujas tabelas possuem diferentes donos (alguma falta de padrão no momento da criação).

O script abaixo troca o dono para o desejado. Deve ser executado dentro do banco alvo.

SELECT format(
          'ALTER TABLE public.%I OWNER TO novo_dono',
          table_name
       )
FROM information_schema.tables
WHERE table_schema = 'public'
  AND table_type = 'BASE TABLE' \gexec

Seleção global dos elementos da coluna em tabelas html

Em casos onde temos mais de uma coluna com o recurso de seleção global dentro de uma tabela html, podemos usar o exemplo abaixo para controlar qual elemento foi selecionado.

<th data-sortable="false" width="150px" class="text-danger">
    <input class="form-check-input" type="checkbox" id="chkSelectAllRem" />
    <label for="chkSelectAllRem">Todas/Nenhuma</label>
</th>
<th data-sortable="false" width="150px" class="text-success">
    <input class="form-check-input" type="checkbox" id="chkSelectAllEnc" />
    <label for="chkSelectAllEnc">Todas/Nenhuma</label>
</th>

...

<td>
    <div class="form-check">
        <input class="form-check-input" type="checkbox" name="op_rem[]" value="<?= $row->id_pr_ordens ?>"
               id="op_rem.<?= $row->id_pr_ordens ?>">
        <label class="form-check-label text-danger" for="op_rem.<?= $row->id_pr_ordens ?>">Remover</label>
    </div>
</td>
<td>
    <div class="form-check">
        <input class="form-check-input" type="checkbox" name="op_enc[]" value="<?= $row->id_pr_ordens ?>"
               id="op_enc.<?= $row->id_pr_ordens ?>" />
        <label class="form-check-label text-success" for="op_enc.<?= $row->id_pr_ordens ?>">Encerrar</label>
    </div>
</td>

...

<script>
$("th input[type='checkbox']").on("change", function() {
    // remove seleção de todos checkboxes
    $('input[type="checkbox"]').not(this).prop("checked", false);
    // seleciona todos os elemento da coluna selecionada
    var cb = $(this), // checkbox that was changed
        th = cb.parent(), // get parent th
        col = th.index() + 1; // get column index. note nth-child starts at 1, not zero
    $("tbody td:nth-child(" + col + ") input").prop("checked", this.checked); // select the inputs and [un]check it
});

$(document).ready(function() {
    // uncheck other boxes on the same row
    $(".form-check-input").click(function() {
        $('input[type="checkbox"]').change(function() {
            $(this).closest('tr').find('input[type="checkbox"]').not(this).prop('checked', false);
        });
    });
});
</script>

Atualizando estado da linha da tabela com jQuery

De acordo com o conteúdo de cada linha de uma tabela html podemos modificar as propriedades dos elementos usando jQuery.

No exemplo abaixo temos uma tabela onde atribuímos os valores a serem observados nos campos data dentro da tag <tr>.

<form action="<?= $acao_aplicar ?>" method="post" id="form1">
    <input type="hidden" name="id_requisicao" value="<?= $requisicao->id ?> ">
    <table class="table table-sm small table-striped" id="itensTable">
        <thead>
            <tr>
                <th>Item</th>
                <th>Produto</th>
                <th>Descrição</th>
                <th>Unidade</th>
                <th class="text-end">Qt. Calculada</th>
                <th class="text-end">Saldo</th>
                <th width="140px" class="text-end">Qt. Solicitada</th>
            </tr>
        </thead>

        <tbody>
            <?php foreach ($itens as $row): ?>
                <tr class="align-middle" data-produto-ativo="<?= $row->produto_ativo ?>"
                    data-quantidade="<?= $row->qt_solicitada ?>">
                    <!-- item -->
                    <td>
                        <?= $row->item ?>
                    </td>
                    <!-- produto -->
                    <td>
                        <?= $row->produto_codigo ?>
                    </td>
                    <!-- descricao -->
                    <td>
                        <?= $row->produto_descricao ?>
                    </td>
                    <!-- unidade -->
                    <td>
                        <?= $row->produto_unidade ?>
                    </td>
                    <!-- quantidade calculada -->
                    <td class="text-end">
                        <?= number_format($row->qt_calculada, 3, ',', '.') ?>
                    </td>
                    <!-- saldo -->
                    <td class="text-end">
                        <?= number_format($row->saldo, 3, ',', '.') ?>
                    </td>
                    <!-- quantidade solicitada -->
                    <td>
                        <input type="hidden" name="id[]" value="<?= $row->id ?>">
                        <input type="number" style="text-align:right" class="form-control form-control-sm"
                               name="qt_solicitada[]" value="<?= $row->qt_solicitada ?>" required>
                    </td>
                </tr>
            <?php endforeach ?>
        </tbody>

    </table>
</form>

Em seguida, o código jQuery faz as alterações nos elementos.

<script>
    $(document).ready(function () {
        $('#itensTable tbody tr').each(function () {
            var produto_ativo = $(this).data('produto-ativo');
            var quantidade = $(this).data('quantidade');
            if (produto_ativo === 'f') {
                $(this).find('td').addClass('text-danger');
                $(this).find('input').prop('disabled', true);
                $(this).find('input').prop('required', false);
            }
            if (quantidade == 0) {
                $(this).find('td').addClass('text-secondary');
                $(this).find('input').prop('disabled', true);
                $(this).find('input').prop('required', false);
            }
        });
    });
</script>

Dica: nunca utilize underscore nos nomes dos campos data (ex: data-produto_inativo).

XDebug no VSCode para CodeIgniter 4

A junção do PHP com o XDebug permite a depuração do código em passos com a visualização dos valores das variáveis em tempo real.

Para quem tem projetos CI4, a configuração abaixo deve ser inserida no arquivo launch.json do VSCode.

{
    "name": "CI4 Spark XDebug",
    "type": "php",
    "request": "launch",
    "runtimeArgs": [
        "spark",
        "serve",
        "-dxdebug.mode=debug",
        "-dxdebug.start_with_request=yes",
        "-S",
        "localhost:8080",
    ],
    "env": {
        "XDEBUG_MODE": "debug",
        "XDEBUG_SESSION": "factor",
    },
    "externalConsole": false,
    "program": "",
    "cwd": "${workspaceRoot}",
    "port": 9003,
    "serverReadyAction": {
        "action": "openExternally",
        "killOnServerStop": false
}

Campos DATA de bancos MySQL no PDI

Dependendo da versão das bibliotecas de acesso a um banco MySQL, o Pentaho pode retornar erro ao ler colunas do tipo DATE, especialmente no que se refere à interpretação do fuso horário.

A mensagem que normalmente aparece é

HOUR_OF_DAY: 0 -> 1

Definindo o fuso horário nas opções de carga dos aplicativos do PDI resolve esse problema. Por exemplo, colocando a definição “-Duser.timezone=GMT-3” dentro do spoon.bat na linha set OPT=…

Executando jobs e transformações do Pentaho no prompt de comando

Não necessariamente somos obrigados a configurar um PDI Server para agendar a execução dos trabalhos e transformações em um servidor.

É possível executar um comando no servidor que dispara a rotina desejada.

Para executar transformações (ktr) usamos o seguinte comando:

sh pan.sh -rep: -file=<arquivo>.ktr

Para executar trabalhos (kjb) usamos o seguinte comando:

sh kitchen.sh -rep: -file=<arquivo>.kjb

Alternando entre versões do PHP no MacOS

Você pode se deparar com a necessidade de executar um código baseado em uma versão específica do PHP no MacOS. Para tal podemos recorrer ao Docker ou a uma máquina virtual. Mas se você simplesmente quer usar o PHP direto no próprio sistema operacional, essa dica pode te ajudar.

A instalação das versões suportadas do PHP pode ser feita pelo brew e a troca das versões pode ser feita da seguinte maneira dentro do prompt de comando:

brew unlink php@8.2
brew link php@7.4

Para versões não mais suportadas do PHP, podemos utilizar o repositório shivammathur/php:

brew tap shivammathur/php
brew install shivammathur/php/php@7.3

Happy coding!