ごもちゃ箱BLOG

自作の PHP クラスライブラリ PEACH の開発日記です。

DT に関するいろいろ

今回は, 自作の時間操作用クラスライブラリ (PHP の Datetime クラスの高機能版のようなもの) の紹介をします.
以下は現在開発中の PEACH ver.2 での書き方ですが, ver.1 系でもクラス名が違うくらいで内容はほとんど同じです.

フィールドの取得

こんな感じで, 時間オブジェクトの get メソッドの引数に文字列を指定することで任意のフィールドの値を取得することができます.

<?php
require_once("/path/to/PEACH2/src/autoload.php");
use Peach\DT\Timestamp;

$t = Timestamp::now();
var_dump($t->get("year"));
var_dump($t->get("month"));
var_dump($t->get("date"));
var_dump($t->get("hour"));
var_dump($t->get("minute"));
var_dump($t->get("second"));

出力

int(2014)
int(8)
int(19)
int(21)
int(40)
int(50)

時間の操作

月初・月末・日付や時刻の加減などの操作もかなり直感的!

<?php
require_once("/path/to/PEACH2/src/autoload.php");
use Peach\DT\Timestamp;

$now   = Timestamp::now();
$first = $now->set("date", 1); // 今月の 1 日
$last  = $now->set("date", $now->getDateCount()); // 今月の末日

var_dump($first->format());
var_dump($last->format());

出力

string(19) "2014-08-01 21:40:50"
string(19) "2014-08-31 21:40:50"

比較

比較のためのメソッドとして before(), after(), equals() などああります. Java の Calendar クラスと同じような使い勝手です.

<?php
require_once("/path/to/PEACH2/src/autoload.php");
use Peach\DT\Timestamp;

$t1 = new Timestamp(2014, 8, 19, 20, 30, 40); // 8 月 19 日 20:30:40
$t2 = new Timestamp(2014, 7, 31, 12, 34, 56); // 7 月 31 日 12:34:56

var_dump($t1->after($t2));
var_dump($t1->before($t2));

出力

bool(true)
bool(false)

日付だけを比較したい!

とある 2 つの時刻について, 日付が同じかどうかだけ調べたいという需要はよくあると思うのですが (少なくとも自分は過去に何度かあった), DT モジュールでは Date クラスにキャストすることで簡単に調べることができます.

<?php
require_once("/path/to/PEACH2/src/autoload.php");
use Peach\DT\Timestamp;

$t1 = new Timestamp(2014, 8, 19, 20, 30, 40); // 8 月 19 日 20:30:40
$t2 = new Timestamp(2014, 7, 31, 12, 34, 56); // 7 月 31 日 12:34:56
$t3 = new Timestamp(2014, 8, 19, 21, 0, 0);   // 8 月 19 日 21:00:00 

var_dump($t1->toDate()->equals($t2->toDate()));
var_dump($t1->toDate()->equals($t3->toDate()));

出力

bool(false)
bool(true)

PEACH ライブラリの近況

