Ir para conteúdo
Fórum Script Brasil
  • 0

Comportamento de funções geradoras e coroutines - PHP


iNosuKe 么

Pergunta

Estava desenvolvendo uma class que abstrai algumas ações do file system do php, e fui tendo ideias durante a implementação de tal, no fim, esse foi o resultado: 

<?php

declare(strict_types=1);

class File
{
    private $file;
    private $type;

    function __construct(string $filePath)
    {
        if (!file_exists($filePath)) {
            throw new InvalidArgumentException("Arquivo não localizado :(");
        }

        $this->type = substr($filePath, -3);
        $this->file = fopen($filePath, "a+");
    }

    function __destruct()
    {
        fclose($this->file);
    }

    function write(string|array $data): int
    {
        return (match ($this->type) {
            "txt" => function () use ($data) {
                $bytesWritten = 0;

                if (is_string($data)) {
                    $result = fwrite($this->file, $data . PHP_EOL);

                    if ($result === false) {
                        throw new RuntimeException("Erro ao tentar escrever no arquivo");
                    }

                    $bytesWritten += $result;
                } else {
                    foreach ($data as  $line) {
                        $result = fwrite($this->file, $line . PHP_EOL);

                        if ($result === false) {
                            throw new RuntimeException("Erro ao tentar escrever no arquivo");
                        }

                        $bytesWritten += $result;
                    }
                }

                return $bytesWritten;
            },
            "csv" => function () use ($data) {
                if (!is_array($data)) {
                    throw new InvalidArgumentException("Para aquivos .csv, espera-se uma estrutura de array bidimensional");
                }

                $bytesWritten = 0;

                foreach ($data as  $line) {
                    if (!is_array($line)) {
                        throw new InvalidArgumentException("Para aquivos .csv, espera-se uma estrutura de array bidimensional");
                    }

                    $result = fwrite($this->file, implode(",", $line) . PHP_EOL);

                    if ($result === false) {
                        throw new RuntimeException("Erro ao tentar escrever no arquivo");
                    }

                    $bytesWritten += $result;
                }

                return $bytesWritten;
            },
        })();
    }

    function read(): Generator
    {
        rewind($this->file);

        return (match ($this->type) {
            "txt" => function (): Generator {
                while (!feof($this->file)) {
                    $line = fgets($this->file);

                    if ($line === false and !feof($this->file)) {
                        throw new RuntimeException("Erro ao tentar ler o arquivo");
                    }

                    yield $line;
                }
            },
            "csv" => function (): Generator {
                while (!feof($this->file)) {
                    $line = fgetcsv($this->file);

                    if ($line === false and !feof($this->file)) {
                        throw new RuntimeException("Erro ao tentar ler o arquivo");
                    }

                    yield $line;
                }
            }
        })();
    }
}


Utilizei o conceito de funções geradoras, aliado ao de funções auto-executáveis. E ao se observar bem, acabou por bagunçar um pouco o conceito fundamental de coroutines, que em cenários normais acontecem entre 2 funções. Na minha implementação, a função read() está no meio das ditas "2 funções":

function read(): Generator
    {
        rewind($this->file);

        return (match ($this->type) {
            "txt" => function (): Generator {
                while (!feof($this->file)) {
                    $line = fgets($this->file);

                    if ($line === false and !feof($this->file)) {
                        throw new RuntimeException("Erro ao tentar ler o arquivo");
                    }

                    yield $line;
                }
            },
            "csv" => function (): Generator {
                while (!feof($this->file)) {
                    $line = fgetcsv($this->file);

                    if ($line === false and !feof($this->file)) {
                        throw new RuntimeException("Erro ao tentar ler o arquivo");
                    }

                    yield $line;
                }
            }
        })();
    }
}

se executado:

$f = new File("f.txt");

foreach ($f->read() as $line) {
    echo $line;
}

o forEach, implicitamente executa métodos da classe **Generator**, que permitem devolver a execução para a **função geradora read()**, mas nesse caso, a função geradora não é explicitamente a read(), mas sim a closure interna a ela. Daí acabei me confundindo um pouco, será que o php repassa a execução para a **função geradora read()**, que por sua vez devolve para sua closure internamente?

Editado por iNosuKe 么
Link para o comentário
Compartilhar em outros sites

5 respostass a esta questão

Posts Recomendados

  • 1

Compreendeu bem ao usar yield em uma closure retornada por read(). Isso permite controlar o fluxo de execução de forma mais eficiente, especialmente ao iterar com foreach sobre os dados lidos do arquivo, seja ele TXT ou CSV.

"será que o php repassa a execução para a função geradora read()"

O PHP gerencia a execução através da função geradora (closure), não pelo método read() diretamente. 

Uma dica é tornar o código mais modular, aplique princípios SOLID. Isso significa separar responsabilidades.

 

