% sudo pear install PHPDocumentor
Password:
downloading PhpDocumentor-1.2.3.tgz ...
Starting to download PhpDocumentor-1.2.3.tgz (2,656,621 bytes)
......................................................................................done: 2,656,621 bytes

Fatal error: Allowed memory size of 8388608 bytes exhausted (tried to allocate 8192 bytes) in /usr/share/php/PEAR/Installer.php on line 316

Fatal error: Allowed memory size of 8388608 bytes exhausted (tried to allocate 35 bytes) in /usr/share/php/System.php on line 133

PHPDocumentorがインストールに標準の8M以上のメモリを必要するのは何とかした方が良いと思った。

Debian/Ubuntuの場合は/etc/php5/cli/php.iniを変える。

;memory_limit = 8M      ; Maximum amount of memory a script may consume (8MB)
memory_limit = 16M

PHPとJavascriptでFeedParser(のラッパー)を作ってて思った。

いくつかフォーマットがあるFeedを統一的に扱うものを作るってことでStrategyパターンぽいのが思い浮かんだ。

parser

こんな感じの。

Javaだったら大体こんな感じで書くと思う。 でもこれ、Javascriptで書いてるとすごいダサい感じがしてくる。そこでもっとシンプルな形に変えた。

Java or Javascriptだとこの辺は直観で切替えられるが、PHPだとstrictな感じで書いた方が良いのか、lightな感じで書いた方がいいのか悩む。(特にに5)

正直、大勢より1?2人ぐらいでプログラムを作りたいので後者の方が好きだ。 下記の様なもっとlightなPHPの書き方があってもいいんじゃないか。

  • E_ALL & ~E_NOTICE前提。
    (issetとか使わない。変数を宣言しない)
  • 名前空間を平気で汚す。
    (関数を一杯定義したり)
  • 設定やデータ構造はphpのArray
    (xmlやyamlやiniじゃなく)
  • 継承を多用(5ではなるべくprotected)
    (差分プログラムのためだけにでも使う)
  • 深く考えずeval, create_functionを使う
  • preg_*を多用
  • 引数に迷ったらすぐcall_user_func_array, func_get_argsを使う

途中から単に通常やってはいけないことリストになってきた…。

付録 A. PHP 4 用の PHPUnit

PHP 4 用の PHPUnit には、TextUI テストランナーがありません。 PHP 4 用の PHPUnit でテストを実行する際の一般的な方法は、 テストスイートを書いた後で、それを 例 A.2 のように手動で実行することです。

高木さんの涙が出る程ありがたい翻訳マニュアル。

ところで、

「PHP4用のPHPUnitにはTextUIテストランナーがありません。」

この事実、以前辿り着くのが結構大変だった覚えがあります。ありそうなんでドキュメントやソースをかなり漁っちゃうんですよね。 車輪の再開発の嫌い過ぎも瞬発力が無くなっちゃいますね。

faviconのエントリはてブもらったので調べてみました。

WWW::Blog::Metadata::Icon – Extract icon (FOAF photo, favicon) from weblog – search.cpan.org

DESCRIPTION

WWW::Blog::Metadata::Icon is a plugin for WWW::Blog::Metadata that attempts to extract photos/icons for a weblog author. It looks in three places:

1. a FOAF file, from either an img or depiction element. 2. a shortcut icon in a tag in the document. 3. a HEAD check on $uri/favicon.ico.

Ben TrottのWWW::Blog::Metadata::Iconってmoduleで取れるそうです。

sub on_got_tag {
    my $class = shift;
    my($meta, $tag, $attr, $base_uri) = @_;
    if ($tag eq 'link' && $attr->{rel}) {
        my %rel = map { $_ => 1 } split /\s+/, lc $attr->{rel};
        if ($rel{icon}) {
            $meta->favicon_uri(URI->new_abs($attr->{href}, $base_uri))->as_string;
        }
    }
}

sub on_finished {
    my $class = shift;
    my($meta) = @_;
    $meta->icon_uri($meta->foaf_icon_uri || $meta->favicon_uri);
}

