PHP

Função para converter HTML para PDF usando PHP e wkhtmltopdf no AWS Lambda

Aqui na TreinaWeb sempre usamos o wkhtmltopdf para a conversão de HTML para PDF. É a base da geração dos nossos certificados, ementas, relatórios administrativos etc.

Um problema que sempre tivemos foi com o manejo do binário do wkhtmltopdf. Tínhamos que tê-lo disponível em qualquer instância EC2 que fôssemos utilizar. Quando uma nova versão era lançada, tínhamos que compilá-lo do zero novamente em todas as instâncias que o utilizassem, até mesmo localmente no ambiente de desenvolvimento. Para além disso, tínhamos com frequência alguns erros na geração do PDF por causa de algum pico de uso da CPU por conta dele.

Resolvemos os nossos problemas de uma forma bem tranquila e altamente escalável usando Serverless. Fizemos o deploy de uma função no AWS Lambda especializada na conversão de HTML para PDF. E, para o nosso caso de uso, tudo o que precisávamos era receber o HTML e então retornar um Base64 do PDF. Notamos, também, uma melhor performance na geração dos PDFs.

Se você não conhece o que é Serverless, sugiro a leitura do artigo: Serverless: uma introdução.

E, para que você consiga acompanhar como o deploy da função que converte o HTML para PDF foi feito, é necessário que você tenha lido o artigo: Aplicação Serverless desenvolvida em PHP usando AWS Lambda.

O que utilizaremos daqui pra frente:

  • PHP: essa maravilhosa linguagem de programação.
  • Bref: ferramenta que faz com que seja simples o deploy de aplicações PHP Serverless.
  • Serverless Framework: framework completo para teste e deploy de aplicações nas principais plataformas Serverless do mercado.
  • AWS Lambda: onde faremos o deploy da função.

Como funciona a conversão do HTML para PDF?

O sistema de layers para o Lambda lançado pela AWS no ano passado mudou o jogo completamente. Com ele é possível que uma função use qualquer binário. É possível rodar até Cobol no AWS Lambda. E foi ele que permitiu que agora pudéssemos criar uma função que use o binário do wkhtmltopdf para a conversão de HTML para PDF. Não obstante, ele também é o responsável por podermos usar o PHP no AWS Lambda.

O que inicialmente tivemos que fazer foi compilar o wkhtmltopdf manualmente na nossa máquina para que pudéssemos criar um Layer dele no AWS Lambda pra ser linkado com a função. E, para isso, o artigo Compiling wkhtmltopdf for use inside an AWS Lambda function with Bref is easier than you’d think deixou tudo bem detalhado sobre como isso pode ser feito.

O código completo da função você encontra nesse repositório: KennedyTedesco/wkhtmltopdf-lambda-php. Mas eu vou passar pelos principais pontos aqui nesse artigo.

Primeiro de tudo, vamos avaliar o arquivo serverless.yml:

service: wkhtmltopdf

provider:
    name: aws
    region: sa-east-1
    runtime: provided
    stage: prod
    memorySize: 1024
    timeout: 60

plugins:
    - ./vendor/bref/bref

functions:
    html-to-base64-pdf:
        handler: index.php
        description: 'HTML to Base64 PDF'
        layers:
            - ${bref:layer.php-73}
            - 'arn:aws:lambda:sa-east-1:391960246434:layer:wkhtmltopdf-bin:1'

Veja em layers que temos declarado arn:aws:lambda:sa-east-1:391960246434:layer:wkhtmltopdf-bin:1, esse é o layer que compilamos manualmente e subimos para a AWS. É o layer do binário do wkhtmltopdf. É o que faz ele ficar disponível no diretório /opt/wkhtmltopdf.

E a função propriamente dita é pura e simplesmente isso:

<?php

declare(strict_types=1);

require __DIR__.'/vendor/autoload.php';

use Knp\Snappy\Pdf;

lambda(static function (array $event) {
    $pdf = new Pdf('/opt/wkhtmltopdf');

    $options = [
        'encoding' => 'utf-8',
        'page-size' => 'A4',
        'margin-bottom' => 0,
        'margin-left' => 0,
        'margin-top' => 0,
        'margin-right' => 0,
        'disable-smart-shrinking' => true,
        'disable-javascript' => true,
    ];

    if (isset($event['options'])) {
        $options = \array_merge(
            $options,
            \json_decode($event['options'] ?? '', true)
        );
    }

    $output = $pdf->getOutputFromHtml($event['html'], $options);

    if (empty($output)) {
        throw new \RuntimeException('Unable to generate the html');
    }

    return \base64_encode($output);
});

Usamos a library snappy que abstrai o uso do wkhtmltopdf. No mais, apenas recebemos o HTML e algumas opções para a geração do PDF, executamos o binário e retornamos um base64 do PDF gerado.

Se você tiver seguido o artigo Aplicação Serverless desenvolvida em PHP usando AWS Lambda, para fazer o deploy dessa função na sua infra da AWS, tudo o que você precisará é clonar esse projeto que disponibilizei no Github e então executar:

$ composer install --optimize-autoloader --no-dev

Vai baixar as dependências do projeto. Por fim:

$ serverless deploy

Fará o deploy da função no AWS Lambda.

E como a função é usada nas aplicações?

Não tivemos a necessidade de expor um endpoint do API Gateway para intermediar a execução da função (isso seria perfeitamente possível, principalmente se o serviço fosse uma API de acesso público). Fazemos a invocação direta dela pela SDK da AWS. E a SDK tem implementação para as principais linguagens. No caso do PHP seria algo como:

$lambda = new AwsLambdaClient([
  'version' => 'latest',
  'region' => 'sa-east-1',
  'credentials' => [
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
  ],
]);

$result = $lambda->invoke([
  'FunctionName' => 'wkhtmltopdf-prod-html-to-base64-pdf',
  'InvocationType' => 'RequestResponse',
  'LogType' => 'None',
  'Payload' => \json_encode([
    'html' => '<html>...',
  ]),
]);

$result = \json_decode($result->get('Payload')->getContents(), true); // base64 pdf

E se eu precisar gerar grandes arquivos PDF?

Se o seu caso de uso envolve gerar arquivos PDF de mais de 6MB, esse método da invocação direta não vai ser a melhor opção por causa do limite do tamanho do payload de retorno do AWS Lambda. Nesse caso, a melhor opção é você mudar a estratégia e, ao invés de retornar um base64 do PDF, você passar a salvá-lo em um bucket no S3. E a sua função retornaria um body com o link para acesso ao arquivo, por exemplo:

{
    "url": "https://seu-bucket.s3-sa-east-1.amazonaws.com/pdf/nome-do-arquivo.pdf"  
}

Inclusive, se for necessário, é possível até mesmo ter um bucket para receber os arquivos HTML que precisam ser convertidos e então a função seria invocada para convertê-los e então salvá-los em outro bucket de destino. Algo como:

  • O arquivo HTML é salvo no bucket arquivos-html, esse bucket está configurado para disparar um evento sempre que um novo arquivo é upado, evento esse que vai executar a função que criamos;
  • A função é executada, o PDF é salvo no bucket arquivos-pdf e a função retorna um body com o link para acesso ao arquivo PDF.

As possibilidades são muitas, ademais, na AWS tudo se integra por eventos.

Até a próxima!

Criando um ambiente de desenvolvimento PHP com WSL

O WSL é uma alternativa interessante para quem precisa configurar um ambiente de desenvolvimento em Linux dentro do Windows. Com ele você pode escolher a sua distribuição Linux preferida e configurar seu ambiente exatamente da mesma forma que você configuraria em uma máquina Linux ou em um servidor, por exemplo.

Nesse artigo vamos criar um ambiente de desenvolvimento PHP completo, com MySQL e Nginx dentro do WSL.

Instalação do WSL

Antes de mais nada, é preciso habilitar o WSL na sua máquina e instalar a distribuição de sua escolha. Se você ainda não conhece o WSL ou tem dúvidas se sua máquina tem suporte a ele, recomendo a leitura do artigo O que é Windows Subsystem for Linux (WSL)? para saber mais e acompanhar o processo de instalação.

Para esse tutorial vou utilizar o Ubuntu 18.04. Os comandos apresentados aqui podem não funcionar com outras distribuições, mas você pode utilizar esse artigo para ter uma ideia geral.

Instalando o Nginx

Para iniciar vamos iniciar nosso terminal com o WSL e instalar o Nginx. Ele está disponível via apt-get e pode ser instalado com os comandos abaixo como sudo:

sudo apt-get update
sudo apt-get install nginx

Aproveite esse momento e instale utilitários como o git para versionamento de código e um editor de textos de sua preferência, como o vim:

sudo apt-get install git vim

Depois é necessário habilitar o serviço do Nginx dentro do WSL, antes disso verifique se a porta 80 da sua máquina está liberada. Você pode executar o seguinte comando PowerShell como administrador para descobrir isso:

Get-Process -Id (Get-NetTCPConnection -LocalPort 80).OwningProcess

Se o comando retornar erro, a porta 80 está liberada para uso:

Verificando a porta 80 com PowerShell

Feito isso, volte para seu terminal do WSL e execute o comando:

sudo service nginx start

Com isso seu localhost já estará funcionando:

Página inicial do nginx

Como o WSL não suporta o gerenciamento de serviços usando systemd, sempre que você reiniciar a máquina e iniciar uma nova sessão com o WSL, você precisa iniciar o serviço do Nginx novamente. Veremos como resolver isso logo mais.

Instalando o MySQL

O MySQL 5.7 pode ser instalado a partir do apt-get no Ubuntu 18.04:

sudo apt-get install mysql-server

Como também aconteceu com o Nginx, é preciso iniciar o serviço do MySQL manualmente:

sudo service mysql start

Para acessar sua instância do MySQL, é preciso criar um novo usuário no seu banco de dados para ser utilizado junto com sua aplicação. No seu terminal inicie o cliente do MySQL executando sudo mysql e execute a seguinte query:

GRANT ALL ON *.* TO 'admin'@'localhost' IDENTIFIED BY 'password' WITH GRANT OPTION;

Isso irá criar um usuário chamado admin com a senha password. Esse usuário será acessível apenas pelo endereço localhost. Troque o usuário e senha para algo mais seguro e digite exit para sair do cliente do MySQL.

Teste a conexão com o MySQL utilizando algum cliente, você pode utilizar o MySQL Workbench por exemplo:

Teste da conexão com o MySQL

Instalando o PHP

Agora vamos de fato instalar o PHP e configurá-lo para funcionar com o Nginx. É preciso instalar o PHP-FPM (FastCGI Process Manager) para que o Nginx consiga delegar as requisições recebidas para as páginas a serem interpretadas com PHP. Como estamos utilizando o MySQL como banco de dados, é interessante instalar a extensão do MySQL também:

sudo apt-get install php-fpm php-mysql

Com isso será instalado a versão 7.2 do PHP juntamente com o FPM e as extensões do MySQL:

Versão do php

Depois de instalado, inicie o serviço do PHP-FPM:

sudo service php7.2-fpm start

Configurando nosso site

Por fim, vamos configurar nosso site para ser executado com PHP. Crie um arquivo em /etc/nginx/sites-available/localhost com o seguinte conteúdo:

server {
  listen 80;
  root /var/www/html;
  index index.php index.html index.htm index.nginx-debian.html;
  server_name localhost;

  location / {
    try_files $uri $uri/ =404;
  }

  location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
  }

  location ~ /\.ht {
    deny all;
  }
}

Habilite o site criado para utilizar a nova configuração através de um link simbólico (conhecido também como symlink) no diretório /etc/nginx/sites-enabled e remova a configuração de site padrão com os seguintes comandos:

sudo ln -s /etc/nginx/sites-available/localhost /etc/nginx/sites-enabled/
sudo unlink /etc/nginx/sites-enabled/default

Reinicie o serviço do Nginx para ler essas novas configurações:

sudo service nginx restart

E crie o arquivo /var/www/html/index.php com o seguinte conteúdo:

<?php 
phpinfo();

No meu caso, eu precisei alterar o dono do diretório /var/www/html para poder criar esse arquivo:

sudo chown -R $USER: /var/www/html

Com isso, você deve conseguir ver a configuração do PHP no seu navegador:

Configurações do php

Configurando os serviços para iniciarem automaticamente

Devido a algumas limitações na arquitetura do WSL, não é possível iniciar serviços automaticamente pelo systemd. Para contornar isso podemos iniciar esses serviços a partir do nosso arquivo .bashrc e desabilitar a necessidade de perguntar a senha do sudo para esses serviços.

Lembrando que essa prática não é recomendada em ambientes de produção. Em produção utilize o systemd e nunca permita que serviços que precisam de permissões elevadas rodem sem precisar inserir uma senha.

Adicione as seguintes linhas no seu arquivo /etc/sudoers:

%sudo ALL=NOPASSWD: /etc/init.d/mysql
%sudo ALL=NOPASSWD: /etc/init.d/nginx
%sudo ALL=NOPASSWD: /etc/init.d/php7.2-fpm

E no seu arquivo do ~/.bashrc inclua o seguinte:

sudo /etc/init.d/nginx start
sudo /etc/init.d/mysql start
sudo /etc/init.d/php7.2-fpm start

Com isso, sempre que iniciar seu terminal do WSL os serviços serão iniciados automaticamente. Caso esteja utilizando alguma linguagem diferente, a ideia é a mesma: inclua os serviços necessário no .bashrc e desabilite a senha para esses serviços no /etc/sudoers.

Configurando sua IDE

Após tudo configurado, chegou a hora de configurarmos nossa IDE para utilizar o WSL. Da forma que configuramos nosso ambiente, os scripts PHP estão dentro da estrutura de arquivos do WSL.

Como o WSL não suporta ferramentas com interface gráfica, não podemos instalar nossa IDE.

Podemos resolver isso de algumas formas. Uma delas é salvar nossos arquivos nos diretórios do Windows e dentro do WSL configurar o Nginx para apontar para um diretório dentro de /mnt/c/. Esse diretório é mapeado diretamente com o C: do seu sistema e permite o WSL ter acesso a esses arquivos.

Caso você utilize o VS Code, uma melhor alternativa é utilizar os arquivos salvos diretamente dentro da estrutura do WSL. Existe uma extensão ainda em preview chamada Remote Development. Com ela é possível se conectar com sistemas externos através de SSH, Container ou WSL para trabalhar diretamente com o sistema de arquivos dessas soluções.

Para utilizar o Remote Development com o WSL, instale a extensão Remote – WSL no seu VS code e reinicie o mesmo.

Extensão Remote - WSL

Ao reiniciar, repare no canto esquerdo um novo ícone verde. A partir dele é possível abrir uma nova janela do VS Code com acesso ao sistema de arquivos da sua distribuição no WSL:

Nova janela com acesso ao WSL

Ao abrir essa nova janela, será instalado um agente dentro do WSL que permite a conexão com o VS Code. O ícone verde indicará o nome da sua distribuição que o VS Code está conectado. Ao abrir um diretório você navegará no sistema de arquivos do WSL.

Abra o diretório /var/www/html e podemos observar que nosso index.php está acessível:

Abrir diretório

Arquivos no WSL

Esse agente que é instalado no WSL permite uma melhor integração com o VS Code. Com ele é possível dentro do terminal do WSL abrir qualquer diretório que você esteja navegando utilizando o comando code . e também é possível instalar extensões do VS Code para executarem diretamente dentro do WSL, como por exemplo as extensões de intellisense e debug para PHP:

Extensões instalados no WSL

Para a extensão intellisense do PHP funcionar, é preciso que o executável do PHP esteja disponível no sistema. Instalando essa extensão dentro do WSL você consegue utilizar do executável já disponível no WSL, concentrando todas as funcionalidades necessárias para o seu ambiente em um único lugar.

Conclusão

Aprendemos nesse artigo como criar do zero um ambiente de desenvolvimento para PHP dentro do WSL. Esse ambiente pode ser uma alternativa para quem precisa executar um projeto em ambiente Linux dentro do Windows. Vimos também como configurar o VS Code para acessar diretamente os arquivos dentro do sistema de arquivos do WSL, tornando uma experiência muito próxima com a que teríamos com o Linux.

O que é Symfony?

Software livre sobre a licença MIT, o Symfony é um Framework web escrito em PHP, lançado em 2005.

Sua comunidade conta com mais de 600.000 desenvolvedores de mais de 120 países, tornando a implementação de novas funcionalidades ao Framework um processo rápido.

É um Framework amplamente conhecido por ter sido um dos primeiros a trabalhar com componentes, possibilitando o reuso do seu código em diversos projetos open sources. Os principais pontos em sua filosofia são:

  • A utilização de melhores práticas de desenvolvimento;
  • A criação de aplicações profissionais, e;
  • A interoperabilidade de aplicações.

Padrão MVC

Utiliza o padrão de projeto MVC, que é basicamente:

  • Model: Parte lógica da aplicação que gerencia o comportamento dos dados. A camada de model apenas tem o necessário para que tudo aconteça, mas não sabe quando irá executar.

  • View: Gerencia a saída gráfica e textual da aplicação ao usuário final, não possuindo a responsabilidade de saber quando vai exibir os dados, apenas como irá exibi-los.

  • Controller: Essa é a camada que sabe quem chamar e quando chamar para executar determinada ação, comandando a visão e o modelo para se alterarem de forma apropriada.

Basicamente, o MVC funciona da seguinte forma:

Ao receber uma requisição, o Controller solicita ao Model as informações necessárias (que provavelmente virão do banco de dados), que as obtém e retorna ao Controller. De posse dessas informações, o Controller as envia para a View que irá renderizá-las.

Vantagens do Symfony

  • Orientado a objetos;
  • Possui ferramentas de depuração;
  • Suporte para ambientes de implantação;
  • Recursos avançados de segurança;
  • Documentação completa e bem explicada;
  • ORM: Fácil integração com Doctrine;
  • Integração com PHPUnit;
  • Versões mantidas por um longo período.

