読者です 読者をやめる 読者になる 読者になる

超PHPerになろう

Enjoy PHP Programming

さまざまな環境で統一的にファイルを操作する [league/flysystem]

Flysystemは複数ファイルシステムを透過的に操作できるライブラリです。

ローカルのファイルシステム(UNIX系/Windows)はもとより、zip形式のファイル書庫やFTPWebDAVを経由したリモートディスク、Amazon S3DropBoxのようなクラウドサービスなど、さまざまです。

公式サイト Flysystem - Multiple Filesystems, One API
概要 Abstraction for local and remote filesystems
パッケージ名 league/flysystem
作者 The League of Extraordinary Packages
ライセンス MIT License
バージョン v1.0.15 (2015-09-30)

インストール

Composerでインストール可能です。

composer.phar require league/flysystem

また、READMEにはComposerに依存しないインストール方法についても言及があります。

API

どのようなファイルシステムに対しても、以下のようなメソッド呼び出しで利用することができます。

以下は基本的なメソッドを抜萃したもので、すべてのメソッドは公式ドキュメントのAPI - Flysystemを参照してください。

<?php

// ファイルの存在確認
$exists = $filesystem->has('path/to/file.txt');
// ファイルを読み込む
$contents = $filesystem->read('path/to/file.txt');
// ファイルを読み込むストリームを得る
$stream = $filesystem->readStream('something/is/here.ext');
// ファイルに書き込む
$filesystem->write('path/to/file.txt', 'contents');
// ファイルに追記する
$filesystem->put('path/to/file.txt', 'contents');
// ファイルを削除する
$filesystem->delete('path/to/file.txt');
// ファイルを読み込んで削除する
$contents = $filesystem->readAndDelete('path/to/file.txt');
// ファイルをリネーム(移動)する
$filesystem->rename('filename.txt', 'newname.txt');

重要なのはストリームのまま処理できることですかね。数十MB程度ならともかく、数GB以上の巨大なファイルを利用する場合にその全てをメモリに載せてファイルに書き込むのは明らかに非効率なので、stream_copy_to_stream($fs->readStream($f), fopen($target, 'w'));のようなイディオムで記述すれば効率よく処理できる、気がする。

Adapter

Flysytemは「Adapter」と呼ばれる機構で、さまざまなファイルシステム/プロトコルに対応します。

本稿執筆時点でドキュメントに記載のある対応ファイルシステムは以下の通りです。

Local
Azure
AWS S3 V2
AWS S3 V3
Copy.com
Dropbox
FTP
GridFS
Memory
Null / Test
Rackspace
ReplicateAdapter
SFTP
WebDAV
PHPCR
ZipArchive

なほ、Local/Null/FTP以外は別途インストールが必要になります。

Integrations

Flysystemと各フレームワークをつなぎこむためのインターフェイスも、それぞれ別ライブラリとして提供されてゐます。

たとへばGrahamCampbell/Laravel-Flysystemを利用すると、Laravelのファサードとして利用することができます。

PHPを「シェル化」する [psy/psysh]

Library

PHPのコードを書いて動かして試行錯誤… するのに、わざわざエディタでコードを書き、保存してphpコマンドを起動する… のは非効率です。そんなときはPsySHを使ってみませう。

PsySHはインタラクティブシェルと呼ばれるジャンルのアプリケーションです。対話環境またはREPL(レプル)とも呼ばれます。RubyにおけるIRBやPry、PythonにおけるpythonコマンドやIPythonと同種のツールです。

公式サイト PsySH
概要 An interactive shell for modern PHP.
パッケージ名 Packagist: psy/psysh
作者 bobthecow (Justin Hileman)
ライセンス MIT License
バージョン v0.6.0 (2015-11-08)

インストールする

Composerを利用します。

composer.phar g require psy/psysh

gはglobalの略で、ホームディレクトリ以下に

$HOME/.composer/vendor/bin/にPATHを通しておくと良いです。

.bashrcであれば、以下のように記述します。

PATH=$HOME/.composer/vendor/bin:$PATH

シェルを再起動してpsyshコマンドが起動できればインストール成功です。

PHP標準の対話環境との比較

php -aで起動すると、PHPの対話環境を起動することは可能です。しかし、echovar_dump()などを明示的に呼び出してやらないと計算結果を目視確認できません。また、SyntaxErrorも非常にわかりにくいです。

一方でPsySHでは、計算結果はそのまま画面にわかりやすく表示されます。また、行末にセミコロン;を付けなくても、自動的に判断して補ってくれます。

あなたの開発するWebアプリケーションをシェル化してみよう

こちらはgを付けず、プロジェクト単位でインストールします。

composer.phar require psy/psysh

この例ではcomposer.jsonがあるディレクトリと同じ場所にshellファイルを作ります。

#!/usr/bin/env php
<?php
// ↓名前空間を利用するプロジェクトでは記述しておく
namespace Nyaan;

