Jump to content
Fórum Script Brasil
  • 0

Comportamento de funções geradoras e coroutines - PHP


iNosuKe 么

Question

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?

Edited by iNosuKe 么
Link to comment
Share on other sites

5 answers to this question

Recommended Posts

  • 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"),
        };
    }
}

 

Edited by William Duarte
Link to comment
Share on other 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 to comment
Share on other 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.

Edited by William Duarte
Link to comment
Share on other 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!

Edited by iNosuKe 么
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Answer this question...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



  • Forum Statistics

    • Total Topics
      152.1k
    • Total Posts
      651.9k
×
×
  • Create New...