Posts da Tag: PHP - Blog da TreinaWeb

PHP

Conhecendo o CodeIgniter, framework PHP

O Codelgniter é um poderoso framework PHP dentre diversos outros já existentes. Criado para desenvolvedores que precisam de um conjunto de ferramentas simples para a criação de aplicativos web completos, o CodeIgniter é uma excelente alternativa para o desenvolvimento de projetos utilizando o PHP.

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

CodeIgniter 3 - Framework PHP
Curso de CodeIgniter 3 - Framework PHP
CONHEÇA O CURSO

Relembrando Framework

Aqui no blog já possuímos um artigo que aborda “Para que serve um Framework”, mas em palavras mais simples, o framework é um facilitador no desenvolvimento de diversas aplicações. Sem dúvidas, sua utilização poupa tempo e custos para quem 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.

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

De volta ao Codelgniter

Multiplataforma e de código aberto, o Codelgniter é um framework ideal para desenvolvedores iniciantes, por possuir a simplicidade atrelada ao desenvolvimento.

Escrito em PHP, foi desenvolvido sobre o paradigma da programação Orientada a Objetos e sob o padrão de arquitetura de software MVC.

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.

Funcionamento do MVC

  • Model: Parte lógica da aplicação que gerencia o comportamento dos dados, ou seja, todos os seus recursos (consultas ao BD, validações, notificações, etc). 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 parte da aplicação visível 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, interpretando as entradas do mouse e teclado do usuário, comandando a visão e o modelo para se alterarem de forma apropriada.
CodeIgniter 3 - Framework PHP
Curso de CodeIgniter 3 - Framework PHP
CONHEÇA O CURSO

Características do Codelgniter

Possuindo uma ótima performance e com um conjunto de arquivos relativamente pequeno, o Codelgniter pode ser uma ótima opção para o desenvolvimento de projetos por possuir diversas características, como veremos abaixo:

  • Framework com estrutura simples;
  • Ótimo desempenho;
  • Soluções simples sem complexidade;
  • Boas práticas de segurança;
  • Excelente documentação;
  • Baixa necessidade de configurações;

Podemos então concluir…

Como vimos neste artigo, o CodeIgniter é uma excelente alternativa aos diversos outros frameworks PHP existentes no mercado. Possui uma estrutura simples e eficaz, o que o torna ainda mais atrativo para desenvolvedores iniciantes.

No site do Codelgniter é possível encontrar toda documentação necessária para sua utilização, além de links para canais de comunicação entre a comunidade.


PHP

O que é Lumen?

Assim como o Slim e o Flask, o Lumen é um micro-framework desenvolvido por Taylor Otwell (que também é desenvolvedor do Laravel), lançado em abril de 2015 sobre a licença MIT, escrito em PHP e hospedado no GitHub.

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

Relembrando Micro-Framework…

Um Micro-Framework são Frameworks modularizados que possuem uma estrutura inicial muito mais simples quando comparado a um Framework convencional.

Pense em um Micro-Framework como uma peça de lego. Inicialmente, um projeto criado com o micro-framework possui apenas o básico para funcionar, (normalmente, sistema de rotas). Porém, ao decorrer do projeto, podem haver necessidades para utilização de outros recursos como, conexão de banco de dados, sistemas de templates, envio de email, etc. A partir desta necessidade, novas bibliotecas são “encaixadas” no projeto, como uma estrutura de lego.

Exemplificando Micro-Framework

De volta ao Lumen

Caracterizado como um dos Micro-Frameworks mais rápidos existentes atualmente, o Lumen é uma excelente opção na criação de APIs REST e no desenvolvimento de aplicações de microsserviços.

Derivado do Laravel, o Lumen possui uma menor quantidade de recursos nativos quando comparado ao “seu irmão mais velho”. Porém, por permitir que novos recursos de terceiros possam ser adicionados através do Composer e permitir habilitar os componentes nativos do Laravel como o próprio Eloquent ORM, o mesmo torna-se uma excelente opção para o desenvolvimento de vários tipos de projetos.

Características do Lumen

Assim como a maioria dos Micro-Frameworks, o Lumen tem como principal característica a sua simplicidade, mas esta não é a única, como podemos ver abaixo:

  • Por possuir apenas o necessário para o desenvolvimento, a simplicidade do Lumen é um ponto positivo para sua utilização. Deste modo, torna-se mais simples que comparado a frameworks maiores já que sua arquitetura é muito mais simples;
  • Possui maior rapidez no desenvolvimento já que o desenvolvedor irá se preocupar apenas com o necessário para o seu projeto. Desta forma, anula configurações desnecessárias para a aplicação;
  • Diante da sua arquitetura mais simplista, tendem a ser menores e mais leves quando comparado a outros frameworks;
  • Permite também a criação de aplicações mais robustas. Por ser um micro-framework totalmente personalizável, permitindo assim, caso necessário, a criação de uma arquitetura mais definida, entre outros.
Silex - Framework PHP
Curso de Silex - Framework PHP
CONHEÇA O CURSO

Exemplo de uma aplicação Lumen

Abaixo podemos verificar um exemplo de uma aplicação Lumen, e notar a sua simplicidade:

<?php

$app->get('user/{id}', function($id) {
    return User::findOrFail($id);
});

Basicamente, a aplicação acima recebe uma requisição do tipo GET para a rota “/user/{id}” e retornar o usuário que possui o id enviado como parâmetro.

Podemos concluir que…

Como vimos durante todo o artigo, o Lumen é uma excelente escolha para o desenvolvimento de APIs REST e microsserviços, já que sua estrutura mais simples permite focar apenas no necessário e utilizar os recursos essenciais para sua criação.

No site do Lumen podemos verificar toda sua documentação, comunidade e tudo sobre a ferramenta.


PHP

O que é Twig?

Licenciado sob a licença BSD, o Twig é um template engine para projetos PHP muito utilizado em todo o mundo. Basicamente, o Twig serve para incluir informações e códigos PHP em páginas HTML para facilitar a criação de templates em projetos.

Inspirado no Jinja2, o Twig é o template engine padrão do Symfony, um dos maiores frameworks PHP do mercado.

Symfony - Template Engine Twig
Curso de Symfony - Template Engine Twig
CONHEÇA O CURSO

Características

A principal funcionalidade de um template engine (e do twig) é permitir que linguagens de programação possam ser incorporadas em páginas HTML, facilitando, assim, todo o processo de criação dos templates. Com isso, permite que os programadores possam utilizar estruturas de condição, estruturas de repetição, herança e diversos outros recursos presentes apenas nas linguagens de programação em páginas HTML.

O Twig possui diversas características, sendo que as principais podemos ver abaixo:

  • Permite o uso de estruturas de condição e repetição em páginas HTML;
  • Possibilita utilizar herança em templates, garantindo maior reaproveitamento de código;
  • Permite exibir o conteúdo de diferentes tipos de variáveis PHP em páginas HTML;
  • Sistema de blocos de templates, dentre outros.

Como funciona o Twig

Basicamente, quando criamos um template com o Twig e incorporamos código PHP nas páginas HTML, ele se encarrega de compilar os templates para código PHP totalmente otimizado, que depois será convertido em código PHP, garantindo maior velocidade quando comparado ao código PHP puro.

Sendo assim, um código utilizando o Twig como podemos ver abaixo é convertido em um HTML:

<ul>
{% for user in users %}
  <li><a href="{{ user.url }}">{{ user.username }}</a></li>
{% endfor %}
</ul>

<ul>
    <li><a href="http://url_da_pagina/1"> João </a></li>
    <li><a href="http://url_da_pagina/2"> Maria </a></li>
    <li><a href="http://url_da_pagina/3"> José </a></li>
    <li><a href="http://url_da_pagina/4"> Neuza </a></li>
    <li><a href="http://url_da_pagina/5"> Geraldo </a></li>
</ul>

Vale lembrar que sua sintaxe é muito mais simples e intuitiva que o PHP puro. O mesmo código acima escrito em PHP seria da seguinte forma:

<ul>
<?php foreach($users as $user): ?>
  <li><a href="{{ user.url }}"><?php echo $user['username'] ?></a></li>
<?php endfor; ?>
</ul>

Com isso, podemos notar o quão poderosa é essa ferramenta. Com ela podemos utilizar os principais recursos das linguagens de programação para a criação de páginas HTML.

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

Quem utiliza?

O Twig, como dito anteriormente, é o template engine de um dos principais frameworks PHP do mundo, o Symfony. Ele é incorporado ao Symfony por padrão, permitindo a criação de páginas HTML em conjunto com os recursos do PHP.

Além disso, pode ser incorporado a diversos outros frameworks, como o Laravel ou utilizando o PHP puro.

Conclusão

Como vimos neste artigo, o Twig é um ótimo template engine e que permite a criação de páginas HTML utilizando os principais recursos do PHP. Ele facilita a criação de páginas HTML em conjunto com código PHP utilizando uma sintaxe simples e intuitiva.


PHP

O que é o Composer?

O Composer é o gerenciador de dependências mais utilizado do PHP, que com o passar dos anos foi ganhando cada vez mais espaço e se tornando dia após dia mais indispensável ao desenvolvedor.

Inspirado no npm do Node e no bundler do Ruby, o Composer foi desenvolvido por Nils Adermann e Jordi Boggiano e foi lançado no ano de 2012 sobre a licença MIT.

O Composer fornece recursos de carregamento automático para bibliotecas para facilitar o uso de código de terceiros, tornando o desenvolvimento do projeto mais simples e permitindo com que o desenvolvedor foque apenas em seu código, não no uso de código de terceiros.

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

Basicamente, ele permite que você declare quais bibliotecas são necessárias para a execução de um projeto e ele gerencia (instala, atualiza ou remove) para você.

Afinal, o que é um Gerenciador de Dependências?

Com o objetivo de gerenciar bibliotecas externas em projetos, um gerenciador de dependência nada mais é que um facilitador para instalação, remoção e atualização de pacotes externos em projetos.

Como funciona o Composer

Como dito anteriormente, basicamente, o Composer permite que o desenvolvedor determine quais dependências são necessárias para um projeto funcionar. A partir daí, é ele quem faz o “gerenciamento” destas dependências, realizando a instalação, atualização e remoção, removendo essa responsabilidade do desenvolvedor.

Para isso, o composer utiliza um arquivo chamado composer.json. É neste arquivo que o desenvolvedor especifica as bibliotecas necessárias para o projeto, como podemos ver abaixo:

{
    "require": {
        "monolog/monolog": ">2.1.0",
        "nome_do_outro_pacote": "numero_da_versao"
    }
}

No arquivo descrito acima, podemos definir as dependências que este projeto necessita para funcionar. Estas dependências são definidas na configuração require.

No exemplo acima, o arquivo composer.json define que o projeto necessita do pacote PHP na versão 7.0. Esta configuração permite definir quantos pacotes forem necessários, mantendo apenas o formato especificado (nome do pacote e número da versão).

Ao criar o arquivo, basta utilizar o comando php composer.phar install para que o composer instale as dependências definidas no arquivo composer.json no projeto.

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

Funcionalidades do Composer

