komagataのブログ

flickrss

ちょっとFlickrのRSSをユーザー名から取る必要があったのでいちいち作りました。

p0t::Flickrss

そんなんありそうだけどちょっと探して無かったので頭に来て作りました。 最初、切なくなる程遅かったんだけどphpFilckrはファイルにキャッシュできるそうなのでそれ使ったら速くなった。(時間+9されてないのは後で直す!)

ついでに以前作ったjsのFeed Parerもデモった。 (Descriptionが[Object]になってるのはごかんべんを!)

もっとアプリをカッとなってすぐ作ってしまえるように訓練したい。(結構時間がかかった)

% php -r '::';
Parse error: syntax error, unexpected T_PAAMAYIM_NEKUDOTAYIM in Command line code on line 1

ってやると出るエラー。

PAAMAYIM_NEKUDOTAYIMってなに???

ぱあまいむねくどたいむ?ってなんだよっっ!

通るかっ!そんなもんっ!(カイジ)

…しかしっ、おれにはわかるぞ!偶然だが知っているぞ!

PHP: スコープ定義演算子 (::) – Manual

スコープ定義演算子 (Paamayim Nekudotayimとも呼ばれます)または より簡潔にダブルコロンはトークンで、 static, 定数および オーバーライドされたクラスのメンバやメソッドにアクセスすることができます。

これらの要素をクラス定義の外から参照する際には、 クラスの名前を使用してください。

Paamayim Nekudotayim は、まず、ダブルコロンの名前としては、奇妙に 思えるでしょう。しかし、Zend Engine 0.5 (PHP 3のエンジン)を 書いている時に、Zendチームはこう呼ぶと決めたのです。 これは、実際には、ヘブライ語でダブルコロンのことなのです!

「ふ・・・・・・ふざけるなっ・・・・・・!てめえっ・・・・・・・!」

「んなこと通るかっ・・・・・・・!変更するなら・・・・・・すぐ変更しろっ・・・・・・・!汚ねえぞ・・・・・・っ!」

「許せるかっ・・・・・!許せるかよっ・・・・・!そんなペテンっ・・・・・!」

PEAR :: PEPr :: Details :: CAPTCHA

Description This package generates audio CAPTCHAs.

A CAPTCHA (see http://en.wikipedia.org/wiki/CAPTCHA) is a turing test to decide if a real person is accessing some resource and is typically a distorted image of some text. This package creates audio CAPTCHAS to help those who have difficulty reading this distorted text.

This package joins up multiple wav file recordings of individual letters/numbers being spoken, optionally adding distortion, creating an audio CAPTCHA.

proposalにAudio::CAPTCHAだって。へぇー。 たしかにTextのCAPTCHAってコンピューターと一緒に視覚障害者もブロックしちゃってたわけだ。

% 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返ってくるみたい)