PHP

Ferramentas essenciais para um projeto PHP

Este será um artigo um pouco diferente dos últimos que tenho escrito, vou citar as quatro principais ferramentas que considero essenciais para qualquer projeto PHP:

  • PHPUnit (Testes unitários);
  • PHPStan (Analisador estático);
  • PHP-CS-Fixer (Corrige estilo de código);
  • PHP Insights (Analisa o estilo e a qualidade do código);
Desenvolvedor PHP Sênior
Formação: Desenvolvedor PHP Sênior
Nesta formação você aprenderá aspectos avançados da linguagem PHP e de seu ecossistema, conhecimentos essenciais para se tornar um desenvolvedor diferenciado, e com isso, você terá conhecimento para desenvolver aplicações PHP usando as práticas mais modernas do mercado.
CONHEÇA A FORMAÇÃO

1) PHPUnit

O PHPUnit é o principal e mais estabelecido framework para testes unitários em PHP. Automatizar testes é um requisito cada vez mais essencial para os projetos, não importando se você vai seguir TDD como metodologia, mas pelo menos testar as partes mais críticas da aplicação é algo que pode te salvar algumas boas horas procurando bugs e corrigindo problemas da evolução natural do software.

Você pode instalá-lo em seu projeto executando:

$ composer require phpunit/phpunit:^8.0 --dev

Hoje a ferramenta já tem a documentação traduzida para o português, você pode ler aqui.

Testar no PHPUnit é super simples e bem parecido com a forma que se faz em outros frameworks de teste de outras linguagens:

Exemplo extraído da documentação:

<?php

declare(strict_types=1);

final class Email
{
    private $email;

    private function __construct(string $email)
    {
        $this->ensureIsValidEmail($email);

        $this->email = $email;
    }

    public static function fromString(string $email): self
    {
        return new self($email);
    }

    public function __toString(): string
    {
        return $this->email;
    }

    private function ensureIsValidEmail(string $email): void
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                sprintf(
                    '"%s" is not a valid email address',
                    $email
                )
            );
        }
    }
}

Essa classe poder ser facilmente testada:

<?php

declare(strict_types=1);

use PHPUnit\Framework\TestCase;

final class EmailTest extends TestCase
{
    public function testCanBeCreatedFromValidEmailAddress(): void
    {
        $this->assertInstanceOf(
            Email::class,
            Email::fromString('user@example.com')
        );
    }

    public function testCannotBeCreatedFromInvalidEmailAddress(): void
    {
        $this->expectException(InvalidArgumentException::class);

        Email::fromString('invalid');
    }

    public function testCanBeUsedAsString(): void
    {
        $this->assertEquals(
            'user@example.com',
            Email::fromString('user@example.com')
        );
    }
}
PHP - Testes unitários com PHPUnit
Curso de PHP - Testes unitários com PHPUnit
CONHEÇA O CURSO

2) PHPStan

O PHPStan é um analisador estático que varre o código da sua aplicação procurando por erros e incongruências sem que haja a necessidade de executá-lo, a análise é toda estática.

É uma ferramenta essencial para te apontar bugs ou erros que certamente aparecerão em algum momento do uso da aplicação.

Para instalar o PHPStan no seu projeto:

$ composer require --dev phpstan/phpstan

Uma vez instalado, basta executar a ferramenta indicando quais pastas ele deve avaliar:

$ ./vendor/bin/phpstan analyse src tests

Nesse caso, estará avaliando as pastas src e tests.

Você ainda pode especificar o quão estrita você quer que a análise seja feita. Por padrão ele opera no level 0, mas vai até o 7:

$ ./vendor/bin/phpstan analyse src tests --level 7

Para aplicações que rodam em cima do Laravel, tem o Larastan, que facilita a instalação e personalização.

Apesar de eu preferir o PHPStan, também existe o ótimo Psalm, mantido pelo pessoal do Vimeo. Vale a pena estudá-lo, talvez você venha a preferi-lo.

3) PHP-CS-Fixer

O PHP-CS-Fixer corrige todo o estilo do seu código, quebras, espaços, estilo de declarações etc. É possível definir para que ele siga as PSR-1 e PSR-2, por exemplo. Não obstante, já estão desenvolvendo suporte nele para o mais novo padrão de estilo de código, o PSR-12.

Para instalá-lo em seu projeto:

$ composer require friendsofphp/php-cs-fixer --dev

E para rodar as correções usando as regras da PSR-2:

./vendor/bin/php-cs-fixer fix src --rules=@PSR2 --allow-risky yes

Nesse caso ele rodará na pasta src.

Uma forma mais simples de configurar o perfil de execução dele é criando um arquivo .php_cs na raiz do seu projeto e então definir o que é pra ele validar, o que não é, quais regras usar, quais configurações de estilo usar etc. Em nossas aplicações no TreinaWeb usamos esse perfil:

<?php

$finder = Symfony\Component\Finder\Finder::create()
    ->notPath('vendor')
    ->notPath('bootstrap')
    ->notPath('storage')
    ->notPath('resources')
    ->in(__DIR__)
    ->name('*.php')
    ->notName('*.blade.php');

return PhpCsFixer\Config::create()
    ->setRules([
        '@PSR2' => true,
        'final_class' => true,
        'static_lambda' => true,
        'linebreak_after_opening_tag' => true,
        'blank_line_after_opening_tag' => true,
        'declare_strict_types' => true,
        'array_syntax' => ['syntax' => 'short'],
        'ordered_imports' => ['sortAlgorithm' => 'length'],
        'no_unused_imports' => true,
        'native_function_invocation' => true,
        'is_null' => true,
        'list_syntax' => [
            'syntax' => 'short',
        ],
        'lowercase_cast' => true,
        'lowercase_static_reference' => true,
        'mb_str_functions' => true,
        'modernize_types_casting' => true,
        'native_constant_invocation' => true,
        'native_function_casing' => true,
        'new_with_braces' => true,
        'blank_line_before_statement' => [
            'statements' => ['declare',],
        ],
        'return_type_declaration' => [
            'space_before' => 'none',
        ],
    ])
    ->setFinder($finder);

Outras configurações você pode verificar no repositório oficial da ferramenta. Uma vez tendo o arquivo .php_cs, é só executar sem precisar informar --rules:

./vendor/bin/php-cs-fixer fix src --allow-risky yes

4) PHP Insights

