Les flux

Méconnus et sous-utilisés

Paris, 23 novembre 2015

Appuyez sur [s] pour ouvrir les notes présentateur dans une nouvelle fenêtre.

Utilisez la touche [ESPACE]
pour passer au slide suivant.

Introduction

Objectifs

  • Qu'est-ce qu'un flux ?
  • Ils sont partout !
  • Gestionnaires, filtres, contextes
  • On peut faire des trucs sympa
  • Et même des trucs très sympa

Mais d'abord...

Lecture / écriture

Fichier entier


$str = file_get_contents(__DIR__ . '/data/file-01.txt');
                        

$str = "plop glop !";
file_put_contents(__DIR__ . '/data/file-01-out.txt', $str);
                        

Lecture


$file = fopen(__DIR__ . '/data/file-01.txt', 'r');
if (false === $path) {
    // Ouverture impossible
}

while (!feof($file)) {
    $line = fgets($file);
    echo "-> $line";
}

fclose($file);
                        

Position


$file = fopen(__DIR__ . '/data/file-01.txt', 'r');
if (false === $path) {
    // Ouverture impossible
}
                        

$position = ftell($file);

fseek($file, 10, SEEK_CUR);
fseek($file, -5, SEEK_END);

rewind($file);
                        

fclose($file);
                        

Informations


$exists = file_exists($path);
                        

$size = filesize($path);
$type = filetype($path);
                        

$ctime = filectime($path);
$mtime = filemtime($path);
$atime = fileatime($path);
touch($path, strtotime('10 days ago'));
                        

$owner = fileowner($path);
$group = filegroup($path);
                        

$info = stat($path);
                        

Manipulation de fichiers

  • unlink()
  • rename()
  • copy()
  • tempnam(), tmpfile()

Manipulation de répertoires

  • mkdir()
  • rmdir()
  • opendir(), readir(), closedir(), ...

Bref...

PHP permet de manipuler des fichiers !

Et les flux ?

Un flux ?

  • Une ressource
  • Comportement streamable
  • Lecture ou écriture linéaire
  • Eventuellement : positionnement arbitraire

Mais alors ?

Travailler avec des flux,
c'est comme manipuler
des fichiers ?

Et bien, oui !

Ressource, streamable,

lecture / écriture.

Les flux

  • 10+ ans !
    Depuis PHP 4.3
  • Intégration forte à PHP

Même si on ne s'en rend pas toujours compte !

Mais c'est quoi ???

  • Généralisation d'opérations
    • Accès aux fichiers, au réseau
    • Compression de données
    • ...
  • Qui partagent des usages communs
    • Lecture / écriture
    • Positionnement
    • Mémoire tampon

Représentation

  • Type resource
  • Avec des fonctions qui savent les manipuler
    • stream_*()
    • Nombreuses fonctions de fichiers
  • Pas de classe Stream

Gestionnaires

Wrappers

Référencer un flux

schema://cible

  • schema : nom du gestionnaire
  • cible : dépend du gestionnaire

Par exemple

file:///home/pmartin/test.txt

/home/pmartin/test.txt

Gestionnaires internes

  • http://www.example.net
  • ftp://user:password@example.com/pub/file.txt
  • phar://my.phar/somefile.php
  • glob://examples/*.php
  • compress.zlib://file.gz

Exemple d'utilisation

Copier un fichier

  • Depuis une URL, en HTTP
  • Vers une machine distante, en SCP

Exemple d'utilisation

Approche souvent vue

  • Téléchargement via curl,
  • vers un fichier local
  • ensuite poussé vers le serveur distant,
  • via exec('scp ...')

Exemple d'utilisation

Même chose, en 1 ligne


copy(
  "http://blog.pascal-martin.fr/public/cv/pascal-martin-cv.pdf",
  "ssh2.sftp://user:pass@test.machine.dev:22/home/user/cv.pdf"
);
                        

Avec l'extension ssh2

Gestionnaires communauté

Extensions ou userland

  • SSH / SFTP
  • Git
  • Amazon S3, Azure
  • ...

php://

  • php://stdin, php://stdout, php://stderr
  • php://input, php://output
  • php://memory, php://temp

Cas d'usage

Tester des accès fichiers /o\


function do_something($path)
{
    $file = fopen($path, 'r+');

    // ...

    fclose($file);
}
                        

