Leo

Sobre tecnologia...

Armazenando informações em arquivos com PHP - Laravel

6 years ago · 5 MIN READ

No meu dia a dia trabalho muito com desenvolvimento de sites institucionais, frequentemente preciso fazer áreas gerenciaveis para o cliente trocar o conteúdo do site, a primeira coisa que sempre vem à minha cabeça é criar uma tabela de conteúdos no banco para armazerar essa informação.

A criação de uma tabela de conteúdos atende perfeitamente à necessidade, porem se analisarmos outros itens que comumente são armazenados no banco de dados como notícias, usuários, transações, podemos observar o mesmo comportamento: o número de registro da tabela aumenta com o tempo, o que não acontece com a nossa tabela de conteúdos, ela vai ter sempre o mesmo número de registros, e esses registros sempre são alterados

Uma outra discrepância aparece nas áreas gerenciáveis, pois a tabela no banco de dados convencionamente está relacionada com o CRUD, que por sua vez está convencionalmente relacionado à uma estrutura de urls tipo REST, vamos analisar a estrutura de um gerenciador de usuários, por exemplo:

listagem_usuarios_admix.png

Nessa imagem vemos a listagem de usuários que é acessada pela url "/admin/users", essa listagem é importante de um ponto de vista de usabilidade pois através dela podemos buscar usuários específicos, sem ela seria difícil chegar até o form do usuário.

form_usuario_admix.png

Na imagem acima vemos a página que aparece quando clicamos em "editar", essa página tem a url "/admin/users/{id}", através dessa página vamos editar nosso usuário específico.

form_conteudo_admix.png

No nosso "gerenciador" de conteúdo precisaremos de um formulário também, e da mesma rota "edit" que temos para o gerenciador de usuários, podemos utilizar "/admin/contents/{id}" como url, caso você tenha apenas uma linha na sua tabela de conteúdos é possível omitir o parametro "{id}" da rota, dessa forma sempre traremos o primeiro registro da tabela de conteúdos e sempre editaremos o primeiro. Você pode não ter notado mas não existe uma listagem de conteúdos, não faz sentido ter uma listagem pois não precisaremos buscar o conteúdo e a listagem sempre apresentaria o mesmo número de registros, nesse caso gosto de omitir as listagens e manter os links do menu apontando direto para o form, o que estou tentando mostrar é que não há um CRUD completo, consequentemente não temos as respectivas rotas relacionadas às operações ausentes do CRUD, esse é um sinal de que talvez a abordagem padrão não seja a melhor.

Você ta me dizendo que eu vou ter que criar uma migração para adicionar uma tabela que tem apenas um registro?

Em resposta à pergunta acima, supondo que você está utilizando um banco relacional como mysql. Será necessário criar! Nesse ponto que entra os arquivos de texto para simplificar o trabalho, ao invés de guardar as informações em uma tabela temos a opção de guardar em um arquivo de texto, o PHP tem uma API ampla para manipular diversos tipos diferentes de arquivos, vou utilizar como exemplo os arquivos .ini comumente utilizados para guardar configurações.

Nesse link https://github.com/Magicalex/WriteiniFile temos um pacote bem simples que podemos utilizar como exemplo para manipular o arquivo.

[first_section]
one = 1
five = 5
animal = BIRD

[second_section]
path = "/usr/local/bin"
URL = "http://php.net/manual/en/function.parse-ini-file.php"

O texto acima é um exemplo de arquivo .ini tirado do site oficial do PHP, os textos "first_section" e "second_section", que aparecem entre colchetes, representam as páginas gerenciaveis do site, representadas pelas linhas da tabela de conteúdo, discutida na primeira abordagem e os pares de chave e valor são análogos aos campos da tabela de conteúdos porem mais flexível, não precisamos ter os mesmos pares e valores em cada seção.