自作の汎用 PHP クラスライブラリ "PEACH" がリリースされてから早くも 2 ヶ月が経ちました. (詳細はこちら: http://trashtoy.github.io/peach/ )

ver 1.0.0 は対象を PHP 5.1.1 以上としていて, さすがにもうレガシーすぎるだろうということで, ver 1.0.0 のリリース後すぐに PHP 5.3.0 以上を対象とした ver 2.0.0 の作成を進めていました. もうそろそろリリースできそうです.
今後は 1 系と 2 系のコードを同時並行で保守していくことになりそうです.

PEACH を構成する各ライブラリのうち, DT (Date and Time) モジュールに関する説明をこのブログ上でほとんどしてこなかったので, 今日から少しずつ書いていこうという経緯で今回これを書きました.
まだまだ紹介したいことはたくさんあるので, 近いうちにまた別のサンプルを掲載したいと思います.

汎用PHPクラスライブラリ PEACH の ver 1.0.0 をリリースしました

詳しくはコチラを参照してください!

ドキュメント
XML, HTML 出力支援モジュール Markup
ソースコードのダウンロード (ZIP)
https://github.com/trashtoy/PEACH/archive/1.0.0.zip

What's new?

今回、新しく Markup というモジュールを作りました。文字通り HTML や XML の出力をサポートします。
例えば HTML を出力する場合のサンプルコードはこんな感じ。

<?php
require_once("/path/to/PEACH/src/autoload.php");

Peach_Markup_Html::alias(); // 関数 tag() を定義
echo tag("p")->append("This is test")->write();

このコードは以下の文字列を出力します。

<p>This is test</p>

jQuery みたいにサクサク使えます。

<?php
require_once("/path/to/PEACH/src/autoload.php");

Peach_Markup_Html::alias(); // 関数 tag() を定義
$div = tag("div")->attr("class", "test");
tag("p")->append("This is test")->appendTo($div);
tag("ul")
    ->append(tag("li", ["id" => "first"])->append("Hogehoge"))
    ->append(tag("li", ["id" => "second"])->append("Fugafuga"))
    ->append(tag("li", ["id" => "third"])->append("Piyopiyo"))
    ->appendTo($div);

echo $div->write();
?>

このコードは以下を出力します。

<div class="test">
    <p>This is test</p>
    <ul>
        <li id="first">Hogehoge</li>
        <li id="second">Fugafuga</li>
        <li id="third">Piyopiyo</li>
    </ul>
</div>

インデントは、あたかも手書きでコーディングした感じに自動的に調整されます。改行場所を細かくカスタマイズすることもできます。

きっかけ

作ったきっかけなんですが、例えばこんな感じの PHP と HTML のインデントが入り混じったソースをメンテしていて嫌気が差したり

<?php if ($users): ?>
    <?php if ($isTable): ?>
<table>
    <tr>
        <th>ID</th>
        <th>名前</th>
    </tr>
        <?php foreach ($users as $user): ?>
    <tr>
        <td><?php echo $user->getID() ?></td>
        <td><?php echo $user->getName() ?></td>
    </tr>
        <?php endforeach; ?>
</table>
    <?php else: ?>
<ul>
        <?php foreach ($users as $user): ?>
    <li><?php echo $user->getName(); ?></li>
        <?php endforeach; ?>
</ul>
    <?php endif; ?>
<?php endif; ?>

タグの属性の部分がこんな感じにごちゃごちゃしたり (条件に応じて readonly 属性を付与する部分とか、特に)

<p><input type="text" name="hoge"
    value="<?php echo $value; ?>"<?php if ($isPreview): ?> readonly<?php endif; ?>>

typo のせいで HTML が invalid になったり、などなど。

HTML と PHP を混在させずに、全部 PHP で出力させてしまえば良いのだ!と思い、自分にとって理想的な API を作りました。
欠点は WEB デザイナーさんが手を入れにくくなってしまうことかな。

FAQ

PHP 5.5 がリリースされて、もうすぐ PHP 5.6 が出てくるだろうという時期なのに、namespace 対応してないの?

今回リリースしたバージョン 1.0.0 は、対応スペックを PHP 5.1.1 以上としているので……。
namespace 以外にもクロージャとか Generator とか使いたい箇所は多々あるんですが。
これも制作スピードが遅すぎるせいです。ちなみに作り始めたのは今から 4 年前くらい。

ver 2.0.0 では対象バージョンを PHP 5.3.0 以上とし、namespace を使って全クラスを書き直します。

ひええ

前回の記事をアップしてからもう1年以上経ってた。ライブラリの更新が全然できてないなぁ。

フレームワークの中でもわりとレガシーな部類だった CakePHP でさえ、最近 3.0 系になってようやく PHP5.3 以上のサポートになったわけで、PEACH ライブラリも早く namespace に対応せねば。

日付・時間操作用PHPライブラリを作りました

自作の PHP ライブラリの DT パッケージを本日リリースしました。
ドキュメントはコチラからどうぞ。

http://trashtoy.github.com/peach/

力尽きたので詳細は後日書きますが、例えばこんなことが出来ます。

<?php
// timezone = Asia/Tokyo (UTC+9) とします

require_once("/path/to/peach/DT/load.php");

$today     = new DT_Date(2012, 5, 21);
$tomorrow  = $today->add("date", 1);
$yesterday = $today->add("date", -1);
echo $today;     // "2012-05-21"
echo $tomorrow;  // "2012-05-22"
echo $yesterday; // "2012-05-20"

echo $today->get("year");  // "2012"
echo $today->get("month"); // "5"
echo $today->get("date");  // "21"

$d = $today->toDatetime()
           ->add("year", 1)
           ->add("date", -7)
           ->setAll(array("hour" => 12, "minute" => 34));
echo $d; // "2013-05-14 12:34"

/////////////////////

$d1 = DT_Date::now();
$d2 = DT_Datetime::now();
$d3 = DT_Timestamp::now();
echo $d1;        // "2012-12-18"
echo $d2;        // "2012-12-18 22:54"
echo $d3;        // "2012-12-18 22:54:31"

/////////////////////

$f1 = DT_W3cDatetimeFormat::getInstance();
$f2 = DT_HttpDateFormat::getInstance();
$f3 = DT_UnixTimeFormat::getInstance();
 
$d = new DT_Timestamp(2012, 5, 21, 7, 30, 45);
var_dump($d->format($f1)); // string(19) "2012-05-21T07:30:45"
var_dump($d->format($f2)); // string(29) "Sun, 20 May 2012 22:30:45 GMT"
var_dump($d->format($f3)); // string(10) "1337553045"

/////////////////////

$d1 = DT_Timestamp::parse("2012-05-21T07:30:45",           $f1);
$d2 = DT_Timestamp::parse("Sun, 20 May 2012 22:30:45 GMT", $f2);
$d3 = DT_Timestamp::parse(1337553045,                      $f3);

var_dump($d1->equals($d2)); // "bool(true)"
var_dump($d2->equals($d3)); // "bool(true)"
var_dump($d3->equals($d1)); // "bool(true)"

?>

このテーマだと PHP の色分けがちょっと見にくいなぁ。
(※追記) Monokai っぽい感じにしました。参考: http://nikuyoshi.hatenablog.com/entry/20111101/1320166386

PHPでオレオレ時間管理モジュール

気が付いたら最後に日記を書いてからもう2ヶ月以上経ってしまいました。現在、オレオレ時間管理モジュール DT の単体テストをガリガリ書いているところです。

今作っているモジュールについて、コード例を踏まえて特徴を紹介します。

イミュータブル(副作用がない)

まず、デフォルトで定義されている DateTime クラスはこのような使い方をするとおもいます。

$d = new DateTime();
echo $d->format("Y-m-d") . "\n"; // 2012-08-27
$d->modify("-1 day");
echo $d->format("Y-m-d") . "\n"; // 2012-08-26
$d->modify("+2 day");
echo $d->format("Y-m-d") . "\n"; // 2012-08-28

このように modify メソッドによって変数 $d の内部データを書き換えてますが、このモジュールの時間オブジェクトはイミュータブルなので、副作用を気にする必要がありません。上に挙げたのと同じコードは、以下のように書くことができます。変数名を分かりやすく出来ることもポイントです。

$now       = DT_Date::now();
$yesterday = $now->add("date", -1);
$tomorrow  = $now->add("date",  1);
echo $now->format()       . "\n"; // 2012-08-27
echo $yesterday->format() . "\n"; // 2012-08-26
echo $tomorrow->format()  . "\n"; // 2012-08-28

さらに、フィールドを変更するメソッド (add, set など) の返り値が時間オブジェクトになるので、メソッドチェーンもできます。

// 現在から1年後の3日前の日付
$d = DT_Date::now()->add("year", 1)->add("date", -3);
echo $d->format() . "\n"; // 2013-08-24

必要なフィールドだけ扱うことが出来る

既存の DateTime は年・月・日・時・分・秒のすべてのフィールドの値を持っているため, 例えば日付だけの比較を行いたい場合などに、 いちいち時・分・秒を 0 で初期化する必要があったりして面倒な時があるんじゃないかと思うのですが (少なくとも自分はそう)、このモジュールでは日付・日時・秒までの時刻それぞれで別々のクラスを用意しています。

$d1 = DT_Date::now();
$d2 = DT_Datetime::now();
$d3 = DT_Timestamp::now();
echo get_class($d1)  . "\n"; // DT_Date
echo get_class($d2)  . "\n"; // DT_Datetime
echo get_class($d3)  . "\n"; // DT_Timestamp
echo $d1->format()   . "\n"; // 2012-08-27
echo $d2->format()   . "\n"; // 2012-08-27 20:30
echo $d3->format()   . "\n"; // 2012-08-27 20:30:45
echo $d1->get("min") . "\n"; // NULL
echo $d2->get("min") . "\n"; // 30
echo $d2->get("sec") . "\n"; // NULL
echo $d3->get("sec") . "\n"; // 45

別のクラス同士での比較も普通にできます。整数と浮動小数点数を比較するようなイメージです。ただし 2012-08-27 < 2012-08-27T00:00 < 2012-08-27T00:00:00 のような挙動を取る点は少し気をつけたほうが良いかもしれません。

$d1 = new DT_Date(2012, 8, 27);
$d2 = new DT_Datetime(2012, 8, 27, 20, 30);
var_dump($d1->after($d2)); // FALSE
var_dump($d2->after($d1)); // TRUE

オレオレPHPライブラリ紹介(Map編)

先日 GitHub にオレオレ PHP クラスライブラリを公開しました。ぼちぼちと内容紹介していきたいと思います。ダウンロードはこちらから。

trashtoy/PEACH

概要とか

対象ユーザーは、PHP でそれなりにオブジェクト指向な開発をするユーザーです。 静的ページにちょこっとロジックを埋め込む程度だったり、 自分でクラスを作ったりしないようなシステムではあまり使いどころがないと思います。

サンプルコードをいくつか挙げて、どんなことが出来るか紹介したいと思います。

Util_ArrayMap

最近知ったのですが、Symfony に含まれている sfParameterHolder というクラスと同じようなコンセプトです。 例えば配列から値を取り出す際に、キーが存在するかどうかチェックするような場合。

Before

$arr = array("key1" => "hoge", "key2" => "fuga", "key3" => "piyo");
if (isset($arr["key2"])) {
    $x = $arr["key2"];
} else {
    $x = NULL;
}
if (isset($arr["key4"])) {
    $y = $arr["key4"];
} else {
    $y = NULL;
}
var_dump($x); // "fuga"
var_dump($y); // NULL

After

$arr = array("key1" => "hoge", "key2" => "fuga", "key3" => "piyo");
$map = new Util_ArrayMap($arr);
$x   = $map->get("key2");
$y   = $map->get("key4");
var_dump($x); // "fuga"
var_dump($y); // NULL

同じ内容なのにすごくシンプルで分かりやすいですよね!

Util_HashMap

簡単に言うと PHP 版の java.util.HashMap (がちょっと高機能になったクラス) です。

class TestKey {
    private $id;
    private $name;
 
    public function __construct($id, $name) {
        $this->id   = $id;
        $this->name = $name;
    }
 
    public function getId() {
        return $this->id;
    }
 
    public function getName() {
        return $this->name;
    }
}

$map = new Util_HashMap();
$k1  = new TestKey(1, "foo");
$k2  = new TestKey(2, "bar");
$map->put($k1, 100);
$map->put($k2, 200);
 
echo $map->get(new TestKey(1, "foo"), "undef"); // 100
echo $map->get(new TestKey(2, "bar"), "undef"); // 200
echo $map->get(new TestKey(2, "xxx"), "undef"); // "undef"

みたいなことがPHPで出来ちゃいます。どのへんが「ちょっと高機能」かと言うと、キーの等価条件を自由に拡張できるところで、

class TestKeyEquator implements Util_Equator {
    /**
     * 2つの TestKey オブジェクトの id, name がそれぞれ等しい場合に TRUE を返します.
     */
    public function equate($var1, $var2) {
        return ($var1->getId()   === $var2->getId() && 
                $var1->getName() === $var2->getName());
    }
    
    public function hashCode($var) {
        return intval($var->getId());
    }
}
class TestKeyEquator2 implements Util_Equator {
    /**
     * 2つの TestKey オブジェクトの id がそれぞれ等しい場合に TRUE を返します.
     */
    public function equate($var1, $var2) {
        return ($var1->getId() === $var2->getId());
    }
    
    public function hashCode($var) {
        return intval($var->getId());
    }
}

$e1 = new TestKeyEquator();
$e2 = new TestKeyEquator2();
 
$k1 = new TestKey(1, "John Smith");
$k2 = new TestKey(2, "Emily Smith");
$k3 = new TestKey(2, "Emily Johns");
 
$map1 = new Util_HashMap(NULL, $e1); // TestKey の id と name を見て等価条件を確認
$map2 = new Util_HashMap(NULL, $e2); // TestKey の id だけ見て等価条件を確認
 
$map1->put($k1, "foo");
$map1->put($k2, "bar");
$map1->put($k3, "baz");
$map2->put($k1, "foo");
$map2->put($k2, "bar");
$map2->put($k3, "baz"); // $k2 と $k3 が同一視されるので, ここで $k2  "bar" のマッピングが "baz" に上書きされます
 
echo $map1->size(); // => 3
echo $map2->size(); // => 2
 
echo $map1->get($k2); // => "bar"
echo $map2->get($k2); // => "baz"

と、こんな感じに Map の挙動を変えることが出来るわけです。Java の equals や hashCode の制約をそのまま真似ているのでちょっと実装が面倒くさいですけど。

こんな感じで今後も色々紹介したいと思います。ところではてなブログって、手入力で section タグを使っても公開時に取れてしまうみたいなのですが、どうにかならないですかね?

オレオレPHPライブラリを公開してみた

ふと思い立って GitHub で自作の汎用 PHP ライブラリを公開してみました。コチラになります。

trashtoy/PEACH

生い立ちなど

作り始めたのが5~6年前で、いつかは外部に公開したいなーと思いつつダラダラ作り続けていたのですが、ようやく外に出せる品質に達したと判断して公開した次第です。いやー長かった。

そんな経緯もあって、PHP5.3 以上の環境が一般的になってもうだいぶ立ちますがこのライブラリの動作要件は PHP 5.1.1 以上となってます。インストールしたばかりの CentOS5 系 Linux でも動くはずです。たぶん。 namespace もクロージャも各種 SPL インタフェースも使ってません。

そのうち新しくブランチを切って PHP5.3 以上対象のバージョンも出したいなーとは思ってます。

中身について

現バージョンは、Java のコレクション・フレームワークPHP 向けに翻案したような感じの内容になっています。オブジェクト指向大好き人間なので、PHP の配列処理のアレやコレをオブジェクト指向的に実現したいなーという個人的ニーズがあって作りました。

今後は以下のパッケージをリリースする予定です。

  • DT (時間管理モジュール。PHP5.2 から標準サポートされた DateTime クラスが個人的にイケてないので。あと PHP 5.1 系をできる限りサポートしたいので。 時間クラスをイミュータブルで設計しているのが特徴です。作り始めたのは 2005 年頃。)
  • Markup (HTML や XML の出力支援)
  • RB (ResourceBundle の略。Java の ResourceBundle を参考にした、アプリの多言語化機能)

これから LICENSE.txt とかちゃんと整備せねば。。。