おお、すげー参考になる。foafのiconも見るとこがビリッとしてんなー。 これのPHP4版無いのかな。

OKWebでの質問返ってこなかった…。

………よしッ!!!(リアクション間違い)

宣言通りしようがなくJavascriptのParser自分で書いた。

本当に泥臭いから覚悟しろ!

使い方:

<html>
<head>
<script src="scripts/ObjTree.js" type="text/javascript"></script>
<script src="scripts/prototype.js" type="text/javascript"></script>
<script src="scripts/feed.js" type="text/javascript"></script>
<script language="javascript">
var url = 'http://d.hatena.ne.jp/m-komagata/rss';        // RSS1.0
//var url = 'http://d.hatena.ne.jp/m-komagata/rss2';   // RSS2.0
//var url = 'http://p0t.jp/mt/atom.xml';                     // Atom0.3
//var url = 'http://komagata.blogspot.com/atom.xml';  // Atom0.3

url = 'proxy.php?uri=' + url;
new Ajax.Request(url, {
  onComplete : function(req) {
    var feed = new Feed(req.responseText);
    feed.parse();
    var list = '';
    feed.getItems().each(function(item) {
      list += '<h2>' + item.title + '</h2>';
      list += '<p>' + item.link + '</p>';
      list += '<p>' + item.description + '</p>';
      list += '<p>' + item.date + '</p>';
    });
    Element.update('list', list);
  }
});
</script>
</head>

<body>
<div id="list" ></div>
</body>
</html>

パーサー:

var Feed = Class.create();
Feed.prototype = {
  initialize : function(data) {
    this.data = data
  },
  parse : function() {
    var xotree = new XML.ObjTree();
    var tree = xotree.parseXML(this.data);
    this.data = tree;

    var kind = this.getKind();
    eval('this.parser = new ' + kind);
    this.parser.data = this.data;
    this.parser.kind = kind;
  },
  getKind : function() {
    var kind;
    if (this.data['rdf:RDF'] &&
    this.data['rdf:RDF']['-xmlns'] == 'http://purl.org/rss/1.0/') {
      kind = 'RSS10';
    } else if (this.data['rss'] && this.data['rss']['-version'] == '2.0') {
      kind = 'RSS20';
    } else if (this.data['feed'] && this.data['feed']['-version'] == '0.3') {
      kind = 'Atom03';
    } else {
      kind = 'UnknownFeed';
    }
    return kind;
  },
  getTitle : function() {
    return this.parser.getTitle();
  },
  getLink : function() {
    return this.parser.getLink();
  },
  getItems : function() {
    return this.parser.getItems();
  },
  getJSON : function() {
    return {
      title : this.getTitle(),
      link : this.getLink(),
      items : this.getItems()
    };
  }
};

var RSS10 = Class.create();
RSS10.prototype = {
  initialize : function() {},
  getTitle : function() {
    return this.data['rdf:RDF'].channel.title;
  },
  getLink : function() {
    return this.data['rdf:RDF'].channel.link;
  },
  getItems : function() {
    var items = this.data['rdf:RDF'].item;
    var res = [];
    items.each(function(item) {
      item['date'] = item['date'] || item['dc:date'];
      res.push(item);
    });
    return res;
  }
};

var RSS20 = Class.create();
RSS20.prototype = {
  initialize : function() {},
  getTitle : function() {
    return this.data['rss'].channel.title;
  },
  getLink : function() {
    return this.data['rss'].channel.link;
  },
  getItems : function() {
    var items = this.data['rss'].channel.item;
    var res = [];
    items.each(function(item) {
      item['date'] = item['date'] || item['dc:date'] || item['pubDate'];
      res.push(item);
    });
    return res;
  }
};