Uma prática que gosto de adotar porque promove reusabilidade, flexibilidade além de facilitar testes é vincular os serviços que necessito no "service container", isso para quem usa Laravel mas estou certo que outros frameworks trazem abordagens parecidas. Dessa forma podemos tirar proveito da "injeção de dependência", a classe que vamos utilizar não tem grandes dependências mas vamos fazer dessa forma por motivos didáticos.

public function register()
{
    $this->app->singleton(WriteiniFile::class, function(){

        if(!file_exists(base_path('contents.ini'))){
            touch(base_path('contents.ini'));
        }

        return new WriteiniFile(base_path('contents.ini'));
    });
}

Esse códio pode ficar dentro do AppServiceProvider que vem no esqueleto do Laravel, ou de qualquer outro provider que você tenha registrado. Registramos um singleton que retorna a instancia pronta da classe WriteiniFile, sempre que você "chumbar" uma váriavel do tipo WriteiniFile no construtor da sua controller, ou tentar conseguir uma instancia de WriteiniFile dessa forma:

app()->make(WriteiniFile::class);

O "service container" irá procurar um correspondende para você e encontrará o callable que definimos, É importante notar que WriteiniFile recebe um path no seu costrutor, em nosso callable tratamos isso em um "if", caso você queira resolver a instância manualmente, terá que tratar isso em todos os lugares em que for utilizar a classe.

protected $contentWritter;

function __construct(WriteiniFile $contentWritter)
{
    $this->contentWritter = $contentWritter;
}

Dessa forma teremos uma instancia pronta dentro da propriedade $contentWritter no nosso controller.

A classe WriteiniFile não tem uma forma de retornar dados, no projeto da empresa em que trabalho, inseri a classe manualmete no meu namespace e adicionei o seguinte método:

/**
* returns data.
*
* @return array data
*/
public function getData(){
    return $this->data_ini_file;
}

Agora podemos utilizar a classe para preencher o form com o estado inicial

public function edit()
{
    return view('admin.content', $this->contentWritter->getData());
}

As linhas acima criam uma variável para cada seção do .ini, dentro do nosso arquivo content.blade.php, contendo um array de valores, caso você tenha várias páginas e actions diferentes, é possível fazer da seguinte forma:

public function editHome()
{
    return view('admin.home-content', $this->contentWritter->getData()['home']);
}

Agora teremos uma variável para cada "key" da seção "home" do nosso .ini, O seguite input basta para trazer o valor de "description" no .ini abaixo

<form method="post" action="/contents">
<input name="_method" type="hidden" value="put">
<input name="_token" type="hidden" value="{{ csrf_token() }}">
<input name="home[description]" type="text" value="{{ isset($description) ? $description : null }}" required>
<button>Enviar</button>
</form>
[home]
title = 'Titulo teste'
description = 'Descrição teste'

[contact]
phone = "5517997728880"
phone = "5517997728882"

O código todo não foi exatamente esse, o projeto tinha particularidades muito fortes então alguns trechos reescrevi de forma mais simples

public function update(Request $request)
{
    $this->contentWritter->update(['home' => $request->home]);

    if ($this->contentWritter->write()) {
        Flash::success('Conteúdos atualizadas com sucesso.');
    } else {
        Flash::error('Falha na atualização.');
    }

    return back();
}

O código acima atualiza todos os pares da seção home do .ini com os valores do array definido nos inputs do form.

Essa abordagem simplifica bastante o desenvolvimento simplesmente por não envolver estruturas complexas como banco de dados e atende bem o cenário de sites simples.

E o versionamento??

Não gosto de versionar nada que guarda estado da aplicação então não vou adicionar o .ini no versionamento, isso mostra a importância de garantir que o .ini exista, pois caso contrário teríamos que criá-lo manualmente ao clonar o projeto.

E a escalabilidade??

Guardar um arquivo .ini na pasta base do projeto prejudica a escalabilidade, para isso temos que utilizar o S3 ou qualquer outro serviço de armazenamento, não compatível com o que escrevi nesse artigo, a abordagem apresentada contempla sites institucionais simples.

···

Leonardo Lemos


comments powered by Disqus


Proudly powered by Canvas · Sign In