🛠️

PHPでもパイプで処理したい

2022/09/18に公開

bashやelixirにはパイプがある
phpでもそうしたい

bash

例えばbash
こういうcsvがあったとして

cat sample.csv
title,value,impression,release_date
Rustプログラミング入門,4000,微妙,2021
詳解Rustプログラミング,3600,良い,2021
並行プログラミング入門,3200,かなり良い、難しい,2021
プログラミングRust第二版,4800,かなり良い,2021

valueが4000以下の二つのデータの合計値に10%加算したものを算出しようと思うと

one line
sed 1d sample.csv|awk -F ',' '$2<4000'|awk -F ',' '{print $0}'|awk -F ',' '{print $2*1.1}'|xargs |sed 's/\ /+/g'|bc

7480

のようなコマンドを打つだけでOK
※awkを二回打っているのはこの後のコードに寄せる為なので気にしないように

php

しかしphpにパイプはない
そこでパイプっぽく書けるようにしてみる

パイプ用のクラスを作る

pipe.php
<?php
class Pipe{
    private function __construct(private $resource){
    }
    public static function resource($resource){
        return new self($resource);
    }
    public function then($fn) {
        return new self( $fn($this->resource));
    }

    public function __call($name, $arguments){
        return new self (call_user_func($name, $this->resource, ...$arguments));
    }
    public function fin(){
        return $this->resource;
    }
}

__call はオマケで用意したがマジックメソッドなので無い方が良い、しかし今回は例という事で用意した

例1

example01.php
$source = 'abcdefgABCDEFG';
$calc = Pipe::resource($source)
->then(fn($resource)=>substr($resource, 6))
->then(fn($resource) => substr($resource,1))
->then(function($resource){
    return strtolower($resource);
})->strtoupper()
->substr(0,2)
->fin();

var_dump($calc);

string(2) "AB"

処理内容は

  1. パイプのソースとしてabcdefgABCDEFGを渡す
  2. substr6番目以降の文字を切り出す
  3. substrで1番目以降の文字を切り出す
  4. strtolowerで全部小文字にする
  5. strtoupperで全部大文字にする
  6. substrで0番目から2場面の文字を切り出す
  7. 結果を取得する

__callを実装しているから->substr(0,2)のような書き方も出来るけど、全てthenで書く方が良いだろう

例2

bashの例で使ったcsvに対する処理の例

example02.php
$csv = new SplFileObject("sample.csv");
$csv->setFlags(SplFileObject::READ_CSV|SplFileObject::SKIP_EMPTY|SplFileObject::DROP_NEW_LINE);
$result = Pipe::resource($csv)
->then(function($spl){
    //arrayにする
    $ret=[];
    foreach($spl as $row){
        $ret[] = $row;
    }
    return $ret;
})->then(function($v){
    //headerの除去
    array_shift($v);
    return $v;
})->array_filter(fn($v)=>$v[1]<4000) //4000円以下
->array_values()//indexの降り直し
->then(fn($v)=>array_map(fn($row)=>$row[1]*1.1, $v))//怒りの10%追加
->array_sum()//金額の合計
->fin();
echo $result;

7480

※こっちはソース中に処理の説明を書きました

__call が使えない理由のひとつは、組み込み関数array_maparray_filterの引数の順番の違いのようなものがあるから
全ての関数の一番目の引数はリソースとなる値が来るようにして欲しいし、そうじゃない関数はなんでそうしなかったんだろうかと不思議で仕方がない

あとがき

例のthenの書き方だけで良いから全php界で使われると良いなと思う
処理の意図を追いやすいし、技術力や経験に関わらずスコープを意識させずにスコープを意識したコードを強制出来るから

Discussion