O Composer possui diversas funcionalidades que facilitam o dia do desenvolvedor. Dentre elas, as principais são:

  • Instalação de novas dependências no projeto;
  • Atualização das dependências já instaladas no projeto;
  • Remoção de dependências instaladas no projeto;
  • Autoload para os arquivos do projeto, além dos pacotes de terceiro;
  • Execução de scripts;
  • Plugins para estender o comportamento padrão;
  • Permite determinar dependências que serão instaladas no modo de desenvolvimento ou em modo de produção.

Concluindo

Muito utilizado por desenvolvedores PHP, o Composer é uma excelente ferramenta para gerenciar as dependências de um projeto. Muito utilizado no mercado e em projetos pessoais em geral, o Composer facilita (e muito) a vida de um desenvolvedor PHP, permitindo que ele foque apenas no principal: o desenvolvimento.

No site do Composer é possível encontrar toda sua documentação, download da ferramenta, entre outras informações.


PHP

Principais IDEs para desenvolvimento PHP

O que é uma IDE (Ambiente de Desenvolvimento Integrado)?

IDE ou Integrated Development Environment (Ambiente de Desenvolvimento Integrado) é um software que auxilia no desenvolvimento de aplicações, muito utilizado por desenvolvedores, com o objetivo de facilitar diversos processos (ligados ao desenvolvimento), que combinam ferramentas comuns em uma única interface gráfica do usuário (GUI). Neste artigo veremos as principais IDEs para desenvolvimento PHP.

No artigo “O que é uma IDE”, exploramos algumas características, vantagens e desvantagens em sua utilização. De maneira simplificada, podemos dizer que, para o desenvolvedor, é uma forma de criar aplicações de maneira mais rápida, uma vez que estas IDEs auxiliam em todo o processo de desenvolvimento de uma aplicação, provendo diversos benefícios, como a análise de todo o código a ser escrito para identificar bugs causados por um erro de digitação, autocompletam trechos de códigos, e etc.

Abaixo veremos as principais IDEs para desenvolvimento PHP.

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

Principais IDEs para desenvolvimento PHP

Eclipse

Logo do Eclipse

Lançada em 2001 pela IBM, sobre a licença EPL (Eclipse Public Licence), o Eclipse é uma IDE para desenvolvimento em PHP que também suporta diversas outras linguagens apenas com a instalação de plugins (C/C++, Java, Kotlin, Python, entre outras).

Multiplataforma, é possível realizar seu download em diferentes sistemas operacionais como windows, linux e macOS.

Mesmo sendo muito conhecida para o desenvolvimento Java, o Eclipse possui um projeto chamado “PHP Development Tools“, uma versão da IDE com suporte completo ao PHP, tendo como principais features:

  • Validação de sintaxe;
  • Navegação de código;
  • Formatação de código, dentre outros.

O Eclipse é uma excelente IDE, muito utilizada no mercado. Seu uso facilita a criação de aplicações PHP.

O download do Eclipse poderá ser realizado em seu próprio site, já o download do PHP Development Tools, no site secundário.

Slim - Microframework PHP
Curso de Slim - Microframework PHP
CONHEÇA O CURSO

PHPStorm

LOGO PHPSTORM

Disponibilizada oficialmente em 2009 pela JetBrains, o PHPStorm é uma das IDEs mais utilizadas por desenvolvedores PHP.

É uma IDE rica de funcionalidades que facilitam a implementação de aplicações PHP. O PHPStorm também provê recursos para ajudar o desenvolvedor, como um editor de código SQL, plugins para JavaScript, entre outros.

Dentre suas principais vantagens, podemos citar:

  • Fornece análise de código;
  • Multiplataforma;
  • Suporta diversos frameworks PHP, como Laravel, Zend Framework;
  • Possui suporte a testes unitários integrado;
  • Suporte para VCS;
  • Possui suporte a bancos de dados SQL;
  • Compatível com o HTML5;
  • Preenchimento de código inteligente;
  • Verificação dinâmica de erros, entre outros.

Além de todas essas vantagens, o PHPStorm conta com desenvolvimento multitecnologias, onde, além do PHP, oferece suporte para CoffeeScript, TypeScript, JavaScript, SQL, HTML/CSS, linguagens de modelo, AngularJS, Node.js e muitas outras.

O download do PHPStorm é feito em seu próprio site, onde é possível acompanhar todas as suas novidades, recursos, suporte e muito mais.

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

Visual Studio Code

Visual Studio Code

Apesar de ser um editor de textos para desenvolvedores, o Visual Studio Code (ou vscode), é tão completo que é frequentemente confundida como uma IDE.

Criada pela Microsoft, o vscode é um editor de código open source, multiplataforma e com diversos recursos para o desenvolvimento PHP.

Possui suporte nativo ao JavaScript, TypeScript, JSON, HTML, CSS e outras tecnologias, também é possível instalar plugins para melhorar o suporte para outras tecnologias, como o PHP.

Muito utilizado na comunidade, o VScode, mesmo não sendo uma IDE, é tão poderosa quanto.

Para instalar o vscode, é só acessar sua página oficial e realizar seu download.


PHP

API de Reflexão do PHP

O PHP implementa uma API de reflexão que permite com que façamos “engenharia reversa” para extrair e até mesmo alterar características internas de classes, interfaces, métodos e até mesmo de extensões, tudo isso em tempo de execução.

Os casos de uso para reflexão não costumam ser tão comuns no dia a dia de desenvolvimento, mas podemos destacar alguns:

  • Core de frameworks (para alguns comportamentos especiais como, por exemplo, no container de injeção de dependência para saber se determinada classe pode ser instanciada ou não);
  • Para testes (frameworks de teste usam principalmente para mocks);
  • Extrair informações de classes e gerar documentação;
  • Criar hydrators que extraiam/alimentem objetos com dados de forma dinâmica;
  • Extrair comentários de classes e criar meta-dados estendendo o comportamento delas (anotações);

Tudo sobre a API de reflexão você pode consultar direto na documentação oficial do PHP. Nesse artigo vou passar os aspectos que considero principais.

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

Um exemplo elementar pra dar a ideia do que é possível extrair:

<?php

/** @psalm-immutable */
final class Email
{
    public string $email;

    public function __construct(string $email)
    {
        $this->email = $email;
    }
}

$reflectionClass = new ReflectionClass('Email');

echo $reflectionClass->getName() . PHP_EOL;
echo ($reflectionClass->isFinal() ? 'Final' : 'Not final') . PHP_EOL;
echo $reflectionClass->getDocComment() . PHP_EOL;
echo $reflectionClass->getConstructor()->getNumberOfParameters() . PHP_EOL;

echo PHP_EOL . 'Property:' . PHP_EOL . PHP_EOL;

/** @var ReflectionProperty $property */
$property = $reflectionClass->getProperties()[0];
echo $property->getName() . PHP_EOL;
echo ($property->isPrivate() ? 'public' : 'not public') . PHP_EOL;
echo $property->getType() . PHP_EOL;

O resultado da execução desse exemplo:

Email
Final
/** @psalm-immutable */
1

Property:

email
not public
string

A ReflectionClass é responsável por extrair os dados de uma classe. Extraímos o nome da classe, se ela é final, o comentário associado a ela e o número de argumentos do seu construtor. O método getConstructor() retorna uma instância de ReflectionMethod. Depois, usando getProperties() , obtemos um array com os atributos da classe, mas na forma de instâncias de ReflectionProperty, que provê outros métodos de acesso.

Se quisermos dar uma espécie de var_dump() em uma classe:

<?php

/** @psalm-immutable */
final class Email
{
    public string $email;

    public function __construct(string $email)
    {
        $this->email = $email;
    }
}

Reflection::export(new ReflectionClass('Email'));

O resultado será:

/** @psalm-immutable */
Class [ <user> final class Email ] {
  @@ /home/kennedy/Documents/www/php-reflection/index.php 4-12

  - Constants [0] {
  }

  - Static properties [0] {
  }

  - Static methods [0] {
  }

  - Properties [1] {
    Property [ <default> public $email ]
  }

  - Methods [1] {
    Method [ <user, ctor> public method __construct ] {
      @@ /home/kennedy/Documents/www/php-reflection/index.php 8 - 11

      - Parameters [1] {
        Parameter #0 [ <required> string $email ]
      }
    }
  }
}

Podemos extrair diretamente as informações de um método sem precisar usar a ReflectionClass:

<?php

final class Node
{
    private Node $next;

    private function next(): Node
    {
        return $this->next;
    }
}

$reflectionMethod = new ReflectionMethod('Node', 'next');
echo $reflectionMethod->getName() . PHP_EOL; // next
echo ($reflectionMethod->isPrivate() ? 'private' : 'not private') . PHP_EOL; // private
echo $reflectionMethod->getReturnType() . PHP_EOL; // Node

Da mesma forma que ReflectionClass, também temos a ReflectionFunction:

<?php

function foo(int $bar) : int {
    return $bar + 1;
}

$reflectionFunction = new ReflectionFunction('foo');

echo $reflectionFunction->getName() . PHP_EOL; // foo
echo $reflectionFunction->getReturnType() . PHP_EOL; // int

$parameter = $reflectionFunction->getParameters()[0];
echo $parameter->getName() . PHP_EOL; // bar
echo $parameter->getType() . PHP_EOL; // int

var_dump($reflectionFunction->getClosure()); // class Closure#2 (1) {

Outra classe muito importante do “combo” é a ReflectionObject, que trabalha diretamente em uma instância de objeto:

<?php

final class Node
{
    private int $value;
    private ?Node $next;

    public function __construct(int $value, ?Node $next = null)
    {
        $this->value = $value;
        $this->next = $next;
    }

    private function next(): Node
    {
        return $this->next;
    }

    public function value(): int
    {
        return $this->value;
    }
}

$node = new Node(1, new Node(2));

$reflectionObject = new ReflectionObject($node);
echo $reflectionObject->getName() . PHP_EOL; // Node
echo $reflectionObject->getConstructor()->getNumberOfParameters() . PHP_EOL; // 2
echo $reflectionObject->getMethod('next')->getReturnType() . PHP_EOL; // Node
echo $reflectionObject->getMethod('value')->getReturnType() . PHP_EOL; // int

Existem outras classes e elas possuem outros vários métodos a serem explorados. Mas o que vimos até aqui é o essencial para que avancemos um pouco mais em exemplos palpáveis, do mundo real.

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

Um caso prático pra uso de reflexão

Criaremos uma classe transporter base. Um transporter é um conceito para transacionar dados (do input do usuário, por exemplo) entre objetos. Se você já ouviu falar de DTO (Data Transfer Objects), é basicamente isso, mas com outro nome, só pra causar confusão mesmo. 😛

Mas deixando a ladainha de lado, show me the code:

<?php

declare(strict_types=1);

abstract class Transporter
{
    public function __construct(array $properties = [])
    {
        $reflection = new ReflectionObject($this);
        foreach ($properties as $name => $value) {
            $property = $reflection->getProperty($name);
            if ($property->isPublic() || !$property->isStatic()) {
                $this->$name = $value;
            }
        }
    }

    public function toArray(): array
    {
        $reflection = new ReflectionObject($this);
        return \array_map(
            function (ReflectionProperty $property) {
                return [
                    $property->getName() => $property->getValue($this)
                ];
            },
            $reflection->getProperties(ReflectionProperty::IS_PUBLIC)
        );
    }
}

final class UserTransporter extends Transporter
{
    public int $age;
    public string $firstName;
    public string $lastName;
}