Ferramenta para análise da qualidade do código. Ele divide a análise em quatro categorias: código, complexidade (complexidade ciclomática etc), arquitetura (tamanho de métodos e classes, entre outras análises) e estilo de código (formatação, padronização).

Para instalar a ferramenta no seu projeto:

$ composer require nunomaduro/phpinsights --dev

Depois, basta executar:

$ ./vendor/bin/phpinsights

Você pode criar um arquivo phpinsights.php na raiz do seu projeto para configurar o perfil da análise, ou seja, para definir o que você quer remover da análise, o que quer incluir etc. Por exemplo:

<?php

declare(strict_types=1);

use ObjectCalisthenics\Sniffs\Files\FunctionLengthSniff;
use NunoMaduro\PhpInsights\Domain\Insights\ForbiddenTraits;
use ObjectCalisthenics\Sniffs\Metrics\MethodPerClassLimitSniff;
use SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff;
use NunoMaduro\PhpInsights\Domain\Insights\CyclomaticComplexityIsHigh;
use SlevomatCodingStandard\Sniffs\TypeHints\DisallowMixedTypeHintSniff;
use NunoMaduro\PhpInsights\Domain\Insights\Composer\ComposerMustBeValid;
use SlevomatCodingStandard\Sniffs\Namespaces\AlphabeticallySortedUsesSniff;
use PHP_CodeSniffer\Standards\PSR1\Sniffs\Methods\CamelCapsMethodNameSniff;
use PHP_CodeSniffer\Standards\Generic\Sniffs\Files\LineLengthSniff;

return [
    'preset' => 'default',

    'exclude' => [
        'vendor',
        'config',
        'bootstrap',
        'resources',
        'storage',
        'public',
        'tests',
    ],

    'add' => [

    ],

    'remove' => [
        ForbiddenTraits::class,
        TypeHintDeclarationSniff::class,
        DisallowMixedTypeHintSniff::class,
        ComposerMustBeValid::class,
        AlphabeticallySortedUsesSniff::class,
        CamelCapsMethodNameSniff::class,
        LineLengthSniff::class,
    ],

    'config' => [
        CyclomaticComplexityIsHigh::class => [
            'maxComplexity' => 7,
        ],

        FunctionLengthSniff::class => [
            'maxLength' => 30,
        ],

        MethodPerClassLimitSniff::class => [
            'maxCount' => 12,
        ],
    ],
];

Maiores informações sobre a configuração da ferramenta, você pode ver no site oficial.

Usando o Composer como atalho para a execução de scripts

Por fim, uma coisa que acho bem útil e que uso bastante é colocar no composer.json atalhos para os scripts de linha de comando que mais executo no projeto. É possível defini-los numa área scripts, por exemplo:

    "scripts": {
        "phpunit": [
            "./vendor/bin/phpunit"
        ],        
        "cs": [
            "./vendor/bin/php-cs-fixer fix --allow-risky yes"
        ],
        "phpstan": [
            "./vendor/bin/phpstan analyse src tests --level 7"
        ],
        "phpinsights": [
            "./vendor/bin/phpinsights"
        ]
    },

Então, ao invés de executar ./vendor/bin/phpstan analyse src tests --level 7 eu apenas faço:

$ composer phpstan

O mesmo vale para a correção do estilo do código, apenas executo:

$ composer cs

Concluindo

Espero que o artigo tenha despertado a sua curiosidade. Recomendo demais que você leia a documentação dessas ferramentas e passe a utilizá-las, em pouco tempo você estará “viciado” e certamente as usará em todos os futuros projetos.

Até a próxima!

Stream Wrappers personalizados no PHP

No artigo Streams no PHP, que é uma leitura pré-requisito deste, vimos o essencial sobre streams e como podemos operar sobre elas. Também vimos que o PHP implementa diversos wrappers nativamente, como http://, php://, file:// entre outros, que permitem que operemos determinados protocolos usando as funções nativas do PHP como fgets(), fopen(), fwrite() etc. Nesse artigo veremos como podemos implementar um protocolo próprio a partir de um stream wrapper customizado.

PHP Avançado
Curso de PHP Avançado
CONHEÇA O CURSO

Não existe uma classe base a ser estendida ou uma interface a ser implementada, tudo o que o PHP fornece é um protótipo de quais métodos podem ser utilizados no wrapper personalizado. Isso pode ser visto na referência The StreamWrapper Class.

Funcionamento do Wrapper

SqliteJsonWrapper será o nome dele. Definiremos o protocolo sqlj:// e ele terá a responsabilidade de consultar um banco de dados SQLite e retornar os resultados no formato json.

O código completo do wrapper pode ser obtido no repositório KennedyTedesco/artigo-stream-wrappers-php.

Dinâmica de funcionamento:

Dentro da pasta resources do projeto temos uma base de dados SQLite chamada movies.sqlite3, nela existe uma tabela chamada movies com uma relação de filmes e alguns dados sobre eles, nesse formato:

filmgenrestudioyear
(500) Days of SummercomedyFox2009
27 DressesComedyFox2008
A Dangerous MethodDramaIndependent2011
A Serious ManDramaUniversal2009
Across the UniverseromanceIndependent2007
BeginnersComedyIndependent2011
Dear JohnDramaSony2010

Usando o componente illuminate/database do Laravel, vamos nos conectar a essa base de dados e realizar uma consulta, a depender do que será informado na URL do wrapper.

O uso do nosso wrapper se dará dessa forma:

$stream = \fopen('sqlj://movies/year/2009', 'rb', false, $context);

Informamos o protocolo sqlj://, movies é o nome da tabela que será consultada, year é o campo que será usado para delimitar a consulta, ou seja, nesse caso, estaremos resgatando apenas os filmes do ano de 2009 cadastrados na nossa base.

Se a ideia é obter todos os filmes, basta que informemos apenas o nome da tabela:

$stream = \fopen('sqlj://movies', 'rb', false, $context);

Ou, se quisermos obter apenas filmes do gênero drama:

$stream = \fopen('sqlj://movies/genre/drama', 'rb', false, $context);

Você já deve ter percebido que “The Patifaria Never Ends“, esse é um exemplo puramente didático. Não faz muito sentido um wrapper com essa implementação em uma aplicação real que realiza uma consulta em um banco de dados SQLite local, ademais, poderíamos nos conectar diretamente ao banco usando PDO.