// Composerのオートローダーを読み込む
require_once __DIR__ . '/vendor/autoload.php';
// ほかに初期化用のPHPファイルがあれば読み込んでおく
// require_once …

echo __NAMESPACE__ . " shell\n";

$sh = new \Psy\Shell();

// シェル起動直後にプロジェクトのnamespaceを設定する
// 名前空間を利用しないプロジェクトでは↓の行は不要
$sh->addCode(sprintf("namespace %s;", __NAMESPACE__));

$sh->run();

// 終了時に表示するメッセージ
echo "Bye.\n";

これで、Composerのオートローダーで設定されたプロジェクト内のファイルやライブラリを利用可能な状態でシェルが起動します。

デバッガとして利用する

<?php

$awesome = new \AwesomeClass;
$result = $awesome->wonderful();

// ↓停止したい場所に書く
eval(\Psy\sh());

起動されたPsySHでは、その場所にある変数をすべて捕捉することが可能です。

設定

設定ファイルは~/.config/psysh/config.phpに保存します。

この記事では詳述しませんが、PsySH#configureを参照のこと。

castersには配列でクラス名とvar-dumper/Caster形式のCasterクラスの対応表を指定でき、画面に見やすく出力することができます。

tabCompletionMatchersで補完処理を追加することができます。

うまく動かないときは

カーソル移動ができない/ショートカットキーが動かない

readline拡張が有効でないと、PsySHのコマンドラインでカーソルキーを入力したときに^[[Dのように妙な文字が出力されます。また、Ctrl-Aなどで行頭移動なども有効になりません。

そのほかのべんり機能

外部プログラムを起動するには [symfony/process]

Library Symfony

PHPスクリプトから他のプログラムを起動して、その出力結果を得たいことがあります。PHPではexecproc_openがあります。しかし、標準出力(stdout)と標準エラー出力(stderr)をうまく制御して出力を取得するのは、実はなかなか面倒だし、マニュアルを読みながら自分で一から実装するのは不毛です。

symfony/processを利用すれば、簡単で安全に外部プログラムを利用できるようになります。

パッケージ名 Packagist: symfony/process
作者 fabpot (Fabien Potencier)
ライセンス MIT License
バージョン v2.7.6 (2015-10-27)

インストール

Composerでインストールできます。

cd /your/project
composer.phar require symfony/process

特徴

  • 標準の機能よりも簡潔で利用しやすい
  • Unix/WindowsなどOSの差異を吸収してくれる
  • バイナリセーフ

つかひかた

動作確認のために、標準出力(stdout)と標準エラー出力(stderr)に書き込むテスト用のスクリプト/tmp/test_outputに用意します。

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

echo "AAAA\n";
fwrite(STDERR, "1111\n");
echo "BBBB\n";
fwrite(STDERR, "2222\n");

呼び出す側のコードは以下のようになります。

<?php
use Symfony\Component\Process\Process;

$process = new Process('php /tmp/test_output');
$process->run();

// 標準出力の内容を取り出す
$stdout = $process->getOutput();
//=> "AAAA\nBBBB\n"

// 標準エラー出力の内容を取り出す
$process->getErrorOutput();
//=> "1111\n2222\n"

タイムアウトを指定したい

プロセス起動前に$process->setTimeout()を読んでタイムアウト時間を指定すると、ProcessTimedOutExceptionが送出されるようになります。

<?php
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessTimedOutException;

// 1秒待ったあとで "123" を出力する
$process = new Process(' php -r" sleep(1); echo 123; " ');

// 処理に0.5秒以上かかったら停止する
$process->setTimeout(0.5);
try {
    $process->run();
} catch (ProcessTimedOutException $e) {
    echo "停止しました。";
}

var_dump($process->getOutput());
//=> NULL

プロセスが停止されてなければ123が出力されてるはずですが、タイムアウト指定がきっちり機能してるようです。

正常終了しなければ例外

外部プログラムが異常終了すると作業を続行できないような処理では$process->mustRun()で実行すると、終了ステータスが0以外のときProcessFailedExceptionを送出するようになります。

<?php
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;

$process = new Process('php -r" exit(1); "');
try {
    $process->mustRun();
} catch (ProcessFailedException $e) {
    echo $e->getMessage();
    exit(1);
}

例外が発生するケースが極めて稀な場合は、try-catchで括らずプログラム全体を異常停止させた方が良いように感じます。

注意

処理の大小に拘らず、プロセス起動は時間がかかります。特にWebアプリケーションでは何度も起動すると容易にボトルネックとなりえますので、本当に必要なことだけを外部プログラムで処理させるようにするべきです。

データを加工するような処理であれば、PHPで実装されたライブラリが存在しないかPackagistで検索してみると良いかもしれません。

すごいPHPerになるために

べんりなライブラリをいっぱい紹介します ヾ(〃><)ノ゙☆

中のひとはtadsan - QiitaでよくPHPの記事を書いてます。

あと独自フレームワークがだいすきなので、獨自腐麗夢輪惡って名乗ってます☆

超PHPerの歴史

超PHPerの成果物

github.com