Componentes

Os componentes são um conjunto de bibliotecas PHP desacopladas e reutilizáveis, permitindo que sejam utilizadas em qualquer projeto PHP independentemente do Symfony Framework.

Dentre os vários componentes do Symfony, podemos citar:

  • DependencyInjection – Permite padronizar e centralizar a maneira como os objetos são construídos em sua aplicação;
  • Debug – Fornece ferramentas para facilitar a depuração de código PHP;
  • Form – Fornece ferramentas para facilitar a criação, processamento e reutilização de formulários HTML;
  • Mailer – Ajuda a implementar o envio de e-mails e fornece integração com os principais serviços.

Muitos outros componentes podem ser utilizados para te auxiliar na realização de suas aplicações. A lista de todos os componentes do Symfony você pode acessar neste link.

Os componentes do Symfony são usados nos principais projetos PHP. Alguns projetos importantes que fazem o uso são:

  • Laravel Framework
  • Yii Framework
  • Drupal
  • Joomla
  • Magento

É possível ver a lista completa de projetos que usam componentes do Symfony aqui.

Framework ou Micro-Framework?

A partir da versão 4 o Symfony possui uma ferramenta chamada Flex. Ela realiza a instalação e configuração de pacotes de uma maneira extremamente simples e automática. Isso permite ao desenvolvedor iniciar um projeto com o mínimo de dependências e “plugar” novas funcionalidades quando necessário. Baseado nesse recurso quando vamos instalar o Symfony podemos escolher entre uma aplicação mínima com características de micro-framework ou uma aplicação completa, com características de framework.

Quando usamos a estrutura completa do Symfony temos as principais características de um framework:

  • Completo;
  • Integrado;
  • Padronizado;
  • Fácil de deployar.

Quando usamos a estrutura mínima, podemos citar as seguintes características:

  • Leve;
  • Altamente customizável;
  • “Rápido”;
  • Ideal para cenários com micro-serviços.

Ou seja, desta forma, o Symfony traz o melhor dos dois mundos onde atenderá desde aplicações menores a aplicações mais robustas.

Caso ainda não tenha ficado claro a diferença entre Framework e Micro-Framework, temos aqui no blog dois artigos bem legais sobre o assunto. Basta clicar sobre os links. 🙂

Atualizações e Suporte

O Symfony possui cronogramas muito bem definido de manutenção de correções e suporte para cada uma das versões lançadas. Isso garante uma segurança maior, principalmente para projetos corporativos.

As versões comuns contam com 6 meses de suporte a correções e 1 ano de suporte de segurança, enquanto as versões LTS (long term support) 3 anos de suporte para correção e 4 anos de suporte para segurança, sendo a segunda a versão mais indicada para uso em produção.

A cada 6 meses uma nova versão minor é lançada, conforme o cronograma acima. Atualmente estamos na versão 4.3, a versão LTS atual é a 3.4 e a próxima versão LTS é a 4.4 que será lançada em novembro.

Concluindo

O Symfony é um ótimo framework para a criação de aplicações, independente do seu tamanho. Neste artigo vimos algumas de suas principais características.

Promises no ReactPHP

No último artigo – Introdução à programação assíncrona em PHP usando o ReactPHP – tivemos uma introdução à programação assíncrona com PHP.

No artigo de hoje vamos comentar sobre Promises, que no ReactPHP trata-se de um componente. Promises no ReactPHP são uma implementação da API CommonJS Promises/A. Se você trabalha com JavaScript, vai notar muita similaridade com o que veremos a seguir.

Afinal, o que é uma Promise?

Uma Promise é uma abstração que encapsula um resultado de uma execução assíncrona para ser utilizado quando ele estiver disponível. Promises desempenham a mesma tarefa que os Callbacks dos quais já estamos acostumados, só que de maneira bem mais elegante (principalmente quando lidamos com múltiplos callbacks de forma encadeada).

Dois conceitos importantes que temos que conhecer: Promise e Deferred. Já vimos que uma Promise representa o resultado de um código assíncrono, já Deferred representa a computação que vai gerar esse resultado em algum momento futuro. Um objeto Deferred sempre vai ter uma Promise associada que vai representar (encapsular) o resultado. Usar um objeto Deferred é uma forma de separar a Promise de quem vai resolvê-la ou rejeitá-la (em algum momento futuro).

Uma Promise possui três estados possíveis:

  • unfulfilled: É o estado inicial, pois o valor retornado de Deferred ainda é desconhecido.
  • fulfilled: Esse estado indica que a Promise está devidamente “alimentada” do resultado da computação de Deferred.
  • failed: Houve uma exceção durante a execução do Deferred (o executor pode ter chamado reject()).

Já um objeto Deferred possui dois métodos para mudar o estado da sua Promise:

  • resolve(string $result): Quando o código é executado com sucesso, esse método altera o estado da Promise para fulfilled (enviando para ela o resultado que ela encapsulára);
  • reject(string $reason): A execução do código falhou, altera o estado da Promise para failed.

Uma Promise também possui métodos para que definamos handlers (callbacks a serem executados) para as mudanças de estados dela, são esses: then(), done(), otherwise() e always().

Antes de iniciarmos, certifique-se de instalar o componente React Promise no seu projeto:

$ composer require react/promise

Vamos então ao primeiro exemplo do uso de um objeto Deferred e de sua Promise:

<?php

require './vendor/autoload.php';

use React\Promise\Deferred;

$deferred = new Deferred();
$promise = $deferred->promise();

$promise->done(function ($data) {
    echo 'Resultado: ' . $data;
});

$deferred->resolve('Olá mundo!'); // Resultado: Olá mundo!

Criamos um objeto Deferred e definimos um handler para o método done() da Promise associada a ele. Por fim, resolvemos a operação usando o método resolve(), método este que “altera” o estado da Promise (para fulfilled) e fez o nosso handler (a função anônima que definimos no primeiro argumento do método done()) ser executado.

No caso de o objeto Deferred explicitamente rejeitar uma operação alterando o estado da Promise para failed, podemos usar o segundo argumento do método done() para definir um handler a ser executado em caso de rejeição:

O método done() aceita como argumento handlers tanto para o estado fulfilled quanto para o failed:

public function done(callable $onFulfilled = null, callable $onRejected = null)

No primeiro argumento informamos o handler que será executado quando o estado da Promise mudar para fulfilled, no segundo argumento, um handler para ser executado quando o estado da Promise for alterado para failed.

Por exemplo, vamos informar um handler pro segundo argumento da Promise e então vamos executar o método reject() do Deferred:

<?php

require './vendor/autoload.php';

use React\Promise\Deferred;

$deferred = new Deferred();
$promise = $deferred->promise();

$promise->done(function ($data) {
    echo 'Resultado: ' . $data;
}, function($reason) {
    echo 'Motivo da falha: ' . $reason;
});

$deferred->reject('Erro interno'); // Motivo da falha: Erro interno

Portanto, temos a seguinte relação:

Promises podem ser encadeadas, ou seja, o valor de uma promise resolvida pode ser encaminhado para a próxima da sequência. Isso pode ser atingido usando os seguintes métodos: then() e otherwise(). O método then() é parecido com o done(), com a diferença que ele retorna uma nova promise, enquanto o done() sempre retorna null. E o método otherwise() é uma forma de definir um handler para quando o estado da Promise for failed (e ele retorna uma nova Promise).

Poderíamos, então, trocar o done() por then() no exemplo anterior:

<?php

require './vendor/autoload.php';

use React\Promise\Deferred;

$deferred = new Deferred();
$promise = $deferred->promise();

$promise->then(function ($data) {
    echo 'Resultado: ' . $data;
}, function($reason) {
    echo 'Motivo da falha: ' . $reason;
});

$deferred->reject('Erro interno'); // Motivo da falha: Erro interno

Ou, uma vez que then() retorna uma nova Promise, poderíamos usar o otherwise() de forma encadeada:

<?php

require './vendor/autoload.php';

use React\Promise\Deferred;

$deferred = new Deferred();
$promise = $deferred->promise();

$promise
    ->then(function ($data) {
        echo 'Resultado: ' . $data;
    })
    ->otherwise(function($reason) {
        echo 'Motivo da falha: ' . $reason;
    });

$deferred->reject('Erro interno'); // Motivo da falha: Erro interno

Outro exemplo com o encadeamento de Promises:

<?php

require './vendor/autoload.php';

use React\Promise\Deferred;

$deferred = new Deferred();

$deferred->promise()
    ->then(function ($data) {
        return "Olá, {$data}";
    })->then(function($data) {
        return "{$data}Web";
    })->then(function($data) {
        return strtoupper($data);
    })->then(function($data) {
        echo "{$data}!";
    });

$deferred->resolve('Treina'); // OLÁ, TREINAWEB!

Na programação assíncrona faz muito sentido uma Promise retornar outra Promise, pois não temos o resultado da operação de imediato (quando envolve I/O), o que temos é uma “promessa” de que em algum momento ele poderá vir a estar disponível. Nesse sentido, then() é um mecanismo para aplicar uma transformação em uma Promise e gerar uma nova Promise a partir dessa transformação.