A ideia desse artigo é mostrar que é possível abstrair qualquer tipo de coisa para streams e usar as funções nativas do PHP para lidar com esses chunks de dados, o que faz mais sentido quando operamos com grandes arquivos. Mas no final desse artigo veremos referências de casos de uso de Stream Wrappers que são implementados por grandes projetos. Vamos ao código?

Desenvolvedor PHP Sênior
Formação: Desenvolvedor PHP Sênior
Nesta formação você aprenderá aspectos avançados da linguagem PHP e de seu ecossistema, conhecimentos essenciais para se tornar um desenvolvedor diferenciado, e com isso, você terá conhecimento para desenvolver aplicações PHP usando as práticas mais modernas do mercado.
CONHEÇA A FORMAÇÃO

Show me the code

A classe SqliteJsonWrapper é a implementação do wrapper:

<?php

declare(strict_types=1);

use Illuminate\Database\Connection;
use Illuminate\Database\Capsule\Manager as Capsule;

final class SqliteJsonWrapper
{
    /** @var resource */
    public $context;

    /** @var string */
    private $result;

    /** @var Connection */
    private $connection;

    /** @var int */
    private $position = 0;

    public function stream_open(string $path, string $mode, int $options) : bool
    {
        // Resgata as informações de contexto da stream que foram passadas
        $streamContext = \stream_context_get_options($this->context);

        if (empty($streamContext['database'])) {
            throw new \RuntimeException('Missing Stream Context');
        }

        // Conecta à base de dados
        $this->connect($streamContext);

        // Realiza a pesquisa
        $this->query($path);

        return true;
    }

    public function stream_read(int $count)
    {
        $chunk = \mb_substr($this->result, $this->position, $count);

        $this->position += $count;

        return $chunk;
    }

    public function stream_eof() : bool
    {
        return ! ($this->position < \mb_strlen($this->result));
    }

    public function stream_stat() : ?array
    {
        return null;
    }

    private function connect(array $options) : void
    {
        $capsule = new Capsule();

        $capsule->addConnection([
            'driver'    => 'sqlite',
            'database'  =>  $options['database']['file'],
            'prefix'    => '',
        ]);

        $this->connection = $capsule->getConnection();
    }

    private function query(string $path) : void
    {
        // Extrai o nome da tabela
        $table = \parse_url($path, \PHP_URL_HOST);

        // Tenta extrair se é pra delimitar a consulta com where
        $where = [];
        if ($path = \parse_url($path, \PHP_URL_PATH)) {
            $criteria = \explode('/', $path);

            $where = [
                $criteria[1] => $criteria[2]
            ];
        }

        // Armazena os resultados no formato json
        $this->result = $this->connection->table($table)->where($where)->get()->toJson();
    }
}

A classe do wrapper precisa se basear no protótipo especificado em The StreamWrapper Class, mas utilizando apenas os métodos que fazem sentido para o caso de uso dela. No nosso caso, nos limitamos aos métodos:

  • stream_open(): Executado quando a stream é aberta usando fopen().
  • stream_read(): Executado quando alguma função de leitura é utilizada, como fgets() ou stream_get_contents(), ele retorna em chunks os dados e avança o ponteiro que guarda até que posição de bytes de dados já foram lidos, para que na próxima iteração ele pegue a partir daquele ponto.
  • stream_eof(): Executado quando a função feof() é invocada. Ele informa se está no final do arquivo.
  • stream_stat(): Executado em resposta à função fstat(), mas também quando a stream_get_contents() é chamada. Para o nosso exemplo, esse método não precisa retornar nenhum dado sobre o recurso que estamos operando, mas ele precisa existir na classe, mesmo que sem implementação.

Esses são os quatro métodos que permitem com que façamos as principais operações em cima dos dados resgatados.

Os outros métodos que a classe implementa são apenas da lógica dela para consulta na base de dados:

  • connect(): Esse método realiza a conexão com a base de dados;
  • query(): Uma vez que uma conexão foi aberta, esse método recebe a URL de consulta do wrapper para realizar uma pesquisa na base de dados.

Um uso básico do nosso wrapper:

<?php

declare(strict_types=1);

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

\stream_wrapper_register('sqlj', SqliteJsonWrapper::class);

$context = \stream_context_create([
    'database' => [
        'file' => './resources/movies.sqlite3',
    ],
]);

$stream = \fopen('sqlj://movies/year/2009', 'rb', false, $context);

$buffer = '';
while (\feof($stream) === false) {
    $buffer .= \fread($stream, 128);
}

echo $buffer;

fclose($stream);

O resultado:

[{"film":"(500) Days of Summer","genre":"comedy","studio":"Fox","year":"2009"},{"film":"A Serious Man","genre":"drama","studio":"Universal","year":"2009"},{"film":"Ghosts of Girlfriends Past","genre":"comedy","studio":"Warner Bros.","year":"2009"},{"film":"He's Just Not That Into You","genre":"comedy","studio":"Warner Bros.","year":"2009"},{"film":"It's Complicated","genre":"comedy","studio":"Universal","year":"2009"},{"film":"Love Happens","genre":"drama","studio":"Universal","year":"2009"},{"film":"Not Easily Broken","genre":"drama","studio":"Independent","year":"2009"},{"film":"The Invention of Lying","genre":"comedy","studio":"Warner Bros.","year":"2009"},{"film":"The Proposal","genre":"comedy","studio":"Disney","year":"2009"},{"film":"The Time Traveler's Wife","genre":"drama","studio":"Paramount","year":"2009"},{"film":"The Twilight Saga: New Moon","genre":"drama","studio":"Summit","year":"2009"},{"film":"The Ugly Truth","genre":"comedy","studio":"Independent","year":"2009"}]⏎

A stream_wrapper_register() registra o wrapper. Na variável $context criamos o contexto da stream com a informação de localização do arquivo da base de dados que será trabalhado. Depois disso, abrimos a stream em cima do protocolo que definimos e extraímos os dados dela em chunks de 128 bytes.

O mesmo exemplo também poderia ser reescrito para usar stream_get_contents():

<?php

declare(strict_types=1);

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

\stream_wrapper_register('sqlj', SqliteJsonWrapper::class);

$context = \stream_context_create([
    'database' => [
        'file' => './resources/movies.sqlite3',
    ],
]);

$stream = \fopen('sqlj://movies/year/2009', 'rb', false, $context);