do_something(__DIR__ . '/data/mon-fichier.txt');
                        

Cas d'usage

Tester des accès flux \o/


$stream = fopen(__DIR__ . '/data/mon-fichier.txt', 'r+');
do_something($stream);
                        

$stream = get_test_stream();
do_something($stream);
                        

function get_test_stream() {
    $stream = fopen('php://memory', 'w+');
    fwrite($stream, "plop glop !");
    rewind($stream);

    return $stream;
}
                        

Autre cas 1/2

Gérer des affichages /o\


function output_smth($str) {
    echo "$str\n";
}
                        

output_smth("Hello, World !");
                        

Autre cas 2/2

Gérer des sorties \o/


function output_smth($stream, $str) {
    fwrite($stream, "$str\n");
}
                        

$out = fopen('php://output', 'w');
output_smth($out, "Hello, World !");
                        

$out = fopen('file://' . __DIR__ . '/data/out.txt', 'w');
output_smth($out, "Hello, World !");
                        

Créer un gestionnaire

php.net/class.streamwrapper


class FluxWrapper
{
    public $context;

    public function stream_open ($path , $mode , $options , & $opened_path) { }
    public function stream_read($count) {}
    public function stream_write($data) {}
    public function stream_eof() {}
    // ...
}
                        

Créer un gestionnaire


if (stream_wrapper_register(
        'flux', FluxWrapper::class
    ) === false) {
    // ...
}

$stream = fopen('flux://plop', 'r');
if (!$stream) { /* ... */ }

// Utilisation du flux...

fclose($stream);
                        

Filtres

Filtre de flux

  • Modification de données au passage
  • En lecture, ou en écriture
  • stream_filter_append(), stream_filter_prepend() et stream_filter_remove()

Exemple de filtrage


$s = fopen('php://output', 'w');

$rot = stream_filter_append($s, 'string.rot13');
$low = stream_filter_append($s, 'string.tolower');
fwrite($s, "Hello, World!\n");
                        

uryyb, jbeyq!
                        

Quelques filtres

  • string : rot13, tolower, toupper
  • convert : base64-encode, base64-decode, iconv, ...
  • zlib : inflate, deflate
  • bzip2 : decompress, compress

Créer un filtre 1/2


class MyFilterToUpper extends php_user_filter {
    public function filter($in, $out, & $consumed, $closing) {
        while ($bucket = stream_bucket_make_writeable($in)) {
            $bucket->data = strtoupper($bucket->data);
            $consumed += $bucket->datalen;
            stream_bucket_append($out, $bucket);
        }

        return PSFS_PASS_ON;
    }
}
                        

C'est pas très drôle...

Créer un filtre 2/2


// Enregistrement du filtre
if (stream_filter_register(
        'mytoupper', MyFilterToUpper::class
    ) === false) {
    // ...
}
                        

// Ajout du filtre à ceux portant sur le flux
stream_filter_append($stream, 'mytoupper');
                        

Manipulations avancées

Contexte de flux

Paramétrage du comportement

  • Ensemble d'options
  • Reçu en paramètre par de nombreuses fonctions qui travaillent sur des flux
  • Créé avec stream_context_create()

Contexte de flux 1/2


$options = [
    'http' => [
        'protocol_version' => '1.1',
        'method' => 'GET',
        'header' => "Accept-language: fr\r\n",
        'user_agent' => "Mon client d'API",
        'timeout' => 15,
        'ignore_errors' => true,
    ],
];
$params = [];

$context = stream_context_create($options, $params);
                        

Contexte de flux 2/2

Paramètre optionnel des fonctions de flux


$url = "http://api.openweathermap.org/data/2.5/weather?"
        . http_build_query([
            'q' => "Lyon,FR", 'APPID' => API_KEY,
        ], null, '&');

// 3ème paramètre de file_get_contents()
$str = file_get_contents($url, false, $context);

$data = json_decode($str, true);
                        

Contexte par défaut


$default = stream_context_get_default();

$default = stream_context_set_default([
    'http' => [
        'protocol_version' => '1.1',
        'method' => 'GET',
        'header' => "Accept-language: fr\r\n",
        'user_agent' => "Mon client d'API",
        'timeout' => 15,
        'ignore_errors' => true,
    ],
]);
                        

Options disponibles

Dépendent du gestionnaire utilisé

php.net/context