Outro exemplo de encadeamento de then() com otherwise():

<?php

require './vendor/autoload.php';

use React\Promise\Deferred;

$deferred = new Deferred();

$deferred->promise()
    ->then(function ($data) {
        return "Olá, {$data}";
    })->then(function($data) {
        throw new InvalidArgumentException("{$data}Web");
    })->otherwise(function(InvalidArgumentException $exception) {
        return strtoupper($exception->getMessage());
    })->done(function($data) {
        echo "{$data}!";
    });

$deferred->resolve('Treina'); // OLÁ, TREINAWEB!

Nesse exemplo o nosso handler em otherwise() só vai ser invocado se ele receber uma exceção do tipo InvalidArgumentException (e fizemos ele receber).

Outro exemplo:

<?php

require './vendor/autoload.php';

use React\Promise\Deferred;

$deferred = new Deferred();

$deferred->promise()
    ->then(function ($data) {
        return "Olá, {$data}";
    })->then(function($data) {
        throw new RuntimeException("{$data}Web");
    })->otherwise(function(InvalidArgumentException $exception) {
        return strtoupper($exception->getMessage());
    })->otherwise(function(Exception $exception) {
        return strtolower($exception->getMessage());
    })->done(function($data) {
        echo "{$data}!";
    });

$deferred->resolve('Treina'); // olá, treinaweb!

Nesse exemplo, como a promise lança uma exceção do tipo RuntimeException, apenas o handler que está esperando por uma exceção genérica (do tipo Exception) será invocado:

...
})->otherwise(function(Exception $exception) {
  return strtolower($exception->getMessage());
})
...

E, claro, se nenhuma promise passar pelo estado failed os nossos handlers que lidam com as falhas não serão invocados:

<?php

require './vendor/autoload.php';

use React\Promise\Deferred;

$deferred = new Deferred();

$deferred->promise()
    ->then(function ($data) {
        return "Olá, {$data}";
    })->then(function($data) {
        // throw new RuntimeException("{$data}Web");
        return "{$data}Web";
    })->otherwise(function(InvalidArgumentException $exception) {
        return strtoupper($exception->getMessage());
    })->otherwise(function(Exception $exception) {
        return strtolower($exception->getMessage());
    })->done(function($data) {
        echo "{$data}!";
    });

$deferred->resolve('Treina'); // Olá, TreinaWeb!

Agora que já entendemos o funcionamento do objeto Deferred e como as Promises funcionam, podemos verificar como seria resolver (ou rejeitar) uma Promise sem um objeto Deferred:

<?php

require './vendor/autoload.php';

use React\Promise\Promise;

$promise = new Promise(function(Closure $resolve, Closure $reject) {
    if (\random_int(1, 1000000) % 2 === 0) {
        $resolve('Gerou um número par.');
    } else {
        $reject('Gerou um número ímpar.');
    }
});

$promise->then(function($data) {
    echo 'Sucesso: ' . $data;
})->otherwise(function($reason) {
    echo 'Falha: ' . $reason;
});

Nesse exemplo instanciamos um objeto Promise passando para ele uma função anônima que vai cuidar da computação que precisamos realizar. Apenas para fins didáticos, estamos gerando um número inteiro aleatório e então verificamos se ele é par, se verdadeiro, resolvemos a Promise, caso contrário, a rejeitamos. Nisso que passamos a função anônima no construtor da classe Promise, ela é invocada e recebe duas novas funções anônimas como argumento: $resolve e $reject. São essas funções que invocamos no término da nossa operação para decidir o estado da Promise, se vai ser fulfilled ou failed. Por fim, apenas definimos handlers que serão executados em caso de sucesso ou falha e, para isso, usamos os métodos then() e otherwise().

Observe que a diferença dessa abordagem (de instanciar um objeto Promise diretamente) para a que usa um objeto Deferred, é que nessa última invertemos o controle de quem resolve a Promise, ou seja, essa responsabilidade fica com o objeto Deferred.

Agora que já vimos o essencial do funcionamento das promises, podemos fazer um paralelo de como seria um código que usa callbacks versus um que usa promises (usando JavaScript como linguagem de referência):

request('http://www.treinaweb.com.br', function (error, response) {
    if (error) {
        // Aqui a gente lida com o erro.
    } else {
        request('http://www.treinaweb.com.br/' + response.path, function (error, response) {
            if (serror) {
                // Aqui a gente lida com o erro.
            } else {
                // Aqui lidaríamos com o sucesso da requisição.
            }
        });
    }
});

Nesse modelo estamos sempre aninhando callbacks uns dentro dos outros, o que nos torna suscetíveis ao mal do callback hell. O mesmo exemplo acima usando a biblioteca axios que trabalha com Promises ficaria assim:

axios.get('http://www.treinaweb.com.br')
    .then(function (response) {
        return axios.get('http://www.treinaweb.com.br/' + response.path);
    })
    .then(function (response) {
        // Lida com a resposta da requisição anterior
    })
    .catch(function (error) {
        // Lida com alguma exceção, se existir.
    });

No artigo de introdução ao ReactPHP mostramos um exemplo que usava a library reactphp-buzz. Veremos outro exemplo com ela, pois ela faz uso intensivo de promises. Primeiro instale-a como dependência do projeto:

$ composer require clue/buzz-react:^2.6

O nosso exemplo vai de forma assíncrona imprimir o bairro de alguns CEPs usando a API pública do Postmon:

<?php

require './vendor/autoload.php';

use Clue\React\Buzz\Browser;
use React\EventLoop\Factory;
use Psr\Http\Message\ResponseInterface;

$browser = new Browser(
    $loop = Factory::create()
);

$ceps = [
    '01311200', // Bela Vista
    '70630904', // Setor Militar Urbano
    '70165900', // Zona Cívico-Administrativa
    '32685888', // Erro, cep não existe.
];

foreach ($ceps as $cep) {
    $browser->get("https://api.postmon.com.br/v1/cep/{$cep}")
        ->then(function (ResponseInterface $response) {
            $endereco = \json_decode($response->getBody());

            echo $endereco->bairro . PHP_EOL;
        })
        ->otherwise(function (\Exception $exception) use ($cep) {
            echo 'Erro no CEP: ' . $cep . PHP_EOL;
        });
}

$loop->run();

Esse exemplo demonstra que o método get() que realiza uma requisição HTTP retorna uma Promise. Se você executar esse exemplo várias vezes, verá que não terá uma ordem definida para a impressão dos resultados, ademais, um resultado só é impresso quando ele fica pronto, não seguindo uma ordem linear (que é o que estamos acostumados na programação síncrona).

Para finalizar, é importante pontuar que Promises por si só não fazem nossos códigos serem assíncronos, elas são apenas mecanismos para encapsular os resultados.

Até a próxima!

O que é Laravel?

O Laravel é um dos Frameworks PHP mais utilizado no mercado.

Caso você não saiba:

Um framework é um facilitador no desenvolvimento de diversas aplicações e, sem dúvida, sua utilização poupa tempo e custos para quem o utiliza, pois de forma mais básica, é um conjunto de bibliotecas utilizadas para criar uma base onde as aplicações são construídas, um otimizador de recursos. Tem como principal objetivo resolver problemas recorrentes com uma abordagem mais genérica. Ele permite ao desenvolvedor focar nos “problemas” da aplicação, não na arquitetura e configurações.

Aqui no blog também temos um artigo bem legal sobre: Para que serve um Framework?

História do Laravel

Desenvolvido por Taylor B. Otwell, tendo sua primeira versão beta lançada em meados de Junho de 2011, o Laravel é um Framework Open Source sob a licença MIT, criado com o propósito de ser uma alternativa mais avançada do CodeIgniter. Atualmente, se encontra na versão 5.8, tendo seu código-fonte hospedado no GitHub.

Recursos do Laravel

Dentre os diversos recursos do Laravel, podemos citar como principais:

  • Sistema de template (Blade)

O Laravel possui um sistema de template que facilita a criação da camada de visualização de dados (Páginas HTML). Com ele, podemos facilmente criar páginas simples e intuitivas de forma rápida e eficaz. Dentre alguns dos recursos do Blade, se destacam: Herança de layouts, sistema de tags, seções e uso de código PHP nos templates.

  • Módulo de autenticação

O Laravel possui, por padrão, um módulo de autenticação/autorização completo que provê todos os recursos para sua implementação, como: Autenticação de usuários, autorização de usuários, recuperação de senhas, logout, controle de sessão e cadastro de usuários.

  • Eloquent ORM

Com o Laravel não precisamos criar códigos SQL para manipular ou criar tabelas no Banco de Dados. Todo processo é feito utilizando código PHP que, posteriormente, será convertido em instruções SQL. Implementa o padrão Active Record, onde cada model da aplicação representa uma tabela no banco de dados.

Padrão MVC