var Atom03 = Class.create();
Atom03.prototype = {
  initialize : function() {},
  getTitle : function() {
    var title =  this.data['feed'].title;
    if (typeof(title) == 'string') {
      return title;
    } else {
      return title['#text'];
    }
  },
  getLink : function() {
    var link = this.data['feed'].link;
    var res = '';
    if (!link['-href']) {
      link.each(function(ln) {
        if (ln['-rel'] == 'alternate') {
          res = ln['-href'];
        }
      });
    } else {
      res = this.data['feed'].link['-href'];
    }
      return res;
  },
  getItems : function() {
    var items = this.data['feed'].entry;
    var res = [];
    items.each(function(item) {
      var title = item['title'] = item.title;
      if (typeof(title) == 'string') {
        item['title'] = title;
      } else {
        item['title'] = title['#text'];
      }
      item['date'] = item['date'] || item['dc:date'] || item['created'];
      if (typeof(item.link['-href']) != 'string') {
        item.link.each(function(ln) {
          if (ln['-rel'] == 'alternate') {
            item['link'] = ln['-href'];
          }
        });
      } else {
        item['link'] = item.link['-href'] || '';
      }

      if (typeof(item.content['#cdata-section']) == 'string') {
        item['description'] = item.content['#cdata-section'];
      } else {
        item['description'] = item.content.div['#text'];
      }
      res.push(item);
    });
    return res;
  }
};

proxy.phpのソース:

<?php
require_once 'PHP/Compat/Function/file_get_contents.php';
echo @file_get_contents($_GET['uri']);
?>

結果:

feed-result.png

相当泥臭い。大体のフィード読めてるけどいくつか取れないのも。あとでちゃんとRSS/Atomの仕様読んでちゃんとした方法で対応したい。本題のfavicon取る機能はまだ…。

XMLをJSONに変換してくれるXML.ObjTreeが必須。

AwesomeFrameworkってどうなってるの?」って言われたんで最新版(0.9.0)をアップしました。

ちょこちょこいじってはいるんですが、相変わらずぱっと見でわかるぐらいのソースです。

追加機能は以下。

  • defineいじればDocumentRootとかを好きな場所に設定できるようになった。
    (いままでは男らしいフラット管理のみだった。でもまだデフォルトはフラット)
  • 設定ファイルを置けば読んでくれるようになった。
    (別に置かなくてもいい)
  • $R($_REQUEST), $S($_SESSION), $C(設定)とか短くアクセスできるようになった。
  • get関数が追加。
    (テンプレ中で<? if (isset($foo)): ?><?=$foo?><? endif; ?>と書きたくないためだけに生まれた。NOTICE切ればいいだけだが…。)

以下サンプル。

アクション(default.php):

&lt;?php
assign('hello', 'Hello World!!!');
forward("view");
?&gt;

テンプレ(view.php):

&lt;html&gt;
&lt;head&gt;&lt;title&gt;&lt;?=fetch('title')?&gt;&lt;/title&gt;&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;&lt;?=fetch('title')?&gt;&lt;/h1&gt;
&lt;p&gt;&lt;?=$hello?&gt;&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;

コンポーネント(title.php):

Hello World

(コンポーネントはアクションでもなんでもいい)

結果:

&lt;html&gt;
&lt;head&gt;&lt;title&gt;Hello World&lt;/title&gt;&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Hello World&lt;/h1&gt;
&lt;p&gt;Hello World!!!&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;

アプリ作るとき(1人か2人を想定)あと面倒なのってDBとValidation。 DBはこの間のヤツがちゃんとできれば簡単になりそう。Validationは…なんも思いつかない。

以下、全ソース。

&lt;?php
//
// AwesomeFramework - extremely simple framework.
//
// Copyright (C) 2005 Masaki Komagata &lt;komagata@p0t.jp&gt;
//     All rights reserved.
//     This is free software with ABSOLUTELY NO WARRANTY.
//
// You can redistribute it and/or modify it under the terms of 
// the PHP License, version 3.0.
//
define('DEFAULT_PAGE', 'default');
define('VERSION', '0.9.0');
define('EXT', '.php');
define('PARAM', 'p');
define('DOCROOT_DIR', dirname(__FILE__) . '/');
define('ROOT_DIR', DOCROOT_DIR);
define('APP_DIR', ROOT_DIR);
define('LIB_DIR', ROOT_DIR);
define('CONF_FILE', ROOT_DIR . 'config.php');
define('INC_FILE', APP_DIR . 'include.php');
define('DOCROOT_DIR_URI', dirname($_SERVER['SCRIPT_NAME']) . '/');
define('ROOT_DIR_URI', DOCROOT_DIR_URI);