echo stream_get_contents($stream);
PHP Intermediário
Curso de PHP Intermediário
CONHEÇA O CURSO

Concluindo

Igual comentei anteriormente, existem diversas implementações possíveis para Stream Wrappers, vou me delimitar a duas bem legais, que são:

  • Google Storage: No cliente do Google Cloud para PHP eles definiram um Stream Wrapper para que o usuário consiga recuperar ou gravar objetos no serviço usando as funções nativas do PHP a partir do protocolo gs://.
  • AWS S3: O mesmo existe na SDK da AWS para PHP, um Stream Wrapper para que seja possível usar o protocolo s3:// e recuperar / gravar objetos no serviço de storage S3.

No caso da AWS, ter uma implementação de Stream Wrapper para abstrair a comunicação com o serviço de storage dela, traz benefícios para o desenvolvedor que usa sua SDK tais como:

Para recuperar um arquivo:

$data = file_get_contents('s3://bucket/key');

Se precisa trabalhar com grandes arquivos:

// Abre a stream
if ($stream = fopen('s3://bucket/key', 'r')) {
    // Enquanto a stream continua aberta
    while (!feof($stream)) {
        // Lê 1024 bytes da stream
        echo fread($stream, 1024);
    }
    fclose($stream);
}

Como também permite que objetos sejam criados:

file_put_contents('s3://bucket/key', 'Hello!');

Bom, é isso! É bem possível que eu continue escrevendo sobre o assunto de streams nos próximos artigos, então, até breve!

Streams no PHP

Streams representam coleções de dados que podem não estar completamente disponíveis de imediato e que também não possuem a limitação de terem que caber de uma só vez na memória. Isso faz com que elas sejam uma poderosa ferramenta para lidarmos com os dados em partes (chunks) e acessados sob demanda. Uma definição mais técnica para streams é que representam a transmissão/fluxo de dados de uma fonte para um destino.

Entender o essencial sobre streams é fundamental para se trabalhar com arquivos. Sem streams não conseguiríamos abrir um arquivo de 20GB se não tivéssemos 20GB de memória disponíveis. Com elas, abrimos o arquivo e o lemos em partes. Sistemas operacionais baseados no Unix proveem uma interface padrão para operações I/O (input e output) que se passa por descritores de arquivo. Um descritor de arquivo é representado por um número inteiro que se associa a um arquivo aberto e, nesses sistemas, há uma generalização de que “tudo é um arquivo”, então, quando falamos aqui de arquivo, ele pode ser uma conexão de rede (um socket é um tipo especial de arquivo), um arquivo de texto, um arquivo de vídeo ou até mesmo uma pasta (que é um tipo especial de arquivo).

PHP Básico
Curso de PHP Básico
CONHEÇA O CURSO

Grande parte do que é feito no PHP é sobre streams. Ao ler um arquivo, lidamos com streams. Ao retornar um output para o cliente, lidamos com streams. Ao ler um input, lidamos com streams. Ao obter dados de uma conexão TCP/IP, também estaremos trabalhando com streams. O PHP abstrai ao máximo o trabalho com as streams e, na maior parte do tempo, usamos funções de alto nível da linguagem que deixam bem transparente o uso delas. No entanto, há momentos em que precisamos conhecer um pouco mais sobre elas para que consigamos resolver problemas e necessidades específicas. Essa é a ideia desse artigo.

Operações básicas com streams

Uma stream é referenciada dessa maneira:

<scheme>://<target>

Scheme é nome do wrapper que está sendo utilizado. Target é a identificação do recurso que será trabalhado. E esse recurso vai depender do contexto, se estamos falando do sistema de arquivos ou da rede, por exemplo.

O PHP possui algumas funções genéricas que nos permitem interagir com streams e até mesmo mudar o comportamento delas. Muitas das funções que trabalham com arquivos permitem essa interação. Algumas das mais conhecidas são:

  • fopen()
  • fwrite() ou fputs()
  • fclose()
  • file_get_contents()
  • file_put_contents()
  • Entre outras.

O PHP possui uma extensão chamada Stream que já vem habilitada por padrão. Como o próprio nome sugere, ela lida com toda a abstração de streams de I/O (input e output) do PHP. No decorrer do artigo utilizaremos algumas dessas funções.

Antes, apenas para dar contexto ao que falamos anteriormente de que com streams lidamos com chunks e que isso permite trabalharmos com grandes quantidade de dados sem estourar o limite de memória, desenvolveremos um simples exemplo.

Se você usa um sistema baseado em unix, em um diretório de testes, crie o seguinte arquivo:

$ awk 'BEGIN { n = 1; while (n < 10000000) print (n++) }' > numeros.txt

O arquivo criado tem ~78MB e é composto apenas de números sequenciais. Agora, digamos hipoteticamente que a nossa instalação do PHP nos permite usar apenas 4MB por script, vamos tentar ler o arquivo utilizando file_get_contents():

<?php

ini_set('memory_limit', '4M');

$file = file_get_contents('numeros.txt');

Ao executar, teremos o erro:

PHP Fatal error:  Allowed memory size of ...

Ele tentou alocar mais memória que o permitido, pois o arquivo é muito grande e a file_get_contents() lê o arquivo inteiro na memória.

Agora, a coisa muda quando passamos a utilizar fopen() e fgets(). A fopen() lida “diretamente” com descritores de arquivos, ela apenas abre uma stream para que possamos utilizá-la. Ou seja, dar um echo em fopen não produzirá nenhum resultado, diferentemente de file_get_contents(). Se você der um var_dump() na fopen() verá que é um resource do tipo stream.

<?php

$file = fopen('numeros.txt', 'rb');

var_dump($file); // resource(5) of type (stream)

Usando fgets() podemos ler a linha da stream aberta, sem carregar o arquivo inteiro na memória:

<?php

ini_set('memory_limit', '4M');

$stream = fopen('numeros.txt', 'rb');

while (feof($stream) === false) {
    echo fgets($stream);
}

echo 'Memória utilizada: ' . (memory_get_peak_usage(true) / 1024 / 1024);

fclose($stream);

A função feof() avalia se já chegou ao final do arquivo, se não, continua no while até que o arquivo seja completamente lido e seus dados impressos no output buffer. A fgets() lê linha a linha da stream aberta.

No entanto, a fgets() retorna false quando não tiver mais nenhum byte a ser lido e, nesse sentido, o while do exemplo acima poderia ser reescrito para:

while (($line = fgets($stream)) !== false) {
    echo $line;
}

Você pode usar a abordagem que preferir, ambas resolvem o problema.

O principal wrapper de streams é o file://, ele é o padrão utilizado pela fopen() e outras funções relacionadas. Sempre que acessamos o sistema de arquivos, estamos de forma transparente o utilizando. Por esse motivo, o mesmo exemplo acima poderia ser reescrito para:

<?php

$stream = fopen('file://' . __DIR__ . '/numeros.txt', 'rb');

while (feof($stream) === false) {
    echo fgets($stream);
}

fclose($stream);

Se abríssemos o servidor embutido do PHP no diretório onde o exemplo está salvo:

$ php -S localhost:8000

Também conseguiríamos acessar o numeros.txt através do wrapper http:// do PHP:

<?php

$stream = fopen('http://localhost:8000/numeros.txt', 'rb');

while (feof($stream) === false) {
    echo fgets($stream);
}

fclose($stream);

Teríamos o mesmo resultado, mas no fundo seria aberto um socket TCP/IP para acessar esse servidor, ou seja, no caso desse exemplo em particular em que só precisamos abrir o arquivo e ele está localmente disponível no nosso sistema de arquivo, abrí-lo através da rede é desnecessário. Fizemos isso apenas para fins didáticos.

Falando em sockets, uma dica de leitura: Programação de Sockets em PHP

Nos exemplos acima usamos fgets() que lê linha a linha, mas se temos a necessidade de pegar chunks de um determinado tamanho em bytes por vez, podemos usar a fread():

<?php

ini_set('memory_limit', '4M');

$stream = fopen('http://localhost:8000/numeros.txt', 'rb');

echo fread($stream, filesize('numeros.txt'));

Nesse exemplo estamos pedindo pra fread() ler o tamanho total do arquivo, o que não será possível, devido ao limite de memória de 4MB que temos. Vai estourar o erro da incapacidade de alocação desses dados.

Agora, se a gente diminui o tanto de bytes que queremos ler por vez:

<?php

$stream = fopen('http://localhost:8000/numeros.txt', 'rb');

while (feof($stream) === false) {
    echo '[-------- CHUNK --------]' . PHP_EOL;
    echo fread($stream, 2048);
}

A cada novo [-------- CHUNK --------] que você notar no terminal significa que 2048 bytes foram lidos.

A função stream_get_meta_data() retorna os metadados de uma stream aberta:

<?php

$stream = fopen('numeros.txt', 'rb');

var_dump(stream_get_meta_data($stream));

fclose($stream);

Teria o retorno:

array(9) {
  'timed_out' => bool(false)
  'blocked' => bool(true)
  'eof' => bool(false)
  'wrapper_type' => string(9) "plainfile"
  'stream_type' => string(5) "STDIO"
  'mode' => string(2) "rb"
  'unread_bytes' => int(0)
  'seekable' => bool(true)
  'uri' => string(11) "numeros.txt"
}

Numa stream que usa o wrapper http teríamos algumas informações adicionais como alguns cabeçalhos http da resposta:

<?php

$stream = fopen('http://localhost:8000/numeros.txt', 'rb');

var_dump(stream_get_meta_data($stream));

fclose($stream);

E o retorno é:

array(10) {
  'timed_out' => bool(false)
  'blocked' => bool(true)
  'eof' => bool(false)
  'wrapper_data' => array(6) {
    [0] => string(15) "HTTP/1.0 200 OK"
    [1] => string(20) "Host: localhost:8000"
    [2] => string(35) "Date: Wed, 21 Aug 2019 18:41:52 GMT"
    [3] => string(17) "Connection: close"
    [4] => string(39) "Content-Type: text/plain; charset=UTF-8"
    [5] => string(24) "Content-Length: 78888888"
  }
  'wrapper_type' => string(4) "http"
  'stream_type' => string(14) "tcp_socket/ssl"
  'mode' => string(2) "rb"
  'unread_bytes' => int(0)
  'seekable' => bool(false)
  'uri' => string(33) "http://localhost:8000/numeros.txt"
}

Voltaremos em breve no wrapper http quando vermos sobre contextos de streams.

A função stream_copy_to_stream()

A função stream_copy_to_stream() permite copiar dados de uma stream para outra. Isso costuma ser útil em algumas operações on-the-fly.

O exemplo mais básico de como isso pode ser feito:

<?php

$destination = fopen('cep.json', 'wb');
$source = fopen('https://viacep.com.br/ws/01001000/json/', 'rb');

stream_copy_to_stream($source, $destination);

Copiará para o arquivo cep.json os dados desse CEP.

PHP Intermediário
Curso de PHP Intermediário
CONHEÇA O CURSO

O wrapper php://

Esse é um wrapper especial que permite com que acessemos as streams de I/O da linguagem. Por exemplo, se queremos ler a STDIN (standard input stream) do PHP e depois escrever direto na STDOUT (standard output stream):

<?php

$input = fopen('php://stdin', 'rb');
$output = fopen('php://stdout', 'rb');

fwrite($output, 'Ei, fale algo. :D' . PHP_EOL);

while (true) {
    $line = trim(fgets($input));

    if ($line === ':close') {
        fwrite($output, 'Bye!');

        break;
    }

    fwrite($output, "User says: {$line}" . PHP_EOL);
}

fclose($input);
fclose($output);

Esse exemplo abre duas streams de I/O do PHP, uma relacionada ao input e outra ao output. Depois, num laço infinito ficamos “ouvindo” por novas entradas e escrevemos de volta tudo o que recebemos.

Se você testar o exemplo executando no seu terminal:

$ php index.php
Ei, fale algo. :D
Oi!
User says: Oi!
PHP
User says: PHP
:close
Bye!

Definimos :close como sendo o comando que vai interromper o while.

Ao invés de explicitamente abrimos a stdin e stdout usando fopen(), poderíamos usar duas constantes que o PHP disponibiliza que já fazem esse trabalho pra nós, são as STDIN e STDOUT.

Internamente o PHP define essas constantes:

define('STDIN', fopen('php://stdin', 'r'));
define('STDOUT', fopen('php://stdout', 'w'));

Por exemplo:

<?php

echo fgets(STDIN);

No terminal, execute:

$ echo "Hello World" | php index.php