O Laravel utiliza o padrão MVC (Model, View e Controller) que, basicamente, funciona da seguinte forma:

  • Model é a camada responsável pela parte lógica da aplicação, ou seja, todos os recursos da sua aplicação (consultas ao BD, validações, notificações, etc), mas ele não sabe quando isso deve ser feito, a camada de model apenas tem o necessário para que tudo aconteça, mas não sabe quando irá executar.

  • View é a camada responsável por exibir dados para o usuário, seja em páginas HTML, JSON, XML, etc. A camada View não possui responsabilidade de saber quando vai exibir os dados, apenas como irá exibi-los.

  • Controller é o famoso “meio-de-campo” da aplicação. Essa é a camada que sabe quem chamar e quando chamar para executar determinada ação.

Basicamente, o MVC funciona da seguinte forma:

Ao receber uma requisição, o Controller solicita ao Model as informações necessárias (que virão do banco de dados), que as obtém e retorna ao Controller. De posse dessas informações, o Controller as envia para a View que irá renderizá-las.

Vantagens em utilizar o Laravel

  • Simples e fácil;
  • Documentação completa;
  • Amplamente utilizado;
  • Comunidade ativa;
  • Gratuito.

Concluindo:

O Laravel é um excelente Framework para desenvolvimento de aplicações web. Neste artigo vimos algumas de suas principais características e funcionalidades, o que o torna uma excelente escolha para a criação dos seus projetos.

Autowiring em Container de Injeção de Dependência

No artigo Entendendo Injeção de Dependência vimos sobre o que é injeção de dependência, seu funcionamento e como se dá a sua aplicação. No artigo Container De Injeção De Dependência (DI Container) vimos como funciona um container para gerenciar o mapa de dependências.

Nesse artigo veremos uma funcionalidade relativamente comum nos containers de dependência de frameworks que é a habilidade de resolver as dependências de um construtor (principalmente) automaticamente, chamada de autowiring.

No artigo Container De Injeção De Dependência (DI Container) implementamos um protótipo (simples, porém funcional) de um container e, em determinado momento, chegamos nesse tipo de caso de uso:

$indexController = new IndexController(
    $container->get('user.repository')
);

$userController = new UserController(
    $container->get('user.repository')
);

$registerController = new RegisterController(
    $container->get('user.repository')
);

Alguns controladores precisavam receber a instância de UserRepository e tivemos que manualmente passá-la para cada um deles. Agora, imagine se cada um desses controladores tivesse que receber outras dependências? Nenhum problema, só que ficaria meio chato e improdutivo ter que ficar repetindo essas construções na instanciação deles, concorda?

E se desenvolvermos uma forma do nosso container automaticamente resolver essas dependências? Desse modo, tudo o que faríamos seria pedir para o Container uma instância desses controladores, sem a necessidade de nos preocuparmos em passar manualmente para o construtor de cada um deles as suas dependências. Teríamos como resultado um código assim:

$indexController = $container->get(IndexController::class);

$userController = $container->get(UserController::class);

$registerController = $container->get(RegisterController::class);

Estamos pedindo as instâncias desses controladores sem nos preocuparmos em alimentá-los de suas dependências, deixamos esse trabalho para o Container.

Estrutura inicial do projeto

Vamos criar o protótipo de uma aplicação para que possamos testar a nossa implementação de Container. A nossa aplicação terá essa estrutura:

- [blog-artigo-di]
- - - [app]
- - - - [Http]
- - - - - - [Controller]
- - - - - - - UserController.php
- - - - [Repositories]
- - - - - LeadRepository.php
- - - - - TagRepository.php
- - - - - UserRepository.php
- - - Container.php
- index.php

O repositório dessa estrutura no GitHub você encontra clicando aqui. Se desejar, você pode fazer download direto dela clicando nesse link: sem-autowiring.zip

Depois de baixar e colocar no local onde normalmente você executa seus projetos, basta que você execute composer install pois o projeto faz uso da PSR-4 para autoloading:

$ ~/D/w/blog-artigo-di> composer install

O nosso Container atualmente possui uma implementação simples e que ainda não suporta a resolução automática de dependências. Abra app/Container.php:

<?php
declare(strict_types=1);

namespace App;

use Closure;

final class Container
{
    private $instances = [];

    public function set(string $id, Closure $closure) : void
    {
        $this->instances[$id] = $closure;
    }

    public function get(string $id) : object
    {
        return $this->instances[$id]($this);
    }

    public function singleton(string $id, Closure $closure) : void
    {
        $this->instances[$id] = function () use ($closure) {
            static $resolvedInstance;

            if (null !== $resolvedInstance) {
                $resolvedInstance = $closure($this);
            }

            return $resolvedInstance;
        };
    }
}

Esse container já foi explicado na indicação de leitura no início desse artigo. Portanto, não vamos entrar novamente em seus pormenores.

O arquivo index.php na raiz do projeto é o responsável pelo bootstrap da nossa aplicação protótipo:

<?php

// Carrega o autoload do Composer
require './vendor/autoload.php';

use App\Repositories\TagRepository;
use App\Repositories\LeadRepository;
use App\Repositories\UserRepository;
use App\Http\Controller\UserController;

// Instancia o container
$container = new App\Container();

// Adiciona referências ao container
$container->set(TagRepository::class, function() {
    return new TagRepository();
});

$container->set(LeadRepository::class, function() {
    return new LeadRepository();
});

$container->set(UserRepository::class, function() {
    return new UserRepository();
});

// Instancia o UserController passando para ele as dependências necessárias
$userController = new UserController(
    $container->get(TagRepository::class),
    $container->get(UserRepository::class),
    $container->get(LeadRepository::class)
);

$userController->index();

Para testar o exemplo, na raiz do projeto execute:

$ ~/D/w/blog-artigo-di> php -S localhost:8000

Isso executará o servidor embutido do PHP em cima da raiz do nosso projeto, permitindo que o acessemos pelo navegador através da URL: http://localhost:8000/

O resultado será:

/Users/kennedytedesco/Documents/www/blog-artigo-di/app/Http/Controller/UserController.php:31:
array (size=2)
  'id' => int 1
  'name' => string 'tag' (length=3)

/Users/kennedytedesco/Documents/www/blog-artigo-di/app/Http/Controller/UserController.php:31:
array (size=2)
  'id' => int 2
  'name' => string 'user' (length=4)

/Users/kennedytedesco/Documents/www/blog-artigo-di/app/Http/Controller/UserController.php:31:
array (size=2)
  'id' => int 3
  'name' => string 'lead' (length=4)

Indica que o método index() do UserController foi executado com sucesso. Ou seja, as dependências, os repositórios necessários para o funcionamento dessa classe foram injetados com sucesso. Até aqui tudo bem, ademais, injetamos tais dependências manualmente:

$userController = new UserController(
    $container->get(TagRepository::class),
    $container->get(UserRepository::class),
    $container->get(LeadRepository::class)
);

Implementando a resolução automática de dependências

Para que possamos implementar a resolução automática das dependências utilizaremos a API de reflexão do PHP. Essa API nos fornece meios para que façamos engenharia reversa nas classes, extraindo muitas de suas informações internas como quantos métodos possui, quais são públicos, protegidos ou privados, se implementa um construtor, quais são os parâmetros do construtor, se são opcionais ou exigidos, entre outras informações.

Dessa forma, se o usuário pedir para o Container a instanciação do UserController:

$userController = $container->get(UserController::class);

Que tal a gente usar reflexão para obter quais dependências (classes) ele necessita para ser instanciado e então resolver essas dependências automaticamente usando o próprio Container e retornar a instância dele?

É exatamente isso que a nossa implementação de autowiring fará. Para tanto, começaremos alterando o código do nosso container app\Container.php para:

<?php
declare(strict_types=1);

namespace App;

use Closure;
use ReflectionClass;
use ReflectionParameter;

final class Container
{
    private $instances = [];

    public function set(string $id, Closure $closure) : void
    {
        $this->instances[$id] = $closure;
    }

    public function get(string $id) : object
    {
        // Se essa referência existe no mapa do container, então a retorna diretamente.
        if ($this->has($id)) {
            return $this->instances[$id]($this);
        }

        // Se a referência não existe no container, então foi passado uma classe para ser instanciada
        // Façamos então a reflexão dela para obter os parâmetros do método construtor
        $reflector = new ReflectionClass($id);
        $constructor = $reflector->getConstructor();

        // Se a classe não implementa um método construtor, então vamos apenas retornar uma instância dela.
        if (null === $constructor) {
            return new $id();
        }

        // Itera sobre os parâmetros do construtor para realizar a resolução das dependências que ele exige.
        // O método "newInstanceArgs()" cria uma nova instância da classe usando os novos argumentos passados.
        // Usamos "array_map()" para iterar os parâmetros atuais, resolvê-los junto ao container e retornar um array das instâncias já resolvidas pelo container.
        return $reflector->newInstanceArgs(array_map(
            function (ReflectionParameter $dependency) {
                // Busca no container a referência da classe desse parâmetro
                return $this->get(
                    $dependency->getClass()->getName()
                );
            },
            $constructor->getParameters()
        ));
    }