$transporter = new UserTransporter([
    'age' => 29,
    'firstName' => 'Kennedy',
    'lastName' => 'Parreira',
]);

echo $transporter->age . PHP_EOL;
echo $transporter->firstName . PHP_EOL;
echo $transporter->lastName . PHP_EOL;

var_dump($transporter->toArray());

O resultado:

29
Kennedy
Parreira
/home/kennedy/Documents/www/php-reflection/index.php:50:
array(3) {
  [0] =>
  array(1) {
    'age' =>
    int(29)
  }
  [1] =>
  array(1) {
    'firstName' =>
    string(7) "Kennedy"
  }
  [2] =>
  array(1) {
    'lastName' =>
    string(8) "Parreira"
  }
}

Esse exemplo usou basicamente o que já tínhamos visto anteriormente. Ele permite que recebamos os dados via array (no construtor) e então alimenta os atributos do objeto dinamicamente. O método toArray() itera nos atributos públicos e não estáticos da classes e os retorna em um array.

Queue jobs do Laravel

Outro caso de uso interessante de se passar aqui, pra mostrar como reflexão pode ser útil em código de framework, refere-se às Queue Jobs do Laravel:

<?php

namespace App\Jobs;

use App\Podcast;
use App\AudioProcessor;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;

class ProcessPodcast implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $podcast;

    public function __construct(Podcast $podcast)
    {
        $this->podcast = $podcast;
    }

    public function handle(AudioProcessor $processor)
    {
        // Process uploaded podcast...
    }
}

Veja que essa classe implementa a interface ShouldQueue. No Dispacher de jobs tem um método que recebe o nome da classe pra decidir se ela deve entrar na fila ou não, e esse método usa reflexão para verificar se a classe implementa a interface ShouldQueue:

protected function handlerShouldBeQueued($class)
{
    try {
        return (new ReflectionClass($class))->implementsInterface(
            ShouldQueue::class
        );
    } catch (Exception $e) {
        return false;
    }
}

Nesse contexto específico é melhor usar reflexão que futilmente gerar uma instância da classe para depois verificar se o objeto gerado implementa a requerida interface. Se fôssemos implementar instanciando a classe, ficaria mais ou menos assim:

protected function handlerShouldBeQueued($class)
{
    $object = new $class();

    return $object instanceof ShouldQueue;
}

(E note que nesse protótipo nem nos preocupamos com as possíveis dependências que o construtor de $class poderia precisar receber).

Anotações

Você já deve ter visto libraries/componentes que estendem seus comportamentos a partir de anotações. Uma delas é a Annotations, para Laravel, que permite definir comportamentos diretamente por anotações nas classes, por exemplo:

/**
 * @Middleware("guest", except={"logout"}, prefix="/your/prefix")
 */
class AuthController extends Controller
{
    /**
     * @Get("logout", as="logout")
     * @Middleware("auth")
     */
    public function logout()
    {
        $this->auth->logout();

        return redirect(route('login'));
    }
}

E usa-se reflexão para obter essas anotações.

Palavras finais

É bem provável que no dia a dia de desenvolvimento no código de aplicação não usemos reflexão, mas são uma importante ferramenta, principalmente para libraries e frameworks.

Até a próxima!

Desenvolvedor PHP
Formação: Desenvolvedor PHP
Nesta formação você aprenderá todos os conceitos da linguagem PHP, uma das mais utilizadas no mercado. Desde de conceitos de base, até características mais avançadas, como orientação a objetos, práticas de mercado, integração com banco de dados. Ao final, você terá conhecimento para desenvolver aplicações PHP usando as práticas mais modernas do mercado.
CONHEÇA A FORMAÇÃO

PHP

Generators no PHP

iterGenerators foram adicionados no PHP na versão 5.5 (meados de 2013) e aqui estamos nós, anos depois, falando sobre eles. É que esse ainda não é um assunto muito difundido, digo, não é trivial encontrar casos e mais casos de uso para eles no contexto padrão de desenvolvimento para o PHP. Normalmente o uso deles fica abstraído em libraries e frameworks.

Mas, antes de tudo, é importante entendermos o que são Iterators, ademais, Iterators e Generators são assuntos intrinsecamente relacionados.

Para isso, recomendo a leitura do artigo: Iterators no PHP

Todos os exemplos desse artigo estão disponíveis no repositório: https://github.com/KennedyTedesco/php-iterators-generators

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

O que é um Generator?

Numa explicação acessível, Generators são uma forma prática de se implementar Iterators. Isso quer dizer que, com Generators, podemos implementar complexos Iterators sem que precisemos criar objetos, implementar interfaces e tudo mais que vimos anteriormente. Generators dão para uma função a capacidade de retornar uma sequência de valores.

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

Vejamos o exemplo mais elementar possível de uma função generator:

<?php

declare(strict_types=1);

function getBooks(): Generator
{
    yield 'Book 1';
    yield 'Book 2';
    yield 'Book 3';
    yield 'Book 4';
    yield 'Book 5';
}

foreach (getBooks() as $book) {
    echo $book . \PHP_EOL;
}

O resultado:

Book 1
Book 2
Book 3
Book 4
Book 5

Sempre que uma função usar o operador yield ela vai retornar um objeto do tipo Generator. E, se Generator é um Iterator, logo, ele pode ser iterado.

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

<?php

declare(strict_types=1);

function getBooks(): Generator
{
    for ($i = 0; $i < 100; $i++) {
        yield "Book {$i}";
    }
}

foreach (getBooks() as $book) {
    echo $book . \PHP_EOL;
}

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

<?php

declare(strict_types=1);

function getBooks(): Generator
{
    for ($i = 0; $i < 10; $i++) {
        yield $i * 2 => "Book {$i}";
    }
}

foreach (getBooks() as $key => $value) {
    echo "{$key} -> {$value}" . \PHP_EOL;
}

O resultado:

0 -> Book 0
2 -> Book 1
4 -> Book 2
6 -> Book 3
8 -> Book 4
10 -> Book 5
12 -> Book 6
14 -> Book 7
16 -> Book 8
18 -> Book 9

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

Um dos benefícios de se usar generators está no baixo consumo de memória associado. Com um generator recuperamos a informação sob demanda, sem alocar toda a coleção na memória. Trabalhamos com um dado por vez no buffer.

É relativamente comum aplicações que precisam trabalhar em grandes massas de dados. Vamos emular uma situação onde possamos visualizar essa relação do uso eficiente de memória?

&lt;?php

declare(strict_types=1);

function getRecords(): array
{
    $records = [];
    for ($i = 0; $i  $record) {
        $new[] = "[$index] -&gt; {$record}";
    }
    return $new;
}

$registros = formatRecords(getRecords());
foreach ($registros as $registro) {
    echo $registro . \PHP_EOL;
}

echo 'Used memory: ~' . \round((\memory_get_peak_usage()/1024/1024), 2) . 'MB';

A getRecords() aloca 100k registros na memória e os retorna. A formatRecords() recebe uma coleção de dados, formata-os, aloca-os na memória (em um novo array) e então os retorna.

O resultado da memória consumida por esse script foi:

Used memory: ~16.78MB

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

&lt;?php

declare(strict_types=1);

function getRecords(): Generator
{
    for ($i = 0; $i  $record) {
        yield "[$index] -&gt; {$record}";
    }
}

$registros = formatRecords(getRecords());
foreach ($registros as $registro) {
    echo $registro . \PHP_EOL;
}

echo 'Used memory: ~' . \round((\memory_get_peak_usage()/1024/1024), 2) . 'MB';

O resultado:

Used memory: ~0.41MB

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

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

Lendo grandes arquivos

Vamos supor que precisamos ler um documento de texto linha a linha. Uma das formas tradicionais de lidar com isso seria assim:

 file.txt

O resultado dessa execução no meu ambiente foi:

Used memory: ~18.26MB

Se refatorarmos o exemplo para:

&lt;?php

declare(strict_types=1);

function getLines(string $filePath): Generator
{
    $file = \fopen($filePath, &#039;rb&#039;);

    while (!\feof($file)) {
        yield \fgets($file);
    }

    \fclose($file);
}

$lines = getLines(__DIR__.&#039;/file.txt&#039;);

foreach ($lines as $line) {
    echo $line;
}

echo PHP_EOL . &#039;Used memory: ~&#039; . \round((\memory_get_peak_usage()/1024/1024), 2) . &#039;MB&#039;;

O resultado será:

Used memory: ~0.41MB

Veja que a diferença do consumo de memória entre as duas abordagens é enorme.

Usando iterators e generators

No artigo sobre Iterators vimos sobre a interface SeekableIterator. Agora, vamos criar um Iterator personalizado que lê linha a linha de um arquivo e que também nos dará a opção de pular para uma linha arbitrária qualquer.

A classe se chama LineFileIterator:

filePath = $filePath;
        $this-&gt;rewind();
    }

    public function rewind(): void
    {
        $this-&gt;generator = $this-&gt;getGenerator();
    }

    public function current()
    {
        return $this-&gt;generator-&gt;current();
    }

    public function key(): int
    {
        return (int) $this-&gt;generator-&gt;key();
    }

    public function next(): void
    {
        $this-&gt;generator-&gt;next();
    }

    public function valid(): bool
    {
        return $this-&gt;generator-&gt;valid();
    }

    public function seek($position): void
    {
        while ($this-&gt;valid()) {
            if ($this-&gt;generator-&gt;key() === $position) {
                return;
            }

            $this-&gt;generator-&gt;next();
        }

        throw new OutOfBoundsException("Invalid position ($position)");
    }

    private function getGenerator(): Generator
    {
        $file = \fopen($this-&gt;filePath, 'rb');

        while (!\feof($file)) {
            yield \fgets($file);
        }

        \fclose($file);
    }
}

Usando o mesmo arquivo de texto que criamos anteriormente, podemos testar essa implementação assim:

seek(50_000);

// Get the current line
echo $iterator-&gt;current();

// Move to the next line (50.001)
$iterator-&gt;next();

// Get the current line
echo $iterator-&gt;current();

echo 'Used memory: ~' . \round((\memory_get_peak_usage()/1024/1024), 2) . 'MB';

Nesse nosso exemplo construímos um iterator que por debaixo dos panos usa generators para acessar sob demanda linhas de um arquivo.

Palavras finais

O uso de Generators é indicado e muitas vezes se faz necessário quando é preciso iterar uma grande coleção de dados. Eles são, inclusive, base para programação assíncrona e um framework que se destaca utilizando-os é o AMP:

$iterator = new Producer(function (callable $emit) {
    yield $emit(1);
    yield $emit(new Delayed(500, 2));
    yield $emit(3);
    yield $emit(4);
});

Inclusive, também escrevi um artigo sobre Corrotinas e código assíncrono em PHP usando Generators, recomendo a leitura.

E ainda sobre programação assíncrona, recomendo a leitura dos artigos:

Até a próxima!

Desenvolvedor PHP
Formação: Desenvolvedor PHP
Nesta formação você aprenderá todos os conceitos da linguagem PHP, uma das mais utilizadas no mercado. Desde de conceitos de base, até características mais avançadas, como orientação a objetos, práticas de mercado, integração com banco de dados. Ao final, você terá conhecimento para desenvolver aplicações PHP usando as práticas mais modernas do mercado.
CONHEÇA A FORMAÇÃO