interface FileHandler {
    public function write($data): int;
    public function read(): Generator;
}

// Implementação para arquivos TXT
class TextFileHandler implements FileHandler {

    public function write($data): int {
        // Lógica de escrita para TXT
    }
    public function read(): Generator {
        // Lógica de leitura para TXT
    }
}

// Implementação para arquivos CSV
class CsvFileHandler implements FileHandler { 
    public function write($data): int {
        // Lógica de escrita para CSV
    }
    public function read(): Generator {
        // Lógica de leitura para CSV
    }
}

class FileFactory {
    public static function create(string $filePath): FileHandler {
        $extension = pathinfo($filePath, PATHINFO_EXTENSION);
        return match ($extension) {
            'txt' => new TextFileHandler(),
            'csv' => new CsvFileHandler(),
            default => throw new InvalidArgumentException("Unsupported file type: $extension"),
        };
    }
}

 

Editado por William Duarte
Link para o comentário
Compartilhar em outros sites

  • 0

Eu nunca mexi numa função geradora, assim eu pedi para a Gemini me explicar. Foi pior, ela deu um exemplo que não funcionou. Para ela existe um método chamado next( ) que faz a função geradora executar o próximo comando.

Se você quer usar um temporizador para exibir uma linha de cada vez o conteúdo do arquivo f.txt, acho melhor usar o JavaScript e não o PHP. O PHP publica tudo de uma vez e não uma linha de cada vez, ao contrário do JavaScript que o desenvolvedor pode amarrar junto com o cronômetro.

Link para o comentário
Compartilhar em outros sites

  • 0
Em 13/02/2024 em 12:47, Frank K Hosaka disse:

Eu nunca mexi numa função geradora, assim eu pedi para a Gemini me explicar. Foi pior, ela deu um exemplo que não funcionou. Para ela existe um método chamado next( ) que faz a função geradora executar o próximo comando.

Se você quer usar um temporizador para exibir uma linha de cada vez o conteúdo do arquivo f.txt, acho melhor usar o JavaScript e não o PHP. O PHP publica tudo de uma vez e não uma linha de cada vez, ao contrário do JavaScript que o desenvolvedor pode amarrar junto com o cronômetro.

Tem que entender sobre a diferença entre PHP e JavaScript em termos de execução síncrona versus assíncrona. No PHP, as funções geradoras, com yield e o método next(), permitem "pausar" e "continuar" a execução dentro da própria função, ideal para manipular grandes volumes de dados de forma eficiente. Agora se você esta falando sobre Javascript no backend seja com node ou deno, basicamente é a mesma coisa.

Editado por William Duarte
Link para o comentário
Compartilhar em outros sites

  • 0
Em 18/02/2024 em 07:54, William Duarte disse:

Compreendeu bem ao usar yield em uma closure retornada por read(). Isso permite controlar o fluxo de execução de forma mais eficiente, especialmente ao iterar com foreach sobre os dados lidos do arquivo, seja ele TXT ou CSV.

"será que o php repassa a execução para a função geradora read()"

O PHP gerencia a execução através da função geradora (closure), não pelo método read() diretamente. 

Uma dica é tornar o código mais modular, aplique princípios SOLID. Isso significa separar responsabilidades.

 

interface FileHandler {
    public function write($data): int;
    public function read(): Generator;
}

// Implementação para arquivos TXT
class TextFileHandler implements FileHandler {

    public function write($data): int {
        // Lógica de escrita para TXT
    }
    public function read(): Generator {
        // Lógica de leitura para TXT
    }
}

// Implementação para arquivos CSV
class CsvFileHandler implements FileHandler { 
    public function write($data): int {
        // Lógica de escrita para CSV
    }
    public function read(): Generator {
        // Lógica de leitura para CSV
    }
}

class FileFactory {
    public static function create(string $filePath): FileHandler {
        $extension = pathinfo($filePath, PATHINFO_EXTENSION);
        return match ($extension) {
            'txt' => new TextFileHandler(),
            'csv' => new CsvFileHandler(),
            default => throw new InvalidArgumentException("Unsupported file type: $extension"),
        };
    }
}

 

Obrigado pela dica, irei aderir!

Editado por iNosuKe 么
Link para o comentário
Compartilhar em outros sites

Participe da discussão

Você pode postar agora e se registrar depois. Se você já tem uma conta, acesse agora para postar com sua conta.

Visitante
Responder esta pergunta...

×   Você colou conteúdo com formatação.   Remover formatação

  Apenas 75 emoticons são permitidos.

×   Seu link foi incorporado automaticamente.   Exibir como um link em vez disso

×   Seu conteúdo anterior foi restaurado.   Limpar Editor

×   Você não pode colar imagens diretamente. Carregar ou inserir imagens do URL.



  • Estatísticas dos Fóruns

    • Tópicos
      152,1k
    • Posts
      651,8k
×
×
  • Criar Novo...