    public function singleton(string $id, Closure $closure) : void
    {
        $this->instances[$id] = function () use ($closure) {
            static $resolvedInstance;

            if (null !== $resolvedInstance) {
                $resolvedInstance = $closure($this);
            }

            return $resolvedInstance;
        };
    }

    public function has(string $id) : bool
    {
        return isset($this->instances[$id]);
    }
}

(Através do GitHub você pode visualizar o que foi adicionado/removido. Basta visualizar esse commit aqui).

A partir do momento que o nosso Container consegue resolver as dependências automaticamente, podemos alterar no index.php a forma de instanciar, que era uma instanciação direta da classe usando new UserController:

$userController = new UserController(
    $container->get(TagRepository::class),
    $container->get(UserRepository::class),
    $container->get(LeadRepository::class)
);

Para uma instanciação que usa o Container (para que ele possa resolver as dependências para nós):

$userController = $container->get(UserController::class);
$userController->index();

(O código completo do exemplo com autowiring encontra-se no branch master do repositório desse exemplo).

Você pode testar o exemplo e terá o mesmo resultado obtido anteriormente quando não usávamos autowiring. O UserController continuará sendo instanciado com sucesso.

Concluindo

Vimos nesse artigo a fundação sobre como a resolução automática de dependências é feita nos containers de injeção de dependência. É por esse caminho que Symfony, Laravel entre outros frameworks (inclusive de outras linguagens) fazem. Obviamente o nosso Container é simples, didático e bem direto ao ponto, não estando 100% pronto para uso em projetos reais. Algumas verificações de segurança (se a classe existe, senão lançar uma exceção etc) precisariam ser implementadas. Na realidade, existem boas implementações de containers por aí e, se você usa um Web Framework, não vai precisar criar a sua própria. No entanto, saber como funciona, é essencial. Essa foi a intenção desse artigo.

Até a próxima!

Os principais Frameworks PHP

De maneira mais simples e objetiva, um Framework é um conjunto de funcionalidades que auxiliam o desenvolvedor a criar aplicações de forma mais rápida.

Caso você ainda não saiba para que serve um framework, aqui no blog possuímos um artigo bem legal que, sem dúvidas, irá te auxiliar no entendimento.

Desta forma, neste artigo falaremos brevemente sobre os principais Frameworks PHP.

Laravel

Framework open source, o Laravel é, sem dúvidas, um dos mais utilizados atualmente. Desenvolvido por Taylor B. Otwell sobre a licença MIT, o Laravel possui seu código hospedado no GitHub e é um framework para os apreciadores de um código bonito, como diz seu slogan: “The PHP Framework For Web Artisans”.

Por possuir uma sintaxe simples e clara, permite que o desenvolvedor trabalhe de maneira mais rápida e extremamente estruturada. Possui uma imensa comunidade ativa, o que o torna ainda mais aceito no mercado.

Utiliza o padrão arquitetural MVC, que divide a aplicação em três camadas distintas, sendo elas o Model, View e Controller, onde:

  • Model: determina as entidades do projeto, ou seja, quais tabelas serão mapeadas;

  • View: camada responsável por exibir informações ao usuário, normalmente páginas HTML;

  • Controller: camada que faz o “meio de campo” entre a view e o model, ou seja, obtém a requisição que o usuário realiza, busca os dados através do model e retorna à view.