$vars = array();

if (file_exists(CONF_FILE)) {
    include_once CONF_FILE;
}

if (file_exists(INC_FILE)) {
    include_once INC_FILE;
}

session_start();
forward(isset($_REQUEST[PARAM]) ? $_REQUEST[PARAM] : DEFAULT_PAGE);

function forward($page)
{
    $C = &$GLOBALS['C'];
    $R = &$_REQUEST;
    $S = &$_SESSION;
    extract($GLOBALS['vars']);
    include APP_DIR . '/' . $page . EXT;
}

function fetch($page)
{
    ob_start();
    forward($page);
    $buffer = ob_get_contents();
    ob_end_clean();
    return $buffer;
}

function redirect($page, $params = array())
{
    header('Location: ' . url($page, $params));
    exit();
}

function url($page, $params = array())
{
    $param = '';
    foreach ($params as $name =&gt; $value) {
        $param .= '&' . $name . '=' . $value;
    }
    return 'index.php?' . PARAM . '=' . $page . $param;
}

function assign($name, $value)
{
    $GLOBALS['vars'][$name] = $value;
}

function assign_by_ref($name, &$value)
{
    $GLOBALS['vars'][$name] =& $value;
}

function get($name)
{
    if (isset($$name)) {
        return $$name;
    }
}
?&gt;

今日、とても泥臭いソースを書きましたのでご報告申し上げます。

urlからfaviconのurlを取る。

% cat favicon.php
&lt;?php
require_once 'PHP/Compat/Function/file_get_contents.php';
require_once 'XML/XML_HTMLSax.php';

function getFavicon($url)
{
    class FaviconHandler
    {
        var $favicon;
        function openHandler(&$parser, $name, $attrs)
        {
            if ($name  'link' and $attrs['rel']  ‘shortcut icon’) {
                $this->favicon = $attrs[‘href’];
            }
        }
        function closeHandler(&$parser, $data) {}
    }

    $handler = new FaviconHandler();
    $parser =& new XML_HTMLSax();
    $parser->set_object($handler);
    $parser->set_element_handler(‘openHandler’, ‘closeHandler’);

    $doc = file_get_contents($url);
    $parser->parse($doc);

    $favicon = $handler->favicon;

    if (!$favicon) {
        if ($url{strlen($url)-1} != ’/’) {
            $url .= ’/’;
        }
        $favicon =  $url . ‘favicon.ico’;
    }
    return $favicon;
}