PHP

Iterators no PHP

Esse é o primeiro artigo de dois sobre Iterators e Generators no PHP. Este será sobre Iterators, que é base para que possamos entender e usar Generators.

Todos os exemplos desse artigo e do artigo de Generators estão disponíveis nesse repositório: https://github.com/KennedyTedesco/php-iterators-generators

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

Iterators

Nós, como desenvolvedores PHP, estamos acostumados a usar arrays para trabalhar e iterar sobre listas. E se pudermos encapsular esse tipo de comportamento em um objeto? No contexto prático do PHP, Iterator é um mecanismo que permite que um objeto seja iterado e ele próprio fica no controle granular dessa iteração. Mas o seu uso não limita a essa “feature”.

Antes, entretanto, é importante destacar o conceito do que é “iterável” no PHP. Existe, inclusive, um pseudo-tipo chamado iterable que aceita arrays e objetos que implementem a interface Traversable, mas isso veremos mais pra frente.

Então, de forma prática, tudo o que você pode iterar num foreach é considerável iterável. E existe até uma curiosidade aqui, de algo não tão comum, é possível iterar até sobre os atributos públicos de um objeto arbitrário qualquer:

<?php

declare(strict_types=1);

function iterateAndPrint($list): void
{
    foreach ($list as $key => $value) {
        echo "{$key} -> {$value}\n";
    }
}

iterateAndPrint(['foo', 'bar', 'baz']);

iterateAndPrint(new class() {
    public int $foo = 1;
    public string $bar = 'bar';
    protected string $baz = 'baz';
});

O resultado:

0 -> foo
1 -> bar
2 -> baz
foo -> 1
bar -> bar

No caso desse exemplo iteramos tanto sobre um array quanto sobre uma instância de uma classe anônima. Então, podemos dizer que essas duas entidades são iteráveis.

A interface Iterator

Essa interface declara cinco métodos e, quando implementados, os objetos provenientes ganham a capacidade de serem iterados. Mais que isso, ganham o poder sobre o que e como iterar.

Iterator extends Traversable {
    abstract public current ( void ) : mixed
    abstract public key ( void ) : scalar
    abstract public next ( void ) : void
    abstract public rewind ( void ) : void
    abstract public valid ( void ) : bool
}
  • current() – Retorna elemento atual;
  • key() – Obtêm a chave atual;
  • next() – Avança o cursor para o próximo elemento;
  • rewind() – Retorna o cursor para o início (ponto zero);
  • valid() – Retorna true se o ponteiro (posição) encontra-se numa faixa acessível dos dados (se não passou da posição máxima disponível, por exemplo). Caso contrário, false é retornado.

Vamos então a um exemplo:

<?php

declare(strict_types=1);

namespace Iterators;

use Iterator;

final class BookStoreIterator implements Iterator
{
    /** @var string[] $books */
    private array $books;
    private int $index = 0;

    /**
     * @param string[] $books
     */
    public function __construct(array $books)
    {
        $this->books = $books;
    }

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

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

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

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

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

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

Uma possível forma de iterar sobre esse objeto BookStoreIterator:

<?php

declare(strict_types=1);

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

use Iterators\BookStoreIterator;

$books = new BookStoreIterator([
    'Book 1',
    'Book 2',
    'Book 3',
    'Book 4',
    'Book 5',
    'Book 6',
    'Book 7',
    'Book 8',
    'Book 9',
    'Book 10',
]);

while ($books->valid()) {
    echo "{$books->key()} -> {$books->current()} \n";

    $books->next();
}

Resultado:

0 -> Book 1 
1 -> Book 2 
2 -> Book 3 
3 -> Book 4 
4 -> Book 5 
5 -> Book 6 
6 -> Book 7 
7 -> Book 8 
8 -> Book 9 
9 -> Book 10

Essa interface faz com que o objeto tenha a capacidade de decidir como e o que iterar. Destaco novamente esses termos em negrito pois isso é essência dos iterators.

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

Outra forma de iterar esse objeto usando a estrutura for:

for ($books->rewind(); $books->valid(); $books->next()) {
    echo "{$books->key()} -> {$books->current()} \n";
}

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

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

É bom lembrar que a interface Iterator estende a Traversable:

Iterator extends Traversable {
    // ...
}

A iteração poderia ser simplificada para:

foreach ($books as $key => $value) {
    echo "{$key} -> {$value} \n";
}

Esse é um exemplo simples, que usa arrays. Mas a grande sacada dos iterators é nos dar o poder sobre como e o que iterar, ou seja, ficamos no poder de definir o comportamento de toda a iteração.

PHP - Orientação a Objetos - Parte 1
Curso de PHP - Orientação a Objetos - Parte 1
CONHEÇA O CURSO

Refatorando para ArrayIterator

O lado chato da abordagem de implementar diretamente a interface Iterator é a necessidade de implementar todos os seus métodos. É útil quando a lógica para se iterar ou avançar o cursor precisa ser específica. Agora, se ela é trivial, como é o caso do nosso exemplo anterior, podemos usar a classe ArrayIterator, que faz parte do conjunto de Iterators da Standard PHP Library (SPL) do PHP.

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

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

<?php

declare(strict_types=1);

namespace Iterators;

use ArrayIterator;
use IteratorAggregate;

final class BookStoreArrayIterator implements IteratorAggregate
{
    /** @var string[] $books */
    private array $books;