Em seu site (https://laravel.com/) é possível encontrar toda sua documentação, novidades do framework (Podcats, blog, tutoriais..), parceiros e muito mais.

CodeIgniter

Com sua primeira versão pública lançada em 2006, o CodeIgniter é um excelente framework para desenvolvimento de aplicações PHP que exijam mais rapidez em seu desenvolvimento.

Assim como dito em seu site, fornece um conjunto de ferramentas simples e elegantes para criar aplicativos web com recursos completos. De código open source e extremamente leve e rápido, é um framework ideal para desenvolvedores iniciantes, por possuir a simplicidade atrelada ao desenvolvimento.

Em seu site (https://codeigniter.com/) é possível encontrar toda documentação necessária para sua utilização, além de links para canais de comunicação entre a comunidade.

Symfony

Framework Open source, o Symfony é ideal para construção de aplicações mais robustas por oferecer ao desenvolvedor total controle de suas configurações. Lançado em 2005, é um framework criado sobre o conceito de aprendizagem rápida sobre os componentes do Symfony.

Utiliza também paradigma MVC e uma comunidade ativa em mais de 120 países (Como informado em seu site).

Em seu site (https://symfony.com/), você poderá encontrar mais informações sobre sua documentação, comunidade, realizar seu download e ficar por dentro de todas as novidades acerca deste framework.

Zend

Lançado em 2005, o Zend é um Framework orientado a objetos de código aberto que permite que o desenvolvedor faça a reutilização de seus componentes. De fácil escrita, implementar códigos mais complexos com o Zend torna-se algo mais acessível.

Possui uma comunidade extremamente ativa e certificação oficial aceita no mercado que, por sinal, é um grande diferencial por ser um framework utilizado por grandes empresas.

Em seu site (http://www.zend.com/) é possível realizar o download do framework, além de encontrar toda sua documentação e tudo sobre sua certificação.

CakePHP

O CakePHP é um framework que possibilita a criação de aplicações robustas por programadores de todos os níveis, sem perder sua flexibilidade. É um framework sobre a licença MIT perfeito para aplicações de uso comercial.

Assim como informado em seu site, oferece uma camada flexível de acesso ao banco de dados e torna a construção de sistemas pequenos e complexos muito mais simples.

Em seu site (https://cakephp.org/), além de se “deliciar” com uma página extremamente fofíssima, para os amantes de bolinhos <3, é possível realizar o download da ferramenta, encontrar sua documentação, sua comunidade e uma loja online de produtos do Framework, com camisetas e lindíssimos elefantinhos (os CakePHP ElePHPant).

Concluindo:

O uso de Frameworks, sem dúvidas, caiu no gosto popular de diversos desenvolvedores, pois permite a criação de aplicações mais profissionais em um tempo muito menor de desenvolvimento, quando comparado ao uso da linguagem “pura”, ou seja, sem framework algum.

E você? Utiliza algum Framework? Conte pra gente sua experiência e até o próximo artigo! =)

O que é PHP?

Desenvolvida para criação de aplicações web, o PHP é uma linguagem de programação vastamente utilizada nos dias atuais, tanto para pequenas quanto para grandes aplicações.

Desenvolvida em meados dos anos 90, o PHP se tornou bastante utilizado por grandes empresas para criação de seus projetos, tais como: Facebook, Wikipedia, Yahoo, WordPress, entre outras.

É uma linguagem server-side, ou em tradução livre, linguagem de servidor, que significa que é processada dentro do servidor a cada requisição feita pelo usuário. Desta forma, o usuário faz uma requisição através de um site qualquer e este pedido é enviado para o servidor, após isso a aplicação desenvolvida em PHP recebe esta requisição, processa e o transforma em um XHTML que será devolvido ao navegador que exibirá a resposta ao usuário.

No mercado de trabalho, é muito comum nos depararmos com vagas para desenvolvedor PHP, visto que é uma linguagem utilizada para vários fins, como aplicações para web, plugins do WordPress, APIs Restfull e até aplicações desktop.

Além disso, o PHP pode se comunicar com os principais banco de dados relacionais e não-relacionais do mercado, o que abrange ainda mais sua utilização.

Características do PHP

Entre diversas características do PHP podemos citar algumas das principais:

  • Linguagem interpretada: Diferente de algumas linguagens, como C e C++ que precisam ser compiladas, o código criado com PHP é interpretado e convertido em um executável;

  • Tipagem dinâmica: As variáveis criadas em PHP não possuem um tipo de dado fixo, permitindo armazenar diferentes tipos de informações;

  • Server-side: Como dito acima, o código criado em PHP é executado diretamente no servidor;

  • Orientado a objetos: O PHP suporta o paradigma orientado a objetos para criação de aplicações, tornando o código muito mais robusto.

Vantagens de estudar PHP

Diversas são as vantagens em começar a estudar PHP. Abaixo, citaremos algumas delas.

  • Facilidade no aprendizado: O PHP possui uma curva de aprendizado muito grande devido a sua sintaxe simples e seu código legível.

  • Comunidade do PHP: Por ser muito utilizado, o PHP possui diversos grupos de discussões sobre a linguagem, onde a comunidade está sempre a disposição para ajudar novos desenvolvedores.

  • Open source: O PHP é uma linguagem de código-fonte aberto, o que permite que a comunidade esteja sempre melhorando a linguagem.

  • Grandes empresas utilizam: Como dito acima, várias empresas utilizam o PHP, desta forma, vale a pena ficar “de olho” em oportunidades de emprego que possam surgir.

  • Multiplataforma: Utilizando PHP, o desenvolvedor pode criar suas aplicações em qualquer sistema operacional do mercado (Windows, Linux e Mac).

  • Valor baixo para manter sua aplicação: Por ser muito utilizado, os preços dos servidores de armazenamento de uma aplicação PHP são os mais baixos, permitindo que sua aplicação custe o menor valor possível.

Concluindo:

Desta forma, vimos neste artigo as principais características e vantagens do PHP. Muito atuante no mercado, o PHP é uma excelente alternativa para se tornar a sua principal linguagem de programação.

Como aliviar seus controllers com os eventos do Eloquent no Laravel

O sonho de todo programador é desenvolver código limpo, organizado, legível, manutenível e muitos outros adjetivos que poderia colocar em uma lista enorme. O Laravel possui vários recursos que nos ajuda a organizar nosso código para conseguir alcançar alguns desses atributos que descrevi acima, se usados de maneira correta. Um desses recursos são os eventos do Eloquent.

Esses eventos são executados sempre que uma ação acontece no model. Isso possibilita removermos responsabilidade dos controllers e executar quando o evento for acionado, com isso, conseguimos um controller mais simples e organizado.

O Eloquent disponibiliza os eventos:

  • Retrieved
  • Creating e Created
  • Updating e Updated
  • Saving e Saved
  • Deleting e Deleted
  • Restoring e Restored

Os eventos com final ing são executados antes da ação no banco de dados e os eventos com final ed são executados após a ação no banco de dados.

Relacionando os eventos no Model

É possível relacionar um evento do Eloquent a um método que fará a ação de três modos diferentes.

Via o método boot

O primeiro modo que podemos adicionar uma ação em um evento do model é através do método estático boot. Por exemplo, para executar uma ação antes da criação podemos fazer:

protected static function boot()
{
    parent::boot();

    static::creating(function ($nomeDoModel) {
        //Ação a ser executada 
    });

}

O Eloquent sempre injeta uma instância do model automaticamente no evento. Nos eventos executados antes da ação no banco é possível alterar os valores do model que serão persistidos.

A utilização dos eventos dentro do próprio model pode não ser a mais aconselhável, pois dependendo da quantidade de eventos pode sobrecarregar, tirando a complexidade dos controllers e transferindo para o models, o que não é bom, pois a solução de um problema criaria outro.

Via classe de evento

Um modo mais limpo de executar os eventos do Eloquent é através de classes específicas para cada um dos eventos. Para isso, basta declarar no model uma propriedade chamada dispatchesEvents com um array contendo o nome do evento e a respectiva classe que será responsável por executar a ação:

protected $dispatchesEvents = [
        'creating' => NomeDoModelCreating::class,
        'deleting' => NomeDoModelDeleting::class,
    ];

Via Observer

Na minha opinião, esse é o modo mais prático de executar ações quando precisamos executar ações em vários eventos do mesmo model.

Essa abordagem consiste basicamente em criar uma classe e declarar métodos públicos com o mesmo nome dos eventos, com isso, o próprio Eloquent verifica se o método existe, se sim, ele executa.

<?php

namespace App\Observers;

use App\NomeDoModel;

class NomeDoModelObserver
{

    public function creating(NomeDoModel $nomeDoModel)
    {
        //Executa antes de criar no banco
    }

    public function deleting(NomeDoModel $nomeDoModel)
    {
        //Executa antes de deletar no banco
    }
}

Único detalhe é que para o Eloquent fazer a referência entre a classe do model e a classe do Observer precisamos declarar dentro do método boot do AppServiceProvider essa relação:

NomeDoModel::observe(NomeDoModelObserver::class);

Note que usamos o método observe() do próprio model que recebe o caminho da classe do observer.

Exemplo prático

Vamos supor que temos uma relação 1 para 1 entre festa e cliente. Baseado nisso, a cada evento do model festa precisamos executar uma ação:

  • Quanto uma festa for criada para um cliente precisamos alterar o status do cliente para com-proposta;
  • Quando uma festa for atualizada, se o atributo valor for maior que zero precisamos alterar o status do cliente para festa-agendada;
  • Por fim, se a festa for excluída, alteramos o status do cliente para sem-proposta;

Nesse caso, podemos criar um observer com os eventos created, updated e deleted para realizar essas ações:

<?php

namespace App\Observers;

use App\Festa;

class FestaObserver
{

    /**
     * Evento executado após a festa ser criada
     *
     * @param Festa $festa
     */
    public function created(Festa $festa)
    {
        $festa->cliente->update(['status' => 'com-proposta']);
    }


    /**
     * Evento executado após a festa ser atualizada
     *
     * @param Festa $festa
     */
    public function updated(Festa $festa)
    {
        if ($festa->valor > 0) {
            $festa->cliente->update(['status' => 'festa-agendada']);
        }
    }

    /**
     * Evento executado após a festa ser deletada
     *
     * @param Festa $festa
     */
    public function deleted(Festa $festa)
    {
        $festa->cliente->update(['status' => 'sem-proposta']);
    }

}

Conclusão

Esse é apenas um dos recursos que podemos usar no Laravel para manter a organização do código. Podemos usar outros como View Composer, Route Model Bind, Policies para permissões, Presenters para remover lógica das views e muito mais.

Quais dos recursos você tem utilizado em seus projetos para melhorar a qualidade do seu código? Fique à vontade para nos contar aqui nos comentários.

Ah, temos um curso muito bom de Laravel no TreinaWeb. Dê uma espiadinha? 🙂

Até a próxima! 🙂

Generators no PHP

Generators foram adicionados no PHP na versão 5.5 (meados de 2013) e aqui estamos nós, quase cinco anos depois, falando sobre eles. É que esse ainda não é um assunto muito difundido, digo, não é trivial encontrar casos e mais casos de uso para eles no contexto padrão de desenvolvimento para o PHP.

Primeiro de tudo, é importante entendermos o que são Iterators, ademais, Iterators e Generators são assuntos intrinsicamente relacionados.

O que é um Iterator?

No contexto prático do PHP, Iterator é um mecanismo que permite que um objeto seja iterado e ele próprio fica no controle dessa iteração. Mas o seu uso não limita a essa “feature”, é o que veremos mais pra frente.

Há de se destacar que é possível iterar um objeto “limpo” (digo, aquele que não implementa nenhuma interface específica) e o que será levado em consideração são os seus atributos públicos.

Um exemplo:

<?php

class Maiable
{
    public $from =  'email@domain.com';
    public $to = 'email@domain.com';
    public $subject = "Assunto";
    protected $type = 1;
}

$maiable = new Maiable();

foreach($maiable as $atributo => $valor) {
    echo "<strong>{$atributo}:</strong> {$valor} <br>";
}

O resultado será:

from: email@domain.com 
to: email@domain.com 
subject: Assunto 

No entanto, temos no PHP a Iterator, uma interface que quando implementada, os objetos provenientes ganham a capacidade de serem iterados com base nas seguintes assinaturas de métodos:

Iterator extends Traversable
{
    abstract public mixed current ( void )
    abstract public scalar key ( void )
    abstract public void next ( void )
    abstract public void rewind ( void )
    abstract public boolean valid ( void )
}
  • current() – Retorna elemento corrente;
  • key() – Obtêm a chave corrente;
  • next() – Avança o cursor para o próximo elemento;
  • rewind() – Retornar o cursor para o início;
  • valid() – Checa se a posição atual existe;

Vamos então a um exemplo:

class BookStore implements Iterator
{
    private $books = [];
    private $index;

    public function __construct()
    {
        $this->index = 0;

        $this->books = [
            'Book 1',
            'Book 2',
            'Book 3',
            'Book 4',
            'Book 5',
        ];
    }

    public function current()
    {
        return $this->books[$this->index];
    }

    public function key()
    {
        return $this->index;
    }

    public function next()
    {
        $this->index++;
    }

    public function rewind()
    {
        $this->index = 0;
    }

    public function valid()
    {
        return array_key_exists($this->index, $this->books);
    }
}

Nota: Esse é um exemplo puramente didático. Estamos inicializando o array de forma estática e ele por si só não justifica o uso de Iterator. A ideia aqui é passar a essência do mecanismo. Adiante veremos casos de usos mais proximos da realidade, usando Generators.

Observe que os atributos são protegidos e que mantemos no $index o “cursor” atual da iteração.

Uma possível forma de iterar sobre esse objeto:

$books = new BookStore();

while ($books->valid()) {
    echo "<strong>[{$books->key()}]</strong> = {$books->current()} <br>";

    $books->next();
}

Resultado:

[0] = Book 1 
[1] = Book 2 
[2] = Book 3 
[3] = Book 4 
[4] = Book 5 

Essa interface faz com que o nosso objeto tenha a capacidade de decidir como e o que iterar. E destaco esses termos em negrito pois eles são a essência dos interators.

Enquanto existir um índice válido a ser recuperado os valores serão impressos. Na última iteração, $books->next() incrementa em mais um o valor de $index chegando ao valor 5, no entanto, no array $books o maior índice existente é o 4, logo, o while é interrompido.

Outra forma possível seria:

for ($books->rewind(); $books->valid(); $books->next()) {
    echo "<strong>[{$books->key()}]</strong> = {$books->current()} <br>";
}

Essas são as formas mais “primitivas” de iterar o nosso objeto iterável. Estamos manualmente cuidando de avançar o cursor usando $books->next();.

Podemos facilitar isso se usarmos a estrutura foreach, ela reconhece quando o objeto implementa a interface Traversable e trata de avançar o cursor e recuperar os valores automaticamente.

A interface Iterator estende a Traversable.

A nossa implementação pode ser simplificada para:

$books = new BookStore();

foreach($books as $key => $value) {
    echo "<strong>[{$key}]</strong> = {$value} <br>";
}

Refatorando para ArrayIterator

O lado chato da abordagem de implementar diretamente a interface Iterator é a necessidade de implementar todos os seus métodos. É util quando a lógica para se iterar ou avançar o cursor precisa ser específica. Agora, se ela é trivial, como é o caso do nosso exemplo, podemos usar a classe ArrayIterator, também nativa do PHP.

A ArrayIterator implementa não só a interface Iterator como várias outras (que não fazem parte do escopo desse artigo):

ArrayIterator implements ArrayAccess, SeekableIterator, Countable, Serializable {

Ela recebe no seu construtor um array e itera sobre ele, abstraindo de nós a necessidade de implementar os métodos necessários da interface Iterator.

Podemos simplificar e alterar a nossa classe para implementar a interface IteratorAggregate que assina um único método, chamado getIterator(). E, nele, tudo o que temos que retornar é um Iterator, no caso, vamos retornar a instância da ArrayIterator:

class BookStore implements IteratorAggregate
{
    private $books;

    public function __construct()
    {
        $this->books = [
            'Book 1',
            'Book 2',
            'Book 3',
            'Book 4',
            'Book 5',
        ];
    }

    public function getIterator()
    {
        return new ArrayIterator($this->books);
    }
}

Qual o principal benefício de se usar um Iterator?

Os exemplos anteriores não tiveram o objetivo de explicitar isso, mas um grande benefício do uso de Iterators trata-se de um melhor aproveitamento da memória. Com eles, não precisamos carregar grandes datasets de uma só uma vez, podemos carregar item a item, sob demanda, quando preciso. Digamos, é uma implementação “lazy loading”.

Nos tópicos seguintes, sobre Generators, veremos alguns casos de uso mais reais e então entenderemos como os Iterators são importantes para lidar com grandes coleções de dados.

O que é um Generator?

Em termos práticos Generators são uma forma prática de se implementar Iterators. Isso quer dizer que, com Generators, podemos implementar complexos Iterators sem que precisemos criar objetos, implementar interfaces e toda àquela complexidade que vimos anteriormente. Generators dão para uma função a capacidade de retornar uma sequência de valores.

Para criar uma função Generator, basta que ela possua a palavra reservada yield. O operador yield é uma espécie de return, só que com algumas particularidades. E uma função desse tipo retorna um objeto da classe Generator, que é uma classe especial e específica para esse contexto, não sendo possível utilizá-la de outra forma. Esse objeto retornado pode ser iterado. É aí que entra a nossa base de Iterators que aprendemos anteriormente. A classe Generator implementa a interface Iterator.

Bom, vamos praticar um pouco? O exemplo mais elementar possível de uma função generator:

function getLinhas() {
    yield "Linha 1";
    yield "Linha 2";
    yield "Linha 3";
    yield "Linha 4";
    yield "Linha 5";
}

var_dump(getLinhas());

O resultado:

object(Generator)#1 (0) { }

Já confirmamos o que anteriormente foi explicado. Sempre que uma função usa o operador yield ela vai retornar um objeto do tipo Generator.

E, se Generator é um Iterator, logo, podemos iterar sobre ele, certo? Sim!

Vejamos:

<?php

function getLinhas() {
    yield "Linha 1";
    yield "Linha 2";
    yield "Linha 3";
    yield "Linha 4";
    yield "Linha 5";
}

foreach (getLinhas() as $linha) {
    echo "{$linha} <br>";
}

Resultado:

Linha 1 
Linha 2 
Linha 3 
Linha 4 
Linha 5 

Não precisamos de um yield para cada registro da nossa coleção. Normalmente o que vamos ver é um yield dentro de um laço como, por exemplo:

<?php

function getLinhas() {
    for ($i = 0; $i < 100; $i++) {
        yield "Linha {$i}";
    }
}

foreach (getLinhas() as $linha) {
    echo "{$linha} <br>";
}

E é possível que o yield retorne um conjunto de chave => valor:

function getLinhas() {
    for ($i = 0; $i < 100; $i++) {
        yield $i => 'Linha ' . $i * 2;
    }
}

foreach (getLinhas() as $chave => $valor) {
    echo "[{$chave}] => {$valor} <br>";
}

E a parte que “toca” a memória?

Comentamos anteriormente que o principal benefício de se usar um Iterator está no baixo consumo de memória associado. Com um Iterator recuperamos a informação sob demanda, sem alocar toda a coleção na memória.

É relativamente comum aplicações que recuperam milhares de dados de uma base de dados (quando não podem por algum requisito usar paginação) precisarem carregar esses dados em um array e em seguida formatar esses dados carregando-os em um novo array. O pico de consumo de memória nesses casos pode ser altíssimo.

Vamos emular uma situação onde possamos ver o benefício de usar Generators?

<?php

function getRegistros() {
    $registros = [];

    for ($i = 0; $i < 10000; $i++) {
        $registros[] = "Registro $i";
    }

    return $registros;
}

function formataRegistros($registros) {
    $new = [];

    foreach ($registros as $index => $registro) {
        $new[] = "[$index] -> {$registro}";
    }

    return $new;
}

$registros = formataRegistros(getRegistros());

echo 'Memória: ' . bcdiv(memory_get_peak_usage(), 1048576, 2) . ' MB<hr><br>';

foreach ($registros as $registro) {
    echo "{$registro} <br>";
}

A getRegistros() aloca na memória (em um array) milhares de registros e os retorna. A formataRegistros() recebe uma coleção de dados, formata-os, aloca-os na memória (em um array) e então os retorna.

O resultado da memória total gasta nessa operação foi:

Memória: 2.19 MB

Faça um paralelo disso com uma aplicação sua, real, que precisa recuperar milhares de registros, depois formatá-los de alguma maneira ou relacioná-los com outros registros etc.

Agora, refatorando o exemplo para que as duas funções se transformem em generators:

<?php

function getRegistros() {
    for ($i = 0; $i < 10000; $i++) {
        yield "Registro $i";
    }
}

function formataRegistros($registros) {
    foreach ($registros as $registro) {
        yield "-> {$registro}";
    }
}

$registros = formataRegistros(getRegistros());

echo 'Memória: ' . bcdiv(memory_get_peak_usage(), 1048576, 2) . ' MB<hr><br>';

foreach ($registros as $registro) {
    echo "{$registro} <br>";
}

O resultado:

Memória: 0.38 MB

Uma diferença muito grande. Quanto maior o dataset, maior a diferença será.

Outro possível caso de uso: Você precisa ler um documento de texto linha a linha e tratar essas informações de alguma maneira. Usando a forma tradicional de lidar com isso, teríamos algo como:

<?php

function getLinhas($arquivo) {
    $handle = fopen($arquivo, 'r');

    $linhas = [];

    while (($buffer = fgets($handle, 4096)) !== false) {
        $linhas[] = $buffer;
    }

    fclose($handle);

    return $linhas;
}

$linhas = getLinhas('file.txt');

foreach($linhas as $linha) {
    // TODO
}

echo 'Memória: ' . bcdiv(memory_get_peak_usage(), 1048576, 2) . ' MB';

Se o seu sistema é unix você pode gerar um arquivo com dados randômicos de 10MB (para testar o exemplo) executando:

base64 /dev/urandom | head -c 10000000 > file.txt

O resultado na minha máquina foi:

Memória: 19.56 MB

Se refatorarmos isso para um Generator:

<?php

function getLinhas($arquivo) {
    $handle = fopen($arquivo, 'r');

    while (($buffer = fgets($handle, 4096)) !== false) {
        yield $buffer;
    }

    fclose($handle);
}

$linhas = getLinhas('file.txt');

foreach($linhas as $linha) {
    // TODO
}

echo 'Memória: ' . bcdiv(memory_get_peak_usage(), 1048576, 2) . ' MB';

O resultado:

Memória: 0.37 MB

Veja que a diferença do consumo de memória entre as duas abordagens é enorme. Se o tamanho do arquivo que você está lendo ou precisa processar for muito grande você pode, inclusive, estourar o limite de memória do PHP se não usar um Iterator.

Com os Generators é possível que processemos arquivos de dezenas ou centenas de GB’s sem estourar o limite de memória disponível para a aplicação.

Concluindo

O uso de Generators é indicado e muitas vezes se faz necessário quando é preciso iterar uma grande coleção de dados de acesso sequencial.

Não são muitos os casos de uso para Generators na programação regular (digo, do dia a dia). Mas, certamente possuem o seu espaço. Generators são, inclusive, a base para programação assíncrona com PHP. E um framework que se destaca utilizando-os é o AMP.

JUNTE-SE A MAIS DE 150.000 PROGRAMADORES