O resultado será Hello World. O | refere-se ao mecanismo de pipiline do unix, onde podemos passar o stdout de um processo para o stdin de outro (uma forma rudimentar de se atingir inter-process communication).

Poderíamos alterar o exemplo para utilizar essas constantes e torná-lo menos verboso:

<?php

fwrite(STDOUT, 'Ei, fale algo. :D' . PHP_EOL);

while (true) {
    $line = trim(fgets(STDIN));

    if ($line === ':close') {
        fwrite(STDOUT, 'Bye!');

        break;
    }

    fwrite(STDOUT, "User says: {$line}" . PHP_EOL);
}

A relação com todos os I/O streams disponíveis no PHP você pode acessar clicando aqui.

Também poderíamos escrever direto na stream de output do PHP, no sentido de mandar pro output buffer dados sem que precisemos usar echo ou print. Por exemplo:

<?php

$ouput = fopen('php://output', 'rb');

fwrite($ouput, 'Hello World');

fclose($ouput);

Isso é o mesmo que:

<?php

echo 'Hellow World!';

O PHP implementa outros wrappers nativamente, a lista dos disponíveis pode ser obtida executando:

$ php -r "var_dump(stream_get_wrappers());"
array(12) {
  [0] => string(5) "https"
  [1] => string(4) "ftps"
  [2] => string(13) "compress.zlib"
  [3] => string(14) "compress.bzip2"
  [4] => string(3) "php"
  [5] => string(4) "file"
  [6] => string(4) "glob"
  [7] => string(4) "data"
  [8] => string(4) "http"
  [9] => string(3) "ftp"
  [10] => string(4) "phar"
  [11] => string(3) "zip"
}

As streams php://temp e php://memory

São streams de leitura e escrita que permitem o armazenamento temporário de dados na memória, com a diferença que a php://temp armazena na memória até que se atinja o limite padrão de 2MB (esse limite pode ser alterado por configuração), depois disso, os dados são armazenados em arquivo temporário no sistema de arquivos.

Elas são úteis quando se tem a necessidade de tratar um determinado dado como um stream, por exemplo:

<?php

function createStreamFromString(string $contents) {
    $stream = fopen('php://memory', 'r+b');

    fwrite($stream, $contents);
    rewind($stream);

    return $stream;
}

echo fgets(createStreamFromString('Hello World!'));

A função rewind() volta o ponteiro do arquivo para o início.

Um possível caso de uso pra essa abordagem seria, por exemplo, em memória a construção de um arquivo CSV, depois, a compactação dele (para ZIP) e o streaming disso para quem requisitar, sem envolver salvar no disco, por exemplo. No entanto, esse mesmo objetivo pode ser atingido mais facilmente usando a library ZipStream-PHP que abstrai numa API de alto nível o streaming de arquivos zips:

setSendHttpHeaders(true);

$zip = new ZipStream\ZipStream('example.zip', $options);
$zip-&gt;addFile('hello.txt', 'This is the contents of hello.txt');

$zip-&gt;finish();
PHP Avançado
Curso de PHP Avançado
CONHEÇA O CURSO

A função stream_get_contents()

Identica à file_get_contents(), com a diferença que essa função retorna os dados de uma stream aberta. O exemplo anterior poderia ser reescrito para:

<?php

function createStreamFromString(string $contents) {
    $stream = fopen('php://memory', 'r+b');

    fwrite($stream, $contents);
    rewind($stream);

    return $stream;
}

echo stream_get_contents(createStreamFromString('Hello World!'));

Stream contexts

Um contexto é uma série de parâmetros e opções específicas de um determinado wrapper que pode ser informado na maioria das funções que trabalham com streams. Ele serve pra aprimorar o uso ou até mesmo alterar o comportamento de uma stream. Por exemplo, as funções file_get_contents() e file_put_contents() aceitam contexto de stream como parâmetro. Um contexto é criado usando a função stream_context_create().

O uso mais comum de contextos para streams se dá na definição de cabeçalhos HTTP usando o wrapper http. Por exemplo, crie um arquivo producer.php:

<?php

header('Content-Type: application/json');

$id = $_POST['id'];
$qtd = $_POST['qtd'];

if ( ! isset($_POST['id'], $_POST['qtd'])) {
    http_response_code(400);

    exit;
}

echo json_encode([
    'id' => $id,
    'qtd' => $qtd,
    'timestamp' => time(),
]);

E em outro arquivo, no meu caso estou usando um index.php, podemos requisitá-lo passando os dados que ele exige:

<?php

$context = stream_context_create([
    'http'=> [
        'method' => 'POST',
        'header'=> 'Content-type: application/x-www-form-urlencoded',
        'content' => http_build_query([
            'id' => 1,
            'qtd' => 10,
        ]),
    ]
]);

$json = file_get_contents('http://localhost:8000/producer.php', false, $context);

var_dump(json_decode($json, true));

Lembrando que o servidor embutido do PHP precisa estar rodando no diretório do exemplo:

$ php -S localhost:8000

E então, podemos executar:

$ php index.php
array(3) {
  'id' => string(1) "1"
  'qtd' => string(2) "10"
  'timestamp' => int(1566422525)
}

Todas as opções do contexto do wrapper http podem ser vistas em Opções de contexto do HTTP. E o índice com as opções para os diferentes wrappers pode ser visto em Opções e parâmetros de contexto.

O mesmo exemplo consumidor poderia ser reescrito para:

<?php

$context = stream_context_create([
    'http'=> [
        'method' => 'POST',
        'header'=> 'Content-type: application/x-www-form-urlencoded',
        'content' => http_build_query([
            'id' => 1,
            'qtd' => 10,
        ]),
    ]
]);

$stream = fopen('http://localhost:8000/producer.php', 'rb', false, $context);

$json = '';

while (feof($stream) === false) {
    $json .= fgets($stream);
}

var_dump(json_decode($json, true));

fclose($stream);

Ficou mais verboso, mas atingimos o mesmo resultado. Essa segunda opção utilizando fopen() é puramente didática, para mostrar que contextos podem ser trabalhados com fopen() normalmente, mas ela não é a melhor opção para esse exemplo em específico.

Uma função interessante e que pode ser útil dependendo do que se vá desenvolver é a fpassthru(), ela recebe um resource de uma stream e imprime compulsivamente tudo o que recebe dele. É como se ela fizesse um while (true) com echo fgets().

Vamos testá-la? Altere o exemplo de producer.php para:

<?php

$stream = fopen('numeros.txt', 'rb');

while (feof($stream) === false) {
    echo fgets($stream);
}

fclose($stream);

Estamos iterando e imprimindo as partes do arquivo. Agora, no index.php, vamos consumir isso:

<?php

$stream = fopen('http://localhost:8000/producer.php', 'rb');

fpassthru($stream);

fclose($stream);

Ao executar:

$ php index.php

Você verá um tsunami de números sendo impressos no seu terminal.

E, claro, nada impede que o producer.php também use fpassthru():

<?php

$stream = fopen('numeros.txt', 'rb');

fpassthru($stream);

fclose($stream);

Teremos o mesmo resultado.

Stream Filters

Um filtro performa uma transformação on-the-fly no dado que está sendo lido ou gravado em uma stream e N filtros podem ser usados ao mesmo tempo. Os filtros nativamente disponíveis podem ser listados executando:

$ php -r "var_dump(stream_get_filters());"
array(10) {
  [0] => string(6) "zlib.*"
  [1] => string(7) "bzip2.*"
  [2] => string(15) "convert.iconv.*"
  [3] => string(12) "string.rot13"
  [4] => string(14) "string.toupper"
  [5] => string(14) "string.tolower"
  [6] => string(17) "string.strip_tags"
  [7] => string(9) "convert.*"
  [8] => string(8) "consumed"
  [9] => string(7) "dechunk"
}

E, claro, podemos criar nossos próprios filtros. Mas, primeiro vamos focar em usar um filtro nativo Começaremos pelo filtro string.toupper que converte todos os dados da stream para caixa alta. Primeiro, crie um arquivo nomes.txt com o seguinte conteúdo:

<b>Thalia Bishop</b>
<b>Mckenna Carrillo</b>
<b>Wesley Curtis</b>
<b>Irene Neal</b>
<b>Norah Fuentes</b>
<b>Bailey Freeman</b>
<b>Lyric Shields</b>
<b>Chad Freeman</b>
<b>Beatrice Kline</b>
<b>Shayna Jennings</b>
<b>Clayton Potter</b>
<b>Karson Cowan</b>

E então podemos desenvolver esse exemplo:

<?php

$stream = fopen('nomes.txt', 'rb');

stream_filter_append($stream, 'string.toupper');

fpassthru($stream);

fclose($stream);

O retorno será todos os nomes em maiúsculo:

<B>THALIA BISHOP</B>
<B>MCKENNA CARRILLO</B>
<B>WESLEY CURTIS</B>
<B>IRENE NEAL</B>
<B>NORAH FUENTES</B>
<B>BAILEY FREEMAN</B>
<B>LYRIC SHIELDS</B>
<B>CHAD FREEMAN</B>
<B>BEATRICE KLINE</B>
<B>SHAYNA JENNINGS</B>
<B>CLAYTON POTTER</B>
<B>KARSON COWAN</B>

Tudo o que precisamos fazer é usar a função stream_filter_append() que adiciona um filtro na “stack” de filtros a serem executados naquela stream.

O filtro nativo string.strip_tags que aplica a função striptags() foi marcado como deprecated no PHP 7.3. Por esse motivo, vamos recriá-lo a partir de um filtro personalizado:

<?php

class StripTagsFilter extends \PHP_User_Filter
{
    public function filter($in, $out, &$consumed, $closing) : int
    {
        while ($bucket = stream_bucket_make_writeable($in)) {
            $bucket->data = strip_tags($bucket->data);

            stream_bucket_append($out, $bucket);
        }

        return PSFS_PASS_ON;
    }
}

// Registra o filtro
stream_filter_register('striptags', StripTagsFilter::class);

$stream = fopen('nomes.txt', 'rb');

stream_filter_append($stream, 'striptags');
stream_filter_append($stream, 'string.toupper');

fpassthru($stream);

fclose($stream);

O mais importante a ser notado é que a classe que criamos precisa estender \PHP_User_Filter. Os filtros trabalham com os dados no estilo de uma bucket brigade, onde cada bucket recuperado é passado de uma brigade para a outra. A função stream_bucket_make_writeable() faz algo parecido com o que a fgets() faz, só que retorna um objeto bucket que na realidade não possui um tipo específico, é um objeto genérico da stdclass. É no atributo data do bucket onde ficam os dados, é nele que fazemos as transformações que queremos. Na prática, estamos transformando os dados dos buckets da brigade $in e depois repassando esses buckets para a brigade $out. A constante PSFS_PASS_ON informa que terminamos de enviar os buckets para a brigade $out.

Você deve ter percebido que é bem confuso. O sistema de streams do PHP é algo muito poderoso e incrível. O problema está na API, em especial na de filtros, que é uma das mais confusas do PHP e não existe boa documentação para ela. Muitas vezes precisamos trabalhar com a intuição do que está acontecendo por debaixo dos panos. Por exemplo, o fato de ter que usar a função stream_bucket_make_writeable() dentro de um loop é uma recomendação da documentação do PHP, pois subentende-se que vários buckets possam ser recuperados de uma brigade, mas até hoje, em todos os exemplos que pude testar, não percebi tal necessidade.

Desenvolvedor PHP Júnior
Formação: Desenvolvedor PHP Júnior
Nesta formação você aprenderá todos os fundamentos necessário para iniciar do modo correto com a linguagem PHP, uma das mais utilizadas no mercado. Além dos conceitos de base, você também conhecerá as características e a sintaxe da linguagem de forma prática.
CONHEÇA A FORMAÇÃO

Uma abordagem moderna para se trabalhar com filtros de streams

Inspirado pela php-stream-filter, criei uma library chamada simple-stream-filter que abstrai a criação de filtros de streams, fazendo que com foquemos apenas na transformação do dado.

Vamos utilizá-la? No raiz do projeto, execute:

$ composer require kennedytedesco/simple-stream-filter

O mesmo exemplo anterior do filtro personalizado que criamos pode ser reescrito para:

<?php

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

use KennedyTedesco\SimpleStreamFilter\Filter;

$stream = fopen('nomes.txt', 'rb');

Filter::append($stream, static function($chunk = null) {
    return strip_tags($chunk);
});

fpassthru($stream);

fclose($stream);

Mais de um filtro pode ser aplicado:

<?php

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

use KennedyTedesco\SimpleStreamFilter\Filter;