Le retour de la copie !


$context = stream_context_create([
    'ssl' => [
        // /!\ UNIQUEMENT EN DEVELOPPEMENT /!\
        'verify_peer' => false,
        'verify_peer_name' => false,
        'allow_self_signed' => true,
    ],
    'ssh2' => [
        'username' => "squale",
        'pubkey_file' => "/.../.ssh/id_rsa.ks2.pub",
        'privkey_file' => "/../.ssh/id_rsa.ks2",
    ],
]);
	                    

Le retour de la copie !


copy(
    "https://.../public/cv/pascal-martin-cv.pdf",
    "ssh2.sftp://.../home/user/cv.pdf",
    $context
);
	                    

Le retour de la copie !


$from = "https://.../public/cv/pascal-martin-cv.pdf";
$to = "ssh2.sftp://.../home/user/cv.pdf";

$fin = fopen($from, 'r', false, $context) or die("Fail: in\n");
$fout = fopen($to, 'w', false, $context) or die("Fail: out\n");

// Ici, des filtres, si je veux ;-)

stream_copy_to_stream($fin, $fout);

fclose($fin);
fclose($fout);
	                    

Et encore ?

  • stream_is_local($uri_or_stream)
  • stream_set_blocking($stream, $mode)
  • stream_set_read_buffer($stream, $buffer) et stream_set_write_buffer($stream, $buffer) et stream_set_chunk_size($stream, $size)
  • stream_resolve_include_path($filename)

Flux socket

Client / serveur

  • Serveur
    • stream_socket_server()
    • stream_socket_accept()
  • Client
    • stream_socket_client()
  • Manipulation : ce sont des flux ;-)

Serveur simpliste


$server = stream_socket_server('tcp://127.0.0.1:8080');
if (false === $server) { /* ... */ }

while (($conn = stream_socket_accept($server, -1))) {
    $str = "Hello, World!";
    fwrite($conn, "HTTP/1.1 200 OK\r\n");
    fprintf($conn, "Content-Length: %d\r\n", strlen($str));
    fwrite($conn, "\r\n");
    fwrite($conn, $str);
    fclose($conn);
}
                        

Client simpliste


$ip = gethostbyname('example.org');
$client = stream_socket_client("tcp://$ip:80");
if (false === $client) { /* ... */ }

fwrite($client, "GET / HTTP/1.1\n\r");
fwrite($client, "Host: example.org\n\r");
fwrite($client, "User-agent: Some agent\n\r");
fwrite($client, "\n\r");

while (!feof($client)) {
    echo fgets($client);
}

fclose($client);
                        

Truc sympa ;-)

Flux de type socket peuvent être non-bloquants

Serveur de chat 1/2

Le gros de la logique


$server = stream_socket_server("tcp://127.0.0.1:8080",
                                $errno, $err);
if (false === $server) { /* ... */ }

$clients = [];
while (true) {
    $reads = array_merge($clients, [$server]);
    $writes = $excepts = [];

    if (stream_select($reads, $writes, $excepts, 1, 0) === false) {
        // Timeout
    }

    foreach ($reads as $read) {
        // Cf slide suivant ;-)
    } // foreach $reads
} // while true

fclose($server);
                        

Serveur de chat 2/2

Le corps de la boucle


if ($read === $server) {
    if (($new_client = stream_socket_accept($server))) {
        printf("+Client: %s\n", stream_socket_get_name($new_client, true));
        $clients[] = $new_client;
    }
}
else if (($str = trim(fread($read, 1024))) !== '') {
    printf("[%s] Server received: %s\n", date('H:i:s'), $str);
    foreach ($clients as $client) {
        fprintf($client, "> %s\n", $str);
    }
}
                        

Asynchrone en PHP

Conclusion

Les flux

  • Composant central de PHP
  • On les utilise tous les jours...
    ...sans le savoir !
  • Manquent un peu d'amour ;-(
  • Permettent des trucs sympa ;-)

Pascal MARTIN

blog.pascal-martin.fr
contact@pascal-martin.fr
@pascal_martin

TEA, The Ebook Alternative

bit.ly/stage-tea-2016

bit.ly/php-ext-afup

  • 500+ pages
  • 20 chapitres
    • Env. développement
    • Notions de base
    • Classes, objets
    • Flux
    • Tests automatisés
    • ...
  • $25.00 $15.00