/dev/null

脳みそのL1キャッシュ

PHPでCLIツールを書いてみる

はじめに

誰得だが、あまりPHPCLIツールを書く記事が見つからなかったので書いてみました。本記事では、以下の3種類のCLIツールを書いてみます。

  1. コマンドライン引数で受け取ったファイルの中身を出力(catコマンドもどき)
  2. コマンドライン引数で受け取ったURLにアクセスしてリソースをダウンロード(curlコマンドもどき)
  3. コマンドライン引数で受け取ったPHPファイルの抽象構文木(AST)を出力

例: catコマンド

あーだこーだいうより、コードを見せた方が早いでしょう。

#!/usr/bin/env php
<?php

function dump($filename) {
  $handle = fopen($filename, "r") or die("ファイルのオープンに失敗しました");

  while (($buffer = fgets($handle, 4096)) !== false) {
    echo $buffer;
  }

  fclose($handle);
}

$argv = array_slice($argv, 1);
if (count($argv) < 1) {
  print "ファイル名を指定してください\n";
  exit(1);
}

foreach ($argv as $filename) {
  dump($filename);
}

特に引っかかったところはないです。ポイントを強いて挙げるとするなら、インタプリタ絶対パスを指定せず、/usr/bin/envを使ってインタプリタ$PATHから検索しているところでしょうか。

実行してみます。

$ ./cat ./hello.php ./cat
<?php

echo "Hello\n";
#!/usr/bin/env php
<?php

function dump($filename) {
  $handle = fopen($filename, "r") or die("ファイルのオープンに失敗しました");

  while (($buffer = fgets($handle, 4096)) !== false) {
    echo $buffer;
  }

  fclose($handle);
}

$argv = array_slice($argv, 1);
if (count($argv) < 1) {
  print "ファイル名を指定してください\n";
  exit(1);
}

foreach ($argv as $filename) {
  dump($filename);
}

普通にCLIツールが書けるんですね。普段はphp-fpmやらmod_phpでしかPHPインタプリタにアクセスしないので、少し新鮮味を感じました。

例: curlコマンド

#!/usr/bin/env php
<?php

function curl($url) {
  $ch = curl_init($url);

  curl_setopt($ch, CURLOPT_FILE, STDOUT);
  curl_setopt($ch, CURLOPT_HEADER, 0);

  curl_exec($ch);

  if(curl_error($ch)) {
    fwrite($fp, curl_error($ch));
  }

  curl_close($ch);
}

$argv = array_slice($argv, 1);
if (count($argv) != 1) {
  print "URLを指定してください\n";
  exit(1);
}

curl($argv[0]);

PHPの標準ライブラリで用意されているlibcurlのラッパーを使いました。使い方はここに書いてます。

実行してみます。

$ ./curl http://example.com
<!doctype html>
<html>
<head>
    <title>Example Domain</title>

    <meta charset="utf-8" />
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style type="text/css">
    body {
        background-color: #f0f0f2;
        margin: 0;
        padding: 0;
        font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;

    }
...

PHP標準ライブラリを見てみると、割と充実していて(libSVMのラッパーもある)、リッチなCLIツールがかけそうだなという印象を受けました。

例: dump-astコマンド

このコマンドはphp-astが必要です。実際に試してみたい方は、ここここを参考にphp-astをインストールしてください。

#!/usr/bin/env php
<?php

function dumpAst($filename) {
  $handle = fopen($filename, "r") or die("ファイルのオープンに失敗しました");

  $code = "";
  while (($buffer = fgets($handle, 4096)) !== false) {
    $code = $code . $buffer;
  }

  fclose($handle);

  var_dump(ast\parse_code($code, $version=70));
}

$argv = array_slice($argv, 1);
if (count($argv) != 1) {
  print "ファイル名を指定してください\n";
  exit(1);
}

dumpAst($argv[0]);

実行してみます。

$ cat hello.php
<?php

echo "Hello\n";
$ ./dump-ast ./hello.php
object(ast\Node)#1 (4) {
  ["kind"]=>
  int(132)
  ["flags"]=>
  int(0)
  ["lineno"]=>
  int(1)
  ["children"]=>
  array(1) {
    [0]=>
    object(ast\Node)#2 (4) {
      ["kind"]=>
      int(282)
      ["flags"]=>
      int(0)
      ["lineno"]=>
      int(3)
      ["children"]=>
      array(1) {
        ["expr"]=>
        string(6) "Hello
"
      }
    }
  }
}

こんな感じで、静的解析ツールも作れそうです。何かの機会に、PHPソースコードを静的解析して遊んでみる記事でも書こうと思います。(PHPStanPhanなどの静的解析ツールがすでにあるので、自分で静的解析ツールを書くメリットがあまりないような気もしますが…)

おわりに

手続き的すぎる… 自分が知っているPHPCLIツールにartisanlaravelコマンドがあるので、次は、それらのコマンドを参考にして、よりよいCLIツールの書き方を探っていきたいです。

参考文献

qiita.com

qiita.com

github.com