$stream = fopen('nomes.txt', 'rb');

Filter::append($stream, static function($chunk = null) {
    return strip_tags($chunk);
});

Filter::append($stream, static function($chunk = null) {
    return strtoupper($chunk);
});

fpassthru($stream);

fclose($stream);

Se o desejo é aplicar o filtro apenas na leitura:

Filter::append($stream, static function($chunk = null) {
    return strip_tags($chunk);
}, STREAM_FILTER_READ);

Passe no terceiro parâmetro a constante STREAM_FILTER_READ (algo que será explicado mais abaixo).

Ou, como estamos usando funções nativas do PHP, também podemos simplesmente escrever assim:

<?php

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

use KennedyTedesco\SimpleStreamFilter\Filter;

$stream = fopen('nomes.txt', 'rb');

Filter::append($stream, 'strip_tags');

Filter::append($stream, 'strtoupper');

fpassthru($stream);

fclose($stream);

Dessa forma, abstraímos toda a dificuldade e verbosidade de se trabalhar com filtros básicos de streams.

Filtros na escrita de streams

Filtros também podem ser utilizados na escrita de streams. Crie um arquivo teste.txt vazio, no mesmo diretório dos exemplos. E então teste:

<?php

$stream = fopen('teste.txt', 'r+b');

stream_filter_append($stream, 'string.toupper');

fwrite($stream, 'hello world!');

Ao abrir o arquivo, você verá:

HELLO WORLD!

Mas, se a intenção é registrar um filtro apenas na escrita de uma stream, a stream_filter_append() possui um terceiro parâmetro que aceita as constantes:

  • STREAM_FILTER_WRITE
  • STREAM_FILTER_READ
  • STREAM_FILTER_ALL

A primeira marcará que o filtro só será aplicado na escrita. A segunda indicará que apenas na leitura. Se utilizarmos a constante STREAM_FILTER_READ :

<?php

$stream = fopen('teste.txt', 'r+b');

stream_filter_append($stream, 'string.toupper', STREAM_FILTER_READ);

fwrite($stream, 'hello world!');

Será escrito no arquivo:

hello world!

Não aplicou o string.toupper, ademais, exigimos que ele só fosse aplicado na leitura da stream. Se a ideia é permitir o filtro tanto pra escrita quanto pra leitura, o terceiro parâmetro pode ser ignorado ou pode se usar a terceira constante STREAM_FILTER_ALL que é o mesmo que declarar STREAM_FILTER_WRITE | STREAM_FILTER_READ, ou seja, libera tanto para escrita quanto pra leitura.

O meta-wrapper php://filter

Agora já vimos como usar e criar filtros, veremos sobre o meta-wrapper php://filter, apesar de não ser muito comum o seu uso, ele permite a aplicação de filtros junto com a abertura da stream. Útil, por exemplo, pra usar com funções como a file_get_contents() onde o registro tradicional de filtros não é aplicável, pois ela não retorna uma stream.

A sintaxe desse meta-wrapper é:

php://filter/string.toupper/resource=http://localhost:8000/nomes.txt

Informamos depois de filter/ os nomes dos filtros que serão aplicados nessa stream. Exemplo:

<?php

class StripTagsFilter extends \PHP_User_Filter
{
    public function filter($in, $out, &$consumed, $closing) : int
    {
        while ($bucket = stream_bucket_make_writeable($in)) {
            $bucket->data = strip_tags($bucket->data);

            stream_bucket_append($out, $bucket);
        }

        return PSFS_PASS_ON;
    }
}

stream_filter_register('striptags', StripTagsFilter::class);

echo file_get_contents('php://filter/string.toupper|striptags/resource=nomes.txt');

Recuperamos os nomes já aplicando os filtros string.toupper e striptags (aquele registrado por nós). Da mesma forma, esse meta-wrapper pode ser usado para escrever em um arquivo, por exemplo:

<?php

file_put_contents(
    'php://filter/string.toupper/resource=teste-2.txt', 
    'hello world!'
);

O que ler a seguir?

Concluindo

Entender o essencial sobre streams pode ser uma poderosa ferramenta para resolver algumas das complexidades e dificuldades de se trabalhar com arquivos e sockets.

Recomendo também a leitura do artigo sequência deste: Stream Wrappers personalizados no PHP.

Até a próxima!

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.

PHP Básico
Curso de PHP Básico
CONHEÇA O CURSO

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.

PHP Intermediário
Curso de PHP Intermediário
CONHEÇA O CURSO

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.
PHP Avançado
Curso de PHP Avançado
CONHEÇA O CURSO

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.
Symfony - Fundamentos
Curso de Symfony - Fundamentos
CONHEÇA O CURSO

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.

PHP Básico
Curso de PHP Básico
CONHEÇA O CURSO

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.

PHP Intermediário
Curso de PHP Intermediário
CONHEÇA O CURSO

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).

PHP Avançado
Curso de PHP Avançado
CONHEÇA O CURSO

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.

Desenvolvedor Laravel Full-Stack
Formação: Desenvolvedor Laravel Full-Stack
Nesta formação você aprenderá desenvolver aplicações PHP usando o framework Laravel com maestria. Ao final desta formação, você terá condições de trabalhar em grandes aplicações web ou APIs integradas com diversos serviços, tudo isso utilizando as melhores práticas do mercado.
CONHEÇA A FORMAÇÃO

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.

Symfony - Fundamentos
Curso de Symfony - Fundamentos
CONHEÇA O CURSO

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.

Laravel - Framework PHP (Parte 3/3)
Curso de Laravel - Framework PHP (Parte 3/3)
CONHEÇA O CURSO

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.

Desenvolvedor Laravel Full-Stack
Formação: Desenvolvedor Laravel Full-Stack
Nesta formação você aprenderá desenvolver aplicações PHP usando o framework Laravel com maestria. Ao final desta formação, você terá condições de trabalhar em grandes aplicações web ou APIs integradas com diversos serviços, tudo isso utilizando as melhores práticas do mercado.
CONHEÇA A FORMAÇÃO

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 3 - Framework PHP
Curso de CodeIgniter 3 - Framework PHP
CONHEÇA O CURSO

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 - Fundamentos
Curso de Symfony - Fundamentos
CONHEÇA O CURSO

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 Expressive - Microframework PHP
Curso de Zend Expressive - Microframework PHP
CONHEÇA O CURSO

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! =)