    /**
     * @param string[] $books
     */
    public function __construct(array $books)
    {
        $this->books = $books;
    }

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

Exemplo de uso:

<?php

declare(strict_types=1);

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

use Iterators\BookStoreArrayIterator;

$books = new BookStoreArrayIterator([
    'Book 1',
    'Book 2',
    'Book 3',
    'Book 4',
    'Book 5',
    'Book 6',
    'Book 7',
    'Book 8',
    'Book 9',
    'Book 10',
]);

foreach ($books as $key => $value) {
    echo "{$key} -> {$value} \n";
}

A classe ArrayObject

Nos exemplos anteriores vimos como encapsulamos arrays em objetos com iterators. Mas a forma mais simples e elementar de implementar um array como objeto é usando a classe ArrayObject:

<?php

declare(strict_types=1);

$books = new ArrayObject([
    'Book 1',
    'Book 2',
    'Book 3',
    'Book 4',
    'Book 5',
    'Book 6',
    'Book 7',
    'Book 8',
    'Book 9',
    'Book 10',
]);

foreach ($books as $key => $value) {
    echo "{$key} -> {$value} \n";
}

Essa classe implementa diversas interfaces e ela já abstrai tudo pra gente:

ArrayObject implements IteratorAggregate , Traversable , ArrayAccess , Countable {

Não é o assunto do nosso artigo, mas a interface ArrayAccess nos fornece os seguintes métodos:

ArrayAccess {
    abstract public offsetExists ( mixed $offset ) : bool
    abstract public offsetGet ( mixed $offset ) : mixed
    abstract public offsetSet ( mixed $offset , mixed $value ) : void
    abstract public offsetUnset ( mixed $offset ) : void
}

Por exemplo, na instância do ArrayObject podemos acessar:

$books->offsetExists(1) // true

A ArrayObject também encapsula várias funções úteis para manipulação do Iterator:

  • append()
  • asort()
  • count()
  • ksort()
  • natcasesort()
  • natsort()
  • serialize()
  • uasort()
  • uksort()
  • unserialize()

Exemplo:

<?php

declare(strict_types=1);

$list = new ArrayObject([
    5, 4, 3, 2, 1,
]);

for ($i = 5; $i <= 10; $i++) {
    $list->append($i);
}

// Ordena os valores
$list->natsort();

foreach ($list as $key => $value) {
    echo "{$key} -> {$value}\n";
}

Resultado:

4 -> 1
3 -> 2
2 -> 3
1 -> 4
0 -> 5
5 -> 5
6 -> 6
7 -> 7
8 -> 8
9 -> 9
10 -> 10

Podemos, ainda, no segundo argumento da ArrayObject passar a flag ArrayObject::ARRAY_AS_PROPS para permitir que acessemos as chaves do array na forma de objeto:

<?php

declare(strict_types=1);

$list = new ArrayObject(
    [
        'nome' => 'Pedro',
        'idade' => 20,
        'nascimento' => '1990-10-10',
    ],
    ArrayObject::ARRAY_AS_PROPS
);

echo "{$list->nome}, {$list->idade} ({$list->nascimento})";

Resultado:

Pedro, 20 (1990-10-10)

A função iterator_to_array()

Essa função faz uma cópia dos itens do Iterator para um array:

<?php

declare(strict_types=1);

$books = new ArrayObject([
    'Book 1',
    'Book 2',
    'Book 3',
    'Book 4',
    'Book 5',
    'Book 6',
    'Book 7',
    'Book 8',
    'Book 9',
    'Book 10',
]);

var_dump(iterator_to_array($books));

O pseudo-tipo iterable

O PHP implementou na versão 7.1 o pseudo-tipo iterable, que espera receber um array ou algum objeto que implementa a interface Traversable.

Exemplo:

<?php

declare(strict_types=1);

function iterateAndPrint(iterable $list): void
{
    foreach ($list as $key => $value) {
        echo "{$key} -> {$value}\n";
    }
}

iterateAndPrint(['foo', 'bar', 'baz']);

$books = new ArrayObject([
    'Book 1',
    'Book 2',
    'Book 3',
    'Book 4',
]);

iterateAndPrint($books);

Observe que a função iterateAndPrint() agora espera receber uma entidade do pseudo-tipo iterable.

O resultado:

0 -> foo
1 -> bar
2 -> baz
0 -> Book 1
1 -> Book 2
2 -> Book 3
3 -> Book 4

A interface SeekableIterator

Vimos anteriormente que a interface Iterator declara cinco métodos:

Iterator extends Traversable {
    abstract public current ( void ) : mixed
    abstract public key ( void ) : scalar
    abstract public next ( void ) : void
    abstract public rewind ( void ) : void
    abstract public valid ( void ) : bool
}

A interface SeekableIterator é uma extensão da Iterator que adiciona o método seek():

SeekableIterator extends Iterator {
    /* Novo método */
    abstract public seek (int $position) : void

    /* Métodos herdados */
    abstract public Iterator::current(void) : mixed
    abstract public Iterator::key(void) : scalar
    abstract public Iterator::next(void) : void
    abstract public Iterator::rewind(void) : void
    abstract public Iterator::valid(void) : bool
}

Esse método espera receber um valor para mudar a posição do ponteiro interno do iterator. Vamos supor que o iterator está na posição 0 e arbitrariamente queremos pular para a posição 4, esse método seek() pode ser usado para isso.

Vejamos um exemplo. A classe:

<?php

declare(strict_types=1);

namespace Iterators;

use OutOfBoundsException;
use SeekableIterator;

final class BookStoreSeekableIterator implements SeekableIterator
{
    /** @var string[] $books */
    private array $books;
    private int $index = 0;

    /**
     * @param string[] $books
     */
    public function __construct(array $books)
    {
        $this->books = $books;
    }

    public function seek($index): void
    {
        if (! isset($this->books[$index])) {
            throw new OutOfBoundsException("Invalid position ({$index}).");
        }

        $this->index = $index;
    }

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

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

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

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

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

O uso da classe:

<?php

declare(strict_types=1);

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

use Iterators\BookStoreSeekableIterator;

$books = new BookStoreSeekableIterator([
    'Book 1',
    'Book 2',
    'Book 3',
    'Book 4',
    'Book 5',
    'Book 6',
    'Book 7',
    'Book 8',
    'Book 9',
    'Book 10',
]);

echo $books->current() . \PHP_EOL; // Book 1

$books->seek(4);

echo $books->current() . \PHP_EOL; // Book 5

O resultado:

Book 1
Book 5

O seek() permite que arbitrariamente pulemos a posição do ponteiro interno.

Esse é um exemplo puramente didático para termos acesso à essência de como o seek() pode ser usado. Mas no artigo sobre Generators (que muito recomendo a leitura), temos um exemplo onde o seek() tem um sentido de uso real bem mais amplo.

Iterators da SPL

Temos na Standard PHP Library (SPL) dezenas de iterators disponíveis para uso, cada qual com seu caso de uso. Você pode ver a relação deles clicando aqui.

Um exemplo de uso do FilesystemIterator:

<?php

declare(strict_types=1);

$iterator = new FilesystemIterator(__DIR__.'/../');

/** @var SplFileInfo $file */
foreach ($iterator as $file) {
    echo $file->getFilename() . ' -> ' . ($file->isFile() ? 'file' : 'dir') . PHP_EOL;
}

O resultado da iteração:

.php_cs.cache -> file
.php_cs -> file
.gitignore -> file
composer.json -> file
.git -> dir
src -> dir
psalm.xml -> file
examples -> dir
.idea -> dir
vendor -> dir
composer.lock -> file

Outro exemplo é o RegexIterator que aplica um filtro de regex em cima de outro Iterator, por exemplo:

<?php

declare(strict_types=1);

$iterator = new RegexIterator(
    new FilesystemIterator(__DIR__),
    '/^.+\.php$/'
);

/** @var SplFileInfo $file */
foreach ($iterator as $file) {
    echo $file->getFilename() . PHP_EOL;
}

Esse exemplo vai retornar o nome de todos os arquivos que terminam com a extensão .php.

Outra opção seria usar FilterIterator que permite que criemos filtros e os apliquemos em cima de Iterators. Para isso, vamos criar uma classe que estende a FilterIterator:

<?php

declare(strict_types=1);

namespace Iterators;

use FilterIterator;
use Iterator;
use SplFileInfo;

class FileExtensionFilter extends FilterIterator
{
    private string $extension;

    public function __construct(Iterator $iterator, string $extension)
    {
        parent::__construct($iterator);

        $this->extension = $extension;
    }

    public function accept(): bool
    {
        /** @var SplFileInfo $file */
        $file = $this->getInnerIterator()->current();

        return $this->extension === $file->getExtension();
    }
}

E então podemos usar:

<?php

declare(strict_types=1);

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

use Iterators\FileExtensionFilter;

$iterator = new FileExtensionFilter(new FilesystemIterator(__DIR__), 'php');

/** @var SplFileInfo $file */
foreach ($iterator as $file) {
    echo $file->getFilename() . \PHP_EOL;
}

O resultado desse exemplo é exatamente o mesmo do exemplo anterior.

A classe FileExtensionFilter estende a FilterIterator que é um OuterIterator. A ideia é que um OuterIterator possui um InnerIterator (um iterator interno). Por esse motivo dentro do método accept() da classe de filtro conseguimos acessar o InnerIterator que é o FilesystemIterator e então obter a extensão do arquivo do ponteiro atual.

Palavras finais

Um iterator pode iterar recursivamente sobre outros iterators. As possibilidades são muitas quando se trata de agregar iterators. Não é possível em um artigo colocar todos os exemplos possíveis, por isso eu recomendo navegar na documentação e usar na prática os iterators que mais lhe chamam atenção.

Não se esqueça de ler o artigo sobre Generators, lá você terá exemplos bem legais do mundo real e verá como eles são eficientes para lidar com a memória.

Desenvolvedor PHP
Formação: Desenvolvedor PHP
Nesta formação você aprenderá todos os conceitos da linguagem PHP, uma das mais utilizadas no mercado. Desde de conceitos de base, até características mais avançadas, como orientação a objetos, práticas de mercado, integração com banco de dados. Ao final, você terá conhecimento para desenvolver aplicações PHP usando as práticas mais modernas do mercado.
CONHEÇA A FORMAÇÃO

PHP

Estrutura interna de valor, referências e gerenciamento de memória no PHP

A forma com o que o PHP trabalha com referências e gerenciamento de memória não é um assunto muito popular e é compreensível que não seja, se levarmos em conta que na maior parte do tempo como desenvolvedores não precisamos nos preocupar com isso, ademais, o PHP implementa um garbage collector que cuida da contagem das referências e decide o melhor momento de liberar a memória, sem impactar na experiência do desenvolvedor.

Quando falo em “experiência do desenvolvedor”, me refiro ao fato de não precisarmos a todo momento usar unset() para destruir variáveis não mais utilizadas, tampouco passar estruturas mais complexas como objetos ou arrays por referência a todo momento etc. Há raros e específicos casos em que usar referência faz sentido. Como também há raros e específicos casos em que liberar a memória manualmente (sem aguardar o trabalho do garbage collector) faz sentido como, por exemplo, a liberação de um resource para alguma operação subsequente importante que vai consumir um tanto considerável de memória.

A ideia aqui é tentar ao máximo ter uma abordagem leve, de desenvolvedor para desenvolvedor, no sentido de usuários da linguagem. Não tenho a pretenção e nem o conhecimento necessário para explicar o funcionamento interno do PHP e, além do mais, existe gente muito boa e mais preparada pra isso (compartilharei links no final do artigo).

Em suma, esse artigo se propõe a responder as seguintes questões:

  • Como um valor é representado internamente no PHP?
  • Valores em PHP possuem que semântica? De valor ou de referência?
  • Referências? O que são?
  • Objetos são passados por referência?
  • Se tenho um array com milhões de registros e preciso passá-lo para o argumento de uma função, e agora? Vou dobrar o tanto de memória que estou usando?
PHP - Fundamentos
Curso de PHP - Fundamentos
CONHEÇA O CURSO

A estrutura de dado que representa os valores do PHP

No PHP existe uma estrutura de dado elementar chamada zval (zend value), ela é uma estrutura container para qualquer valor arbitrário que se trabalha no PHP:

struct _zval_struct {
    zend_value value;
    union {
        // ...
        uint32_t type_info;
    } u1;
    // ...
};

Para a nossa noção, o mais importante é notarmos que essa estrutura armazena o valor e o tipo desse valor. E, pelo fato de o PHP ser uma linguagem de tipagem dinâmica, o tipo é conhecido apenas em tempo de execução e não em tempo de compilação. Além disso, esse tipo pode mudar durante a execução, por exemplo, um inteiro pode simplesmente virar uma string em um dado momento.

O valor propriamente dito é armazenado na union zend_value (uma union define múltiplos membros de diferentes tipos mas só um deles pode ser ativo por vez):

typedef union _zend_value {
    zend_long         lval;             /* long value */
    double            dval;             /* double value */
    zend_refcounted  *counted;
    zend_string      *str;
    zend_array       *arr;
    zend_object      *obj;
    zend_resource    *res;
    zend_reference   *ref;
    zend_ast_ref     *ast;
    zval             *zv;
    void             *ptr;
    zend_class_entry *ce;
    zend_function    *func;
    struct {
        uint32_t w1;
        uint32_t w2;
    } ww;
} zend_value;

Por essa estrutura já podemos inferir os tipos internos das variáveis que declaramos. Objetos são armazenados numa estrutura separada do tipo zend_object, arrays são do tipo zend_array numa implementação de HashTable e assim por diante. Valores simples como inteiros e float são armazenados diretamente na zval, enquanto que valores complexos como strings e objetos são armazenados em estruturas separadas e representados na zval por ponteiros (como pode ser visto em zend_string *str;).

Outra noção refere-se ao fato de que internamente o PHP faz a contagem da quantidade de vezes que um valor é usado por outras variáveis (symbols) e isso é essencial para o gerenciamento da memória por parte do garbage collector, uma vez que esse contador estiver zerado, indica pro GC que a memória desse valor pode ser liberada.

A semântica dos valores

No PHP os valores possuem a semântica de valor, desde que explicitamente não peçamos por uma referência. Ou seja, quando passamos um valor para um argumento de uma função, ou quando atribuímos uma variável a outra, estamos sempre trabalhando em cópias do mesmo valor. Vamos esclarecer esse conceito com exemplos, para que depois possamos adentrar no assunto de referências.

Exemplo:

<?php

$foo = 1;
$bar = $foo;
$foo++;

var_dump($foo, $bar); // int(2), int(1)

$bar aponta para uma zval diferente, apenas com a cópia do valor de $foo. $foo foi incrementada e isso não refletiu em $bar.

Outro exemplo:

<?php

function increment(int $value) : void {
    $value++;
}

$value = 1;

increment($value);

echo $value; // 1

O valor de $value dentro do escopo da função é um e fora dela, é outro. Por esse motivo, o que está sendo incrementado é o zval do escopo da função.

Mas, veja bem, cópias não são feitas arbitrariamente sem necessidade. Para valores comuns como inteiros, números de ponto flutuante etc, elas são imediatamente feitas. No entanto, o mesmo não acontece com valores complexos como strings e arrays, por exemplo. O PHP usa o princípio copy-on-write, onde uma deep-copy (duplicação) dos valores é feita apenas antes desses dados serem alterados.

O melhor exemplo para ilustrar isso:

<?php

// Etapa 1: Consumo atual de memória
echo sprintf("1) Mem: %s \n", memory_get_usage());

// Etapa 2: Criando um grande array dinâmicamente
$foo = [];
for ($i = 0; $i < 1000000; $i++) {
    $foo[] = random_int(1, 100);
}
echo sprintf("2) Mem: %s \n", memory_get_usage());

// Etapa 3: Atribuindo o array a duas novas variáveis
$bar = $foo;
$baz = $foo;
echo sprintf("3) Mem: %s \n", memory_get_usage());

// Etapa 4: Fizemos uma alteração de escrita no array $bar
$bar[] = 1;
echo sprintf("4) Mem: %s \n", memory_get_usage());

// Etapa 5: Zerando os arrays previamente criados
$foo = [];
$bar = [];
$baz = [];
echo sprintf("5) Mem: %s \n", memory_get_usage());

Resultado:

1) Mem: 395280 
2) Mem: 33953920 
3) Mem: 33953920 
4) Mem: 67512528 
5) Mem: 395312 

Veja que nas etapas 2 e 3 o consumo de memória permaneceu o mesmo. O senso comum seria imaginar que ele fosse triplicar, uma vez que atribuímos dois novos arrays com o valor de $foo. No entanto, o consumo de memória duplicou apenas na etapa 4, pois alteramos o array $bar, essa operação de escrita fez com que o PHP duplicasse o array para esse zval.

O mesmo comportamento pode ser visto com strings:

<?php

// Etapa 1
echo sprintf("1) Mem: %s \n", memory_get_usage());

// Etapa 2
$string = str_repeat('FOO', 100000);
echo sprintf("2) Mem: %s \n", memory_get_usage());