echo getFavicon(‘http://del.icio.us/komagata’) ?> % php favicon.php http://del.icio.us/favicon.ico

ヘッダに書いてない場合は何故か無理矢理URLにfavicon.icoをくっ付けて返す。 誰かJavaScript版ください。さもないとまたおれが泥臭いの書くぞ!faviconの決めつけは良くないということで一応、あるかどうか探すように変更した。

$favicon = $link . 'favicon.ico';
if (!@file_get_contents($favicon)) {
    $favicon = false;
}

file_get_contents()はやっぱりイイ!ネット上のファイルの存在確認もわかる!(404だとfalse返ってくるみたい)

Joel on Software
  • Joel on Software
  • オーム社(2005-12)
  • オーム社
  • (著)Joel Spolsky
  • (翻訳)青木 靖
  • 定価:¥ 2,940
  • 新品価格:¥ 2,940
  • 中古価格:¥ 2,600
  • ASIN:4274066304

ここを読んで、さらに教えの通りここを読んで、それからJoel on Softwareをyoshukiさんから借りて読みました。(買っても読み終わったら捨ててしまうからな)

よくある、「これさえ行えばハッピーになれる!」ていう方法論じゃなく、筆者自身のプログラマとしての泥臭い経験談から来る解答が楽しい。GUIプログラム製品をたくさん作ってきた経験に基づくユーザビリティへの考え方はおれの様なチンカスWebプログラマにはとってもためになりました。

ところで、以前から名著・古典を読もうキャンペーン中だったおかげでこの本で引用されてる内容が良くわかって嬉しいです。 以下引用されてる中で読んで良かった名著・古典リスト。

ピープルウエア 第2版 - ヤル気こそプロジェクト成功の鍵

ソフトウェア開発に置ける人間学。知的労働者の生産性に関して引用されてます。

人月の神話―狼人間を撃つ銀の弾はない
  • 人月の神話―狼人間を撃つ銀の弾はない
  • ピアソンエデュケーション(2002-11)
  • ピアソンエデュケーション
  • (著)Jr.,フレデリック・P. ブルックス
  • (原著)Frederick Phillips,Jr. Brooks
  • (翻訳)滝沢 徹
  • (翻訳)富沢 昇
  • (翻訳)牧野 祐子
  • 定価:¥ 3,045
  • 新品価格:¥ 3,045
  • 中古価格:¥ 2,995
  • ASIN:4894716658

古典中の古典。「遅れているプロジェクトに人を投入するとさらに遅れる。」

伽藍とバザール―オープンソース・ソフトLinuxマニフェスト

オープンソース=レイモンドって感じでたびたび名指しされてる。何でもかんでもOSS万歳って姿勢には否定的です。(おれは信者だが)

Microsoft内部の話も結構書き手によって全然違うので楽しい。闘うプログラマーではWindows NT 1.0開発がプロジェクトX並に感動的に描かれてますが、失敗プロジェクトとあっさり…。

言語に関しての話で、シンタックスはどうでもいい。生産性を上げるにはメモリを自動的に開放してくれる言語を選ぶべき。(VB1.0でもやってくれる)ってところは同意。でもそれじゃ最近の言語を選ぶ指標には全然ならん…。メモリ管理以外ではMixin的な機能があるか無いかが結構でかい気がするんだけどどうだろうか。その観点から言うとAggregation系関数が本体に含まれなくなってPHP5は退化? そんなこと言ったら怒られるか。

LaszloLabs – LaszloJapan

Laszloラボは、OpenLaszloアプリケーションの開発者へOpenLaszloサーバーのホスティングを提供します。labs.laszlo.jpではOpenLaszloサーバーと共に、MySQL、JSP、PHP、CGIそしてRubyOnRails(準備中)を利用したOpenLaszloアプリケーションを公開できます。

(゚Д゚)ラズロー

J2EEサーバーも貸してくれんのは太っ腹な感じがする。Tracも使わせてくれればいいのにな。

そろそろ、「何このUbuntuに夢中な無職」と思われそうなのでたまにはPHPのコードを書いてみました。(無職はスルー)

Webアプリで、思い付いた何かを作るのに何がだるいかというと、DB。オブジェクトを次使うときまでとっておきたい(永続化)だけなのに何故こんな面倒臭いか。

設定が要らなくて、ファイルとか吐かないO/Rマッパーはできないものかと思って試しに書いてみた。

&lt;?php
require_once 'DB.php';

class DB_Object_Abstruct
{
    var $_con = null;
    var $_name = "";

    function get($column, $value)
    {
        $row = $this-&gt;_con-&gt;getRow(
            "SELECT * FROM {$this-&gt;_name} WHERE $column = ?" ,
            array($value),
            DB_FETCHMODE_ASSOC
        );
        foreach ($row as $name =&gt; $val) {
            $this-&gt;$name = $val;
        }
    }
}

class DB_Object
{
    var $dsn = "";

    function DB_Object($dsn = "")
    {
        $this-&gt;dsn = $dsn;
    }

    function factory($name)
    {
        eval("class {$name} extends DB_Object_Abstruct { var \$_name = '{$name}'; }");
        $obj =  new $name;
        $con = DB::connect($this-&gt;dsn);
        $obj-&gt;_con = $con;
        return $obj;
    }
}
?&gt;

まだgetメソッドだけ。 これを、

&lt;?php
require_once 'DB/Object.php';

$dbo = new DB_Object($dsn);
$user = $dbo-&gt;factory('user');
$user-&gt;get('id', 1);
print_r($user);
?&gt;

こうやって使う。