// Etapa 3
$string2 = $string;
$string3 = $string;
echo sprintf("3) Mem: %s \n", memory_get_usage());

// Etapa 4
$string2 .= 'BAR';
$string3 .= 'BAR';
echo sprintf("4) Mem: %s \n", memory_get_usage());

Resultado:

1) Mem: 393504 
2) Mem: 696640 
3) Mem: 696640 
4) Mem: 1302848

Apenas na etapa 4 tivemos aumento da memória, pois as strings foram modificadas, forçando deep-copy.

Outro exemplo:

<?php

// Etapa 1
echo sprintf("1) Mem: %s \n", memory_get_usage());

// Etapa 2
$string = str_repeat('FOO', 100000);
echo sprintf("2) Mem: %s \n", memory_get_usage());

// Etapa 3
function foo(string $string) {
    $string2 = $string;
}

foo($string);

echo sprintf("3) Mem: %s \n", memory_get_usage());

Resultado:

1) Mem: 393400 
2) Mem: 696536 
3) Mem: 696536
PHP Avançado
Curso de PHP Avançado
CONHEÇA O CURSO

Referências no PHP

Referência é um mecanismo que permite que múltiplas variáveis (símbolos) apontem pro mesmo container interno de valor (zval). Referências não são ponteiros como vemos em C ou em Go, por exemplo.

Voltando ao exemplo anterior de $foo e $bar, como podemos fazer para $bar apontar para o mesmo zval que $foo de tal forma que o incremento em $foo reflita em $bar? Aí que entra o operador de referência &amp; na atribuição:

<?php

$foo = 1;
$bar =& $foo;
$foo++;

var_dump($foo, $bar); // int(2), int(2)

Agora, na prática, temos dois símbolos ($foo e $bar) que apontam para o mesmo valor, alterações em um ou em outro refletirão no mesmo container interno de valor. Por esse motivo o incremento de $foo agora refletiu em $bar.

Esse é o exemplo mais elementar para distinguir a semântica de valor da de referência.

Objetos são passados por referência?

Existe a noção entre desenvolvedores e artigos na web de que objetos são passados por referência, mas isso não reflete a realidade. Eles também são passados pela semântica de valor, como qualquer outro valor. Eles apenas possuem um comportamento parecido com “referência” mas que internamente não se trata de referência.

Vejamos esse exemplo:

<?php

class Node
{
    // Todo
}

$node1 = new Node();
$node2 = $node1;

xdebug_debug_zval('node1'); // node1: (refcount=2, is_ref=0)=class Node {  }
xdebug_debug_zval('node2'); // node2: (refcount=2, is_ref=0)=class Node {  }

Nota: A função xdebug_debug_zval() retorna detalhes internos da zval e para ela funcionar, é preciso ter a extensão xdebug instalada.

Na prática, o que temos nesse exemplo, é que as duas variáveis são estruturas (zval) distintas, mas internamente apontam pro mesmo objeto.

O que é diferente de:

<?php

class Node
{
    // Todo
}

$node1 = new Node();
$node2 = &$node1;

xdebug_debug_zval('node1'); // node1: (refcount=2, is_ref=1)=class Node {  }
xdebug_debug_zval('node2'); // node2: (refcount=2, is_ref=1)=class Node {  }

Nesse exemplo, além de ambas as variáveis apontarem pro mesmo objeto, temos explicitamente uma referência, observe que o valor da flag is_ref agora é 1. Isso quer dizer que, $node1 e $node2 apontam para o mesmo zval.

A diferença, a princípio, parece sutil, mas tentemos ver isso na prática. Primeiro um exemplo onde explicitamente pedimos para receber um valor por referência:

<?php

$a = 1;

function change(&$a) {
    $a = 2;
}

change($a);

echo $a; // 2

A alteração dentro do escopo da função refletiu no mesmo container interno de valor (zval) da variável $a do escopo do script. Como era esperado.

Agora a dúvida é: se objetos são sempre passados por referência (como é o senso comum), não precisamos usar o operador de referência para receber um objeto nessa mesma função change(), certo?

Vejamos:

<?php

class Node
{
    // Todo
}

$node = new Node();

function change($node) {
    $node = 10;
}

change($node);

var_dump($node); // class Node#1 (0) { }

Pois bem, se os objetos realmente fossem passados por referência, o valor da variável $node deveria ser 10 e não uma instância da classe Node. Ou seja, quando um objeto é passado para o argumento de uma função/método ou quando é atribuído em outra variável, não temos passagem por referência, o que fica caracterizado é que essas variáveis apenas estão apontando para a mesma estrutura de dado referente ao objeto, o que dá a sensação que está acontecendo passagem por referência.

O que causa essa confusão é esse comportamento:

<?php

class Node
{
    public int $count;
}

$node = new Node();
$node->count = 1;

function change(Node $node) {
    $node->count = 2;
}

change($node);

echo $node->count; // 2

O resultado é 2, mesmo sem passagem por referência, por causa do comportamento de estarmos trabalhando com o mesmo objeto em memória.

O exemplo abaixo demonstra que estamos lidando com duas estruturas (zval) diferentes, mas que ambas referenciam a mesma estrutura de dado do objeto criado:

<?php

class Node
{
    // TODO
}

$node = new Node();

function change(Node $node) {
    $nodeFoo = &$node;

    xdebug_debug_zval('node'); // node: (refcount=2, is_ref=1)=class Node {  }
}

change($node);

xdebug_debug_zval('node'); // node: (refcount=1, is_ref=0)=class Node {  }

Veja que dentro do escopo da função já ficou caracterizado que existe referência, mas fora dela, não. Ou seja, se $nodeFoo dentro de change() fosse alterada para 10, refletiria no mesmo zval de $node. Veja:

<?php

class Node
{
    // TODO
}

$node = new Node();

function change(Node $node) {
    $nodeFoo = &$node;

    $nodeFoo = 10; // Reflete no mesmo zval de $node

    var_dump($node); // int(10)
}

change($node);

var_dump($node); // class Node#1 (0) { }

Agora, a história muda quando explicitamente recebemos a variável por referência:

<?php

class Node
{
    // Todo
}

$node = new Node();

function change(&$node) {
    $node = 10; // Reflete no mesmo zval da variável $node fora desse escopo
}

change($node);

var_dump($node); // int(10)

Por fim, um exemplo de variáveis que não se referenciam, mas que apontam para o mesmo valor interno em memória até que uma operação de escrita seja realizada, aí é feita uma deep-copy:

<?php

$string = str_repeat('FOO ', 4);

xdebug_debug_zval('string'); // string: (refcount=1, is_ref=0)='FOO FOO FOO FOO '

$string2 = $string;

xdebug_debug_zval('string'); // string: (refcount=2, is_ref=0)='FOO FOO FOO FOO '

$string3 = $string;

xdebug_debug_zval('string'); // string: (refcount=3, is_ref=0)='FOO FOO FOO FOO '

$string3 .= 'BAR';

xdebug_debug_zval('string'); // string: (refcount=2, is_ref=0)='FOO FOO FOO FOO '

Observe que depois que $string3 fez uma operação de escrita o refcount do valor de $string foi decrementado.

Outro exemplo genérico:

<?php

class Foo {
    public $index;
}

$a = new Foo();
$b = $a;
$c = &$b;

$c->index = 10;

echo "({$a->index}, {$b->index}, {$c->index})" . PHP_EOL; // (10, 10, 10)

$c = "Hello";

echo "({$a->index}, {$b}, {$c})"; // (10, Hello, Hello)

Na prática temos:

Diagrama de referência

Todos os símbolos apontam pro mesmo objeto, no entanto, $c é referência de $b, alterações feitas em $c refletirão na mesma zval de $b, mantendo $a intacta.

Em que momento devo usar referências?

As funções de sorting de arrays do PHP os recebem por referência, por exemplo:

sort ( array &$array [, int $sort_flags = SORT_REGULAR ] ) : bool

Sempre que você ver na assinatura de uma função &amp;$var, significa que está recebendo por referência e que, portanto, vai trabalhar na mesma zval da variável informada.

E usar referências não é sobre performance, dependendo do uso, pode piorá-la. Usar referências é mais sobre lidar melhor com a memória, como no caso da função acima, não faria sentido duplicar o array inteiro só para aplicar o algoritmo de sorting.

Mas, no geral, a regra é que você dificilmente vai usar com frequência operações com referências. Usa-se referências quando realmente existe um objetivo muito bem definido, portanto, não se preocupe em querer “otimizar” o seu código pra usar referências, pois é bem possível que você não precisa delas.

O que mais posso fazer com referências?

Você pode ver todas as sintaxe possíveis para se usar referências no PHP através desse link da documentação oficial.

Palavras finais

Sumarizando o que foi visto nesse artigo:

  • PHP usa a semântica de valor;
  • Objetos não são passados por referência;
  • Valores complexos como arrays e strings são apenas duplicados quando alterados (usa-se o princípio copy-on-write);
  • Referências não deixam o código mais performático, se mal utilizadas, podem é deixar mais lento, devido aos vários apontamentos que precisam ser feitos. Referências são usadas em casos bem específicos em que o desenvolvedor tem a exata noção do comportamento que ele espera ter;
  • O desenvolvedor não precisa na maior parte do tempo se preocupar com memória (a não ser que vá trabalhar com grandes data sets, mas aí pode-se estudar o uso de generators no PHP ou alguma outra estratégia);

Não é nada fácil entender a figura completa desse conjunto de pecinhas, mas caso você tenha interesse em se aprofundar, recomendo a leitura dos artigos:

Até a próxima!

Desenvolvedor PHP
Formação: Desenvolvedor PHP
Nesta formação você aprenderá todos os conceitos da linguagem PHP, uma das mais utilizadas no mercado. Desde de conceitos de base, até características mais avançadas, como orientação a objetos, práticas de mercado, integração com banco de dados. Ao final, você terá conhecimento para desenvolver aplicações PHP usando as práticas mais modernas do mercado.
CONHEÇA A FORMAÇÃO

PHP

Aplicações em tempo real com PHP usando WebSockets

Neste artigo veremos uma introdução a WebSockets com a criação de um servidor em PHP e usando o navegador do usuário como cliente.

Antes, entretanto, recomendo que você assista o vídeo O que são WebSockets? gravado pelo Akira Hanashiro (aqui da TreinaWeb) que explica de uma forma bem sucinta.

E, se você tiver interesse, também recomendo a leitura do artigo Uma introdução a TCP, UDP e Sockets para se ter a base do que é um socket na arquitetura TCP/IP.

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

Ok, mas o que é um WebSocket?

WebSocket é um protocolo que permite a criação de um canal de comunicação cliente-servidor com transmissão bidirecional onde ambos os lados (cliente e servidor) podem transmitir dados simultaneamente. WebSocket veio para suprir as deficiências do protocolo Http para esse objetivo. O protocolo Http, que por sinal, é unidirecional (a transmissão ocorre só de uma ponta para a outra), onde o cliente envia a requisição e o servidor retorna a resposta, finalizando ali a conexão. Ou seja, se o cliente envia 5 requisições Http para o servidor, 5 conexões TCP independentes são abertas, enquanto que com WebSocket uma única conexão TCP é aberta e ela fica disponível para troca de dados a qualquer momento (uma conexão persistente, até que um dos lados decida fechá-la).

É muito importante pontuar, também, que Socket e WebSocket são coisas diferentes. Um WebSocket é um protocolo que roda em cima de sockets TCP, enquanto que um socket é uma abstração, uma porta lógica de comunicação entre duas pontas numa rede.

Benefícios de usar WebSocket em detrimento a Http

Se existe a necessidade de uma conexão permanecer aberta por um longo tempo para uma troca de dados constante, WebSocket é uma ótima escolha. Uma conexão Http é relativamente pesada, ela transmite não só os dados, mas também cabeçalhos. Além disso, possui um curto tempo de vida e não mantém estado (stateless).

Já uma conexão WebSocket, depois do handshake entre o cliente e o servidor, ela permanece aberta até que uma das partes decida fechá-la. E foco dela é na transmissão dos dados, cabeçalhos não são transmitidos pra lá e pra cá.

Então, em resumo, os benefícios em relação ao Http são: baixa latência, conexão persistente e full-duplex (transmissão bidirecional).

Principais casos de uso para WebSockets

Home Brokers (onde cotações são atualizadas a todo instante), feeds de redes sociais, aplicativos de bate-papo, ferramentas de edição colaborativa, jogos multi-player etc.

Criando o primeiro servidor

Neste artigo usaremos a library Ratchet que nos permite criar um servidor WebSocket assíncrono, e para isso ela utiliza o event loop do ReactPHP. Outra opção seria utilizarmos o servidor de WebSocket do Swoole. Mas o bom de usar o ReactPHP é que não precisamos instalar nenhuma extensão específica na nossa instalação do PHP.

Crie uma pasta chamada websocket-demo e dentro dela crie o arquivo composer.json:

{
    "require": {
        "cboden/ratchet": "^0.4.1"
    }
}

Instale as dependências:

$ composer install

Agora, crie um arquivo server.php com a seguinte implementação:

<?php

require './vendor/autoload.php';

use Ratchet\Server\EchoServer;

$app = new Ratchet\App('localhost', 9980);
$app->route('/echo', new EchoServer, ['*']);
$app->run();

Esse é o servidor mais primitivo que existe, é um “Echo Server”, ele envia de volta tudo o que o cliente manda pra ele. Nem tivemos a necessidade de implementá-lo, pois ele já vem junto com o Ratchet.

Por fim, crie um arquivo index.html com a seguinte implementação:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket EchoServer</title>
</head>
<body>
<label for="input">Digite aqui: </label>
<input id="input" type="text" placeholder="Digite aqui"/>

<div id="response"></div>
<script>
    let input = document.getElementById('input');
    let response = document.getElementById('response');
    const socket = new WebSocket('ws://localhost:9980/echo');

    // Ao estabelecer a conexão enviamos uma mensagem pro servidor
    socket.addEventListener('open', function () {
        socket.send('Conexão estabelecida.');
    });

    // Callback disparado sempre que o servidor retornar uma mensagem
    socket.addEventListener('message', function (event) {
        response.insertAdjacentHTML('beforeend', "<p><b>Servidor diz: </b>" + event.data + "</p>");
    });

    input.addEventListener('keyup', function (event) {
        if (event.keyCode === 13) {
            socket.send(this.value);
            this.value = '';
        }
    });
</script>
</body>
</html>

Para testar, primeiro de tudo temos que ter o servidor rodando. Na raiz do projeto execute:

$ php server.php

Vai iniciar o servidor na porta 9980. Por fim, basta executar o arquivo index.html e interagir escrevendo mensagens e apertando enter para enviá-las. Tudo o que o servidor receber de input, ele retornará de volta para o cliente.

Exemplo servidor

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

O objeto WebSocket é nativo e presente em todos os navegadores modernos. Iniciamos o servidor de forma “mágica” usando o Ratchet, mas a realidade é que por debaixo dos panos ele precisa abstrair algumas importantes coisas como o handshake inicial, as trocas das mensagens (Data Frames) que são criptografadas etc. Toda a dinâmica do funcionamento de um servidor de Websocket pode ser lida nesse documento: Escrevendo um servidor WebSocket.

Agora vamos criar o nosso próprio wrapper, nossa própria implementação de um servidor. Primeiro, na raiz do projeto, crie uma pasta chamada src. Em seguida, altere o composer.json para:

{
    "require": {
        "cboden/ratchet": "^0.4.1"
    },
    "autoload": {
        "psr-4": {
            "Chat\\": "src"
        }
    }
}

Agora crie um arquivo ChatServer.php dentro da pasta src com a seguinte implementação:

<?php

namespace Chat;

use Exception;
use SplObjectStorage;
use Ratchet\ConnectionInterface;
use Ratchet\MessageComponentInterface;

final class ChatServer implements MessageComponentInterface
{
    private $clients;

    public function __construct()
    {
        $this->clients = new SplObjectStorage();
    }

    public function onOpen(ConnectionInterface $conn): void
    {
        $this->clients->attach($conn);
    }

    public function onMessage(ConnectionInterface $from, $msg): void
    {
        foreach ($this->clients as $client) {
            $client->send($msg);
        }
    }

    public function onClose(ConnectionInterface $conn): void
    {
        $this->clients->detach($conn);
    }

    public function onError(ConnectionInterface $conn, Exception $exception): void
    {
        $conn->close();
    }
}

Estamos implementando a interface MessageComponentInterface. A diferença desse servidor pro EchoServer, é que neste estamos guardando as conexões que são estabelecidas e, quando uma mensagem é recebida, enviamos ela de volta para toda as conexões abertas (que é o que o fazemos no método onMessage).

Para testar a implementação, crie um arquivo chat.html no projeto:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket Simple Chat</title>
</head>
<body>
<p>
    <label for="nome">Seu nome: </label>
    <input id="nome" type="text" placeholder="Seu nome"/>
</p>
<p>
    <label for="input">Sua mensagem: </label>
    <input id="input" type="text" placeholder="Sua mensagem"/>
</p>
<hr>
<div id="chat"></div>
<script>
    let chat = document.getElementById('chat');
    let input = document.getElementById('input');
    const nome = document.getElementById('nome');
    const socket = new WebSocket('ws://localhost:9990/chat');

    // Ao receber mensagens do servidor
    socket.addEventListener('message', function (event) {
        // Deserializamos o objeto
        const data = JSON.parse(event.data);
        // Escrevemos no DOM
        chat.insertAdjacentHTML('beforeend', "<p><b>" + data.nome + " diz: </b>" + data.mensagem + "</p>");
    });

    // Ao enviar uma mensagem
    input.addEventListener('keyup', function (event) {
        if (event.keyCode === 13) {
            // Objeto com os dados que serão trafegados
            const data = {
                nome: nome.value,
                mensagem: this.value,
            };

            // Serializamos o objeto para json
            socket.send(JSON.stringify(data));

            this.value = '';
        }
    });
</script>
</body>
</html>

Antes de iniciar o servidor é importante executar o dump-autoload do Composer, para que a classe ChatServer seja reconhecida:

$ composer dump-autoload

Por fim, inicie o servidor:

$ php chat.php

Você pode testar abrindo duas instâncias do chat e usando nomes diferentes:

Exemplo chat

Os exemplos completos você pode ver nesse repositório: https://github.com/KennedyTedesco/websocket-demo

Só o navegador pode ser cliente do meu servidor?

Não, qualquer outra aplicação pode ser cliente de um servidor WebSocket. Você pode, por exemplo, criar uma aplicação cliente em PHP usando a library Pawl.

Palavras finais

O Ratchet simplifica e muito a criação de servidores de WebSocket em PHP, não obstante, ele também suporta sub-protocolos como o Wamp. Em um próximo artigo pretendo abordar um exemplo utilizando-o.

Até a próxima!

Desenvolvedor PHP
Formação: Desenvolvedor PHP
Nesta formação você aprenderá todos os conceitos da linguagem PHP, uma das mais utilizadas no mercado. Desde de conceitos de base, até características mais avançadas, como orientação a objetos, práticas de mercado, integração com banco de dados. Ao final, você terá conhecimento para desenvolver aplicações PHP usando as práticas mais modernas do mercado.
CONHEÇA A FORMAÇÃO

Desenvolvimento Back-end PHP

Laminas: O futuro do Zend Framework

Se você acompanha os principais frameworks em PHP já deve ter ouvido falar sobre o Laminas. Para quem não sabe, esse é o novo nome do já conhecido Zend Framework, um dos mais antigos frameworks em PHP com um foco no mercado de soluções enterprise. Antes de falar sobre essa mudança de Laminas para Zend, vamos começar falando sobre a história do Zend Framework.

História do Zend Framework

Quem programa com PHP faz algum tempo já deve ter usado ou pelo menos ter ouvido falar sobre o Zend Framework. Para uma introdução sobre o que é o Zend Framework leia esse artigo no link. Lançado em março de 2006, o Zend Framework 1 foi um dos primeiros frameworks PHP de todos os tempos, e até o momento a versão com maior tempo de suporte de todos os frameworks em PHP, totalizando um pouco mais de 10 anos de suporte.

De 2006 pra cá, o Zend Framework acompanhou e contribuiu com a evolução do ecossistema do PHP, seja suportando novas versões da linguagem, adicionando suporte ao Composer e melhorando seu comportamento interno. Em 2012 foi lançado o Zend Framework 2, que iniciou o processo de modernização do framework em componentes menores e independentes, cada qual com seu próprio ciclo de vida além de outras melhorias.

Essa quebra do core do framework permitiu que projetos derivados pudessem ser criados, como o Apigility, um pacote de ferramentas especializado no desenvolvimento de API e também o Expressive, um micro framework modular baseado na PSR-7 que te permite escolher quais componentes você quer utilizar, suportando tanto componentes do Zend Framework como outros componentes mantidos pela comunidade.

Zend Expressive - Microframework PHP
Curso de Zend Expressive - Microframework PHP
CONHEÇA O CURSO

Além da ajuda da comunidade, tudo isso aconteceu com o suporte da Zend, empresa fundada em 1999 por Andi Gutmans e Zeev Suraski depois que eles redesenharam o até então PHP-FI, escrito originalmente pelo Rasmus Lerdorf. Em 1998 eles lançaram o Zend Engine, que se tornou o PHP 4 e desde então tornou a base da linguagem que utilizamos hoje em dia (claro, com muitas melhorias e evoluções se comparado com aquela época).

O fim do Zend Framework

Não podemos negar a importância histórica que a Zend trouxe para o ecossistema do PHP, tanto com o Zend Framework como com outras iniciativas da empresa. Porém, em Outubro de 2015, a Zend Technologies foi adquirida pela Rogue Wave Software. Tudo ia bem com o suporte ao Zend Framework. Foi nessa época inclusive que foi lançado o Zend Framework 3, junto com Expressive, consolidando a visão de um framework baseados em componentes individuais e com um ciclo de vida próprio.

Mas isso mudou quando a Rogue Wave Software foi adquirida pela Perforce em Janeiro de 2019. Nessa época, a companhia decidiu rever seu portfólio de projetos e infelizmente continuar suportando o Zend Framework não estava em seus planos. Porém o projeto em si não seria abandonado, em Abril do mesmo ano foi anunciado a transição do Zend Framework para a Linux Foundation, sob o nome de The Laminas Project.

A Linux Foundation é uma organização sem fins lucrativos especializada em projetos open source. Além de ajudar a manter e padronizar o Linux, outros projetos que fazem parte da Linux Foundation incluem o Kubernetes, Jenkins, GraphQL e até NodeJS. Isso da até uma segurança maior para nós, desenvolvedores e também para as empresas, pois temos uma garantia ainda maior na continuidade da evolução do projeto, agora gerido por um modelo de governança que já foi provado que funciona.

O início do Laminas

Após esse anúncio, iniciou o processo de migração do Zend Framework para o Laminas. Por questões de copyright, acredito eu, o nome Zend precisou ser abandonado em favor do Laminas. Com isso, todos os componentes que antes estavam sob o guarda-chuva da Zend passaram para a organização do Laminas dentro do GitHub.

Apesar de ser uma grande mudança, que pode envolver alterar as dependências dos seus pacotes no Composer, essa fase de transição (que já começou e ainda está acontecendo) tem como objetivo garantir uma migração mais suave o possível. O mesmo líder de projeto responsável por manter o Zend Framework, o Matthew Weier O’Phinney, manteve sua função no projeto, agora como líder do Laminas.

Para auxiliar nessa migração, você pode utilizar uma ferramenta de migração em seu projeto que será responsável por atualizar todas as dependências do Zend gerenciadas pelo Composer para a dependência correspondente no Laminas, entregando assim uma maneira de migrar projetos inteiros em alguns poucos comandos, incluindo também suporte para migrar projetos antes desenvolvidos com Expressive e Apigility.

Falando sobre Expressive e Apigility, eles também foram renomeados para Laminas Mezzio e Laminas API Tools, respectivamente, aproveitando assim o momento de alteração do projeto principal.

Subprojetos do Laminas

Desenvolvedor PHP
Formação: Desenvolvedor PHP
Nesta formação você aprenderá todos os conceitos da linguagem PHP, uma das mais utilizadas no mercado. Desde de conceitos de base, até características mais avançadas, como orientação a objetos, práticas de mercado, integração com banco de dados. Ao final, você terá conhecimento para desenvolver aplicações PHP usando as práticas mais modernas do mercado.
CONHEÇA A FORMAÇÃO

Status atual da migração para o Laminas

Nesse momento, já estamos em uma fase bem avançada da migração para o Laminas. Os componentes do Zend já foram migrados para os novos repositórios na organização do Laminas e as referências dos componentes no Composer já podem ser utilizados.

Inclusive, ao tentar utilizar um componente do Zend, você receberá uma mensagem informando que o mesmo foi abandonado e que você deve atualizar suas dependências para utilizar o componente do Laminas:

Componentes do Zend Framework descontinuados

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

Já podemos acessar a documentação dos componentes do Laminas em um novo domínio, bem como do Mezzio e do Laminas MVC. Ainda não está disponível a documentação do Laminas API Tools, mas num futuro próximo já deve estar disponível.

Estou começando um novo projeto, ou quero migrar um projeto existente, o que eu faço?

Então, se você estiver pensando em construir uma nova aplicação com Zend Framework, recomendo já começar utilizando o Laminas MVC ou o Mezzio. Ambos já tem tutoriais na documentação mostrando como criar projetos do zero com essas ferramentas.

Para você que já tem um projeto desenvolvido com Zend Framework, consulte a documentação sobre a migração para o Laminas. Com poucos comandos pode ser possível você migrar toda sua aplicação de forma bastante automatizada e intuitíva.

Pessoalmente estou bastante animado com essa migração, principalmente pelas novas possibilidades que estão sendo adicionadas ao Laminas, como o suporte oficial ao Swoole para desenvolvimento assíncrono no PHP.

Para conhecer mais sobre o Swoole, configura os artigos aqui do blog da TreinaWeb sobre introdução ao Swoole e trabalhando com corrotinas. Ter uma integração oficial com o Swoole no Laminas Mezzio é algo bem promissor! 😀


PHP

Corrotinas e código assíncrono em PHP usando Generators

Nos últimos artigos eu tenho escrito sobre programação assíncrona em PHP com ReactPHP e Swoole. O Swoole tem seu mecanismo interno próprio de corrotinas, mas o que muita gente não sabe é que podemos trabalhar com corrotinas usando PHP puro, através de generators.

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

Eu achei que fosse interessante escrever sobre isso até mesmo como uma forma de apresentar às pessoas o conceito de generators e também como uma forma de instigá-las a buscar mais sobre o tema.

A ideia desse artigo é construir um simples (muito simples) scheduler de corrotinas bem primitivo e didático. Em outros artigos eu escrevi de forma mais abrangente sobre generators, código síncrono, assíncrono, corrotinas etc, conceitos esses que serão importantes você ter para entender o funcionamento do nosso exemplo aqui. Por esse motivo, eu recomendo que você leia esses artigos:

Opcionalmente, se você tiver interesse, também recomendo a leitura deste artigo:

Esse artigo que você está lendo é, de certa forma, uma ramificação deste:

Portanto, o artigo Generators no PHP é realmente uma leitura necessária para entender os códigos que desenvolveremos aqui.

PHP - Orientação a Objetos - Parte 1
Curso de PHP - Orientação a Objetos - Parte 1
CONHEÇA O CURSO

Gênesis

Sabemos que generators são como se fossem funções que podem ser interrompidas e resumidas a qualquer momento. Também sabemos que assincronismo é sobre fluxo de execução. Captando essas duas ideias centrais, podemos chegar na seguinte conclusão lógica: se um generator pode ser interrompido para que outro seja executado, eu posso usar isso para manipular o fluxo de uma execução e então atingir uma modelagem de código assíncrono.

Primeiro vamos ver um exemplo de código síncrono:

<?php

declare(strict_types=1);

$booksTask = static function () {
    for ($i = 1; $i <= 4; ++$i) {
        echo "Book $i\n";
    }
};

$moviesTask = static function () {
    for ($i = 1; $i <= 8; ++$i) {
        echo "Movie $i\n";
    }
};

$booksTask();
$moviesTask();

O resultado dessa execução:

Book 1
Book 2
Book 3
Book 4
Movie 1
Movie 2
Movie 3
Movie 4
Movie 5
Movie 6
Movie 7
Movie 8

A execução é síncrona, linear e previsível. Temos duas tarefas, mas a segunda ($moviesTask) só terá oportunidade de desempenhar seu trabalho depois que a primeira ($booksTask) terminar tudo o que tem para ser feito.

Podemos transformar esse exemplo em um código de modelo assíncrono usando generators e trabalhando com a ideia de que cada task é uma corrotina. Para isso, dois princípios são importantíssimos:

  • Uma Task (tarefa) será apenas um decorator de um generator;
  • Um Scheduler cuidará da fila de tarefas e da execução delas;

O exemplo pode ser encontrado no GitHub: https://github.com/KennedyTedesco/coroutines-php

Uma tarefa será representada pela classe Task:

<?php

declare(strict_types=1);

namespace Coral;

use Generator;

final class Task
{
    private $coroutine;
    protected $firstYield = true;

    public function __construct(Generator $coroutine)
    {
        $this->coroutine = $coroutine;
    }

    public function run(): void
    {
        if ($this->firstYield) {
            $this->firstYield = false;
            $this->coroutine->current();
        } else {
            $this->coroutine->next();
        }
    }

    public function finished(): bool
    {
        return ! $this->coroutine->valid();
    }
}

A tarefa é apenas um decorator de um generator.

E o Scheduler será representado pela classe de seu próprio nome:

<?php

declare(strict_types=1);

namespace Coral;

use SplQueue;

final class Scheduler
{
    private $tasks;

    public function __construct()
    {
        $this->tasks = new SplQueue();
    }

    public function schedule(Task $task): void
    {
        $this->tasks->enqueue($task);
    }

    public function handle(): void
    {
        while (! $this->tasks->isEmpty()) {
            /** @var Task $task */
            $task = $this->tasks->dequeue();

            $task->run();
            if (! $task->finished()) {
                $this->schedule($task);
            }
        }
    }
}

O Scheduler mantém uma fila de tarefas a serem executadas e possui um método público schedule() para adicionar tarefas nessa fila. O método handle() itera nas tarefas executando-as. Antes de entrarmos em mais detalhes, ao executar o exemplo:

<?php

declare(strict_types=1);

require 'vendor/autoload.php';

use Coral\Task;
use Coral\Scheduler;

$scheduler = new Scheduler();

$booksTask = static function () {
    for ($i = 1; $i <= 4; ++$i) {
        echo "Book $i\n";

        yield;
    }
};

$moviesTask = static function () {
    for ($i = 1; $i <= 8; ++$i) {
        echo "Movie $i\n";

        yield;
    }
};

$scheduler->schedule(new Task($booksTask()));
$scheduler->schedule(new Task($moviesTask()));

$scheduler->handle();

Nota: As duas tarefas retornam um generator, que depois é passado para o construtor de Task.

Temos o seguinte resultado:

Book 1
Movie 1
Book 2
Movie 2
Book 3
Movie 3
Book 4
Movie 4
Movie 5
Movie 6
Movie 7
Movie 8

Diferentemente do exemplo síncrono mostrado anteriormente, neste temos a alternância da execução das tarefas, no sentido de que são colaborativas, uma abre espaço para que a outra também tenha oportunidade de ser executada. Isso acontece pois o Scheduler executa a tarefa, o valor corrente dela é impresso, nisso ela volta novamente para a fila do Scheduler para ser executada novamente em outro momento. As tarefas sempre voltam para a fila enquanto ainda tiverem valores a serem processados:

$task->run();
if (! $task->finished()) {
    $this->schedule($task);
}

Essa é uma estratégia para mantê-las em sua essência colaborativas, ou seja, a tarefa abre mão do seu tempo de execução para que outra tarefa também tenha oportunidade.

Considerações finais

Este foi um simples exemplo de como podemos ter uma operação assíncrona utilizando generators. E aqui nem estamos nos referindo a multiplexing de I/O, mas poderíamos usar esse mesmo conceito de generators e implementar I/O não bloqueante (assíncrono) usando algum padrão como o Reactor, usado pelo ReactPHP ou algo mais “simples” usando diretamente a função stream_select(). Inclusive, O Nikita Popov (desenvolvedor do core do PHP) escreveu exatamente sobre isso no artigo Cooperative multitasking using coroutines (in PHP!), que por sinal, é a principal referência desse artigo aqui. Recomendo essa leitura pois ele também fez com que o generator se comunicasse com outros generators e com o Scheduler (que é a realmente essência de uma corrotina), numa espécie de canal de comunicação, o que torna as coisas ainda mais poderosas. O framework assíncrono Amp faz um uso bem intensivo de generators, também vale a pena testá-lo.

Ah, não poderia deixar de pontuar novamente: se você tem interesse por programação assíncrona com PHP, vale a pena a leitura desses artigos:

Até a próxima!

Desenvolvedor PHP
Formação: Desenvolvedor PHP
Nesta formação você aprenderá todos os conceitos da linguagem PHP, uma das mais utilizadas no mercado. Desde de conceitos de base, até características mais avançadas, como orientação a objetos, práticas de mercado, integração com banco de dados. Ao final, você terá conhecimento para desenvolver aplicações PHP usando as práticas mais modernas do mercado.
CONHEÇA A FORMAÇÃO