Home > PHP Archive

PHP Archive

ネット航海時代・更新

  • 2011-11-25 (金)
  • PHP

ネット航海時代を自動でプレイするマクロがあるらしい。
恥ずかしながら最近まで知りませんでした。

というわけで、対策してみました。
どれくらい効果があるものか・・・。煩わしいだけかも。
http://net-sailing.net/

以上

ネット航海時代 リニューアル

ネット航海時代をリニューアルしました!

とりあえずver3βとしています。
まだまだ至らない点が多いですが、ご指摘等頂けたらありがたく思います。

Perlから無駄にPHPに移植し、全面的に作り直しました。
これから機能を増やしていく予定です。

目下の対応案件は携帯です。
twitterのoauth認証がdocomoだと文字化けしまくりでだるい感じです。でも認証はできます。

以上

Yahoo! APIのURLがっ!

「みんなが小説家になるようです。」のあらすじジェネレートが動かなくなっていた。
いろいろ調べると、Yahoo! APIのURLが半年も前に変更されているではないか!
そんなん変えんなよな・・・。
というわけで、修正したら動くようになりました。

以上

PHPのどうしようもなくくだらないテスト5

  • 2009-10-25 (日)
  • PHP

1.explode(",", null)
array("")を返す。返しちゃう。countすると1になっちゃう。


print_r(explode(",", null));
echo count(explode(",", null));

Array
(
    [0] =>
)
1

2.empty(class->__get) == true
クラスのgetterを定義してあってもクラスに定義のない変数はempty。


class GetTest {

	public function __get($name) {
		
		return "hoge\n";
	}
}

$hoge = new GetTest();
echo $hoge->fuga;
echo empty($hoge->fuga);

hoge
1

意外にはまった。

以上

(2010/04/21 追記)
上記2.について。__isset()を適切に実装すればよかった。
http://www.php.net/manual/en/function.empty.php#93117

以上

[php] オートログイン機能を作る

  • 2009-09-19 (土)
  • PHP

まあ、クッキーにログイン名とパスワードを覚えさせちゃえば終わりだが、「パスワードまでクッキーに保存するのはセキュリティ的に良くない!」っていう感じらしいので、ちょっと工夫してみる。

○前提
A-1. ユーザはIDを持っており、IDからユーザ情報を取得できる。
A-2. セッションを使える。
A-3. クッキーを使える。
A-4. ユーザ情報はデータベースにある。
A-5. PDOを使える。

○方針
B-1. データベースにオートログイン用のテーブルを作成する。
B-2. 通常のログインが成功したとき、ランダムな文字列を生成してクッキーとオートログイン用テーブルに保存する。
B-3. セッションにユーザIDがあるときはログイン中と見なす。ない時はクッキーに保存されたランダムな文字列からオートログイン用テーブルに保存されたユーザIDを取得する。オートログイン用テーブルからユーザIDを取得できた場合は、セッションにユーザIDを残す。
B-4. ユーザIDを取得できればログインできたことにしてユーザ情報をIDから取得する。

○実装
B-1. データベースにオートログイン用のテーブルを作成する。
※SQLiteの例


CREATE TABLE autokeys (
   id INTEGER PRIMARY KEY,
   cookiekey CHAR(255),
   user_id INTEGER,
   created DEFAULT NULL,
   modified DEFAULT NULL
);

B-2. 通常のログインが成功したとき、ランダムな文字列を生成してクッキーとオートログイン用テーブルに保存する。


define("SHASALT", "marukatuhayatuharisekaiitidatutanodayo");    // eigenvalue for crypt
define("COOKIE_AUTOKEY", "autologinkey");    // cookie name
define("AUTOKEY_TABLE", "autokeys");      // table name of db for autologin
define("USER_KEY", "user_id");            // field name of db to access user's info
define("COOKIE_EXPIRE_AUTOKEY", 60*60*24*10);      // time, in which cookie will be deleted.

/**
 * オートログイン用にランダムな文字列を発行し、クッキーとオートログイン用テーブルに保存する。
 * @param $dbh データベースにアクセスするためのPDOインスタンス
 * @param $user_id オートログイン用テーブルに保存する、ユーザID。
 * @param $autokeyId nullなら新しくユーザIDとクッキーのセットをデータベースに作成する。nullでないなら更新する。
 * @return bool
 */
function set_new_cookiekey(PDO $dbh, $user_id, $autokeyId = null) {
   $newkey = sha1((string)$user_id . SHASALT . mt_rand());
   $time = time();
   if (null === $autokeyId) {
      $sql = "INSERT INTO " . AUTOKEY_TABLE . " (cookiekey, " . USER_KEY . ", created, modified) VALUES(?,?,?,?)";
      $data = array($newkey, $user_id, $time, $time);
   } else {
      $sql = "UPDATE " . AUTOKEY_TABLE . " SET cookiekey=?, modified=? WHERE id=?";
      $data = array($newkey, $time, $autokeyId);
   }
   $retval = false;
   try {
      $stmt = $dbh->prepare($sql);
      $retval = $stmt->execute($data);
   } catch (PDOException $e) {
      // error
   }
   setcookie(COOKIE_AUTOKEY, $newkey, $time + COOKIE_EXPIRE_AUTOKEY);
   return $retval;
}

B-3. セッションにユーザIDがあるときはログイン中と見なす。ない時はクッキーに保存されたランダムな文字列からオートログイン用テーブルに保存されたユーザIDを取得する。オートログイン用テーブルからユーザIDを取得できた場合は、セッションにユーザIDを残す。


define("SESSION_LOGGEDIN", "userid");      // session name
/**
 * セッションまたはオートログイン用データベースからユーザIDを取得する。
 * 
 * @param $dbh データベースにアクセスするためのPDOインスタンス
 * @return mix ユーザIDまたはfalse
 */
function auto_get_user_id(PDO $dbh) {
   session_start();
   // If the session info hasn't been set...
   if (empty($_SESSION[SESSION_LOGGEDIN])) {
      if (!empty($_COOKIE[COOKIE_AUTOKEY])) {
         $cookieLogin = false;
         $sql = "SELECT * FROM " . AUTOKEY_TABLE . " WHERE cookiekey=?";
         try {
            $stmt = $dbh->prepare($sql);
            if ($stmt->execute(array($_COOKIE[COOKIE_AUTOKEY]))) {
               $autokey = $stmt->fetch(PDO::FETCH_ASSOC);
               // got entry
               if ($autokey && count($autokey) > 0) {
                  $_SESSION[SESSION_LOGGEDIN] = $autokey[USER_KEY];
                  // update cookie db
                  set_new_cookiekey($dbh, $autokey[USER_KEY], $autokey['id']);
                  $cookieLogin = true;
               }
            }
         } catch (PDOException $e) {
            // error
         }
         

         if ($cookieLogin) {
            // logged in...
            return $autokey[USER_KEY];
         } else {
            // cannot login...
            // delete cookie
            setcookie(COOKIE_AUTOKEY, "", $time - COOKIE_EXPIRE_AUTOKEY);
            return false;
         }
      } else {
         return false;
      }
   } else {
      return $_SESSION[SESSION_LOGGEDIN];
   }
}

B-4. ユーザIDを取得できればログインできたことにしてユーザ情報をIDから取得する。


function login($name, $pass) {
   $dsn = "sqlite:default.db";
   $pdo = new PDO($dsn);
   
   // auto login check
   $userid = auto_get_user_id($pdo);
   
   if ($userid) {
      // セッションまたはクッキーからログイン
      $user= get_user_by_id($userid);    // ユーザ情報取得関数
   } else {
      // フォームからログイン
      $user= get_user_by_name_and_pass($name, $pass);    // ユーザ情報取得関数
   }
   
   return $user;
}

ここまで書いてなんだけど、これはひどい。後から見て全然分からん。

以上

staticからprivate

  • 2009-08-19 (水)
  • PHP

これ、なんとなく気持ち悪いからやったこと無かったけど、できるんだなあ。


class PrivateTest {
   private $a = 1;
   
   public static function access($ptest) {
      return $ptest->a;
   }
}
$p = new PrivateTest();
echo PrivateTest::access($p);

1

上記はPHPだがC++でもいける。

追記:
やったこと無いって言うのはうそ。普通にやってた(演算子のオーバーロードとかで)。
PHPで上記のようなことをやろうとしてちょっと不思議に思ってしまった。
同じものを見ていても、見る側の目的意識が変わると違うように感じるものなんだなあ。

以上

レーベンシュタイン距離の挿入コストと削除コスト

レーベンシュタイン距離っていうのは一つの文字列と文字列の距離の求め方です。
ざっくり言うと、Aという文字列とBという文字列の距離を求める場合、Aの各文字を削除したり置換したり、あるいはBにある文字をAに挿入したりしてBと同じ文字列を作り、そのときの手数を距離とします。
例えばA:”abbc”とB:”acbbd”の距離を求めると以下のようになります。

aとbの間にcを挿入→acbbc
最後のcをdに置換→acbbd

ということで距離は2です。編集の方法はいくつもありますが、最小の手数のものを距離と定義します。
例えば

aとbの間にcを挿入→acbbc
後ろのbとcの間にdを挿入→acbbdc
最後のcを削除→acbbd

だと手数が3なのでNGです。プログラムでこの最小手数を求めるにはDP法(動的計画法)を用います。
Wikipediaの解説
でも上記の擬似コードはコメントが間違っていると思います。
レーベンシュタイン距離はあくまで「Aを編集してBにするときの最小コスト」なので、編集はすべてAに対して行わなければなりません(たぶん)。なのでBの文字列を削除したりしてはイカンのです。
d[ i1, i2 ]は文字列Aのi1番目までの文字列と文字列Bのi2番目までの文字列の距離です。
A:a(1)a(2)…a(i1)
B:b(1)b(2)…b(i2)
DP法により、この距離をd[ i1 – 1, i2]、d[ i1, i2 – 1]、d[ i1 – 1, i2 – 1]を使って求めるわけですが、次のように考えるべきです。

・Aからの削除によりBに近づく場合:
Aから末尾の文字a(i1)を削除すれば「a(1)~a(i1 – 1)の文字列」と「b(1)~b(i2 – 1)の文字列」との距離に帰着されるので、d[ i1, i2 ]はd[ i – 1, i2]の距離に末尾文字a(i1)を削除するコストを足したものと等しい。
・Aへの挿入によりBに近づく場合:
a(1)~a(i1)の末尾にb(i2)を挿入すれば、
  “「a(1)~a(i1)b(i2)の文字列(A側の末尾にb(i2)を加えた)」と「b(1)~b(i2 – 1)の文字列」の距離”
  “「a(1)~a(i1)b(i2)の文字列」と「b(1)~b(i2)の文字列」の距離”
は等しくなるのでd[ i1, i2 ]はd[ i1, i2 – 1 ]の距離にb(i2)を挿入するコストを足したものに等しい。
・Aの文字を置換することでBに近づく場合:
a(i1)をb(i2)に置換すればよいので、d[ i1, i2 ]はd[ i1 – 1, i2 – 1]にa(i1)を置換するコストを足したものに等しい。

というわけで、PHPで書くと以下のようになります。


/**
 * レーベンシュタイン距離を求める。
 * @param $a A文字列
 * @param $b B文字列
 * @param $i_c 挿入コスト
 * @param $r_c 置換コスト
 * @param $d_c 削除コスト
 * @return 距離
 */
function leven($a, $b, $i_c=1, $r_c=1, $d_c=1)
{
   $len_a = strlen($a);
   $len_b = strlen($b);
   $matrix = array();
   for ($jj = 0; $jj <= $len_b; $jj++) {
      $matrix[0][$jj] = $jj*$i_c;   // 全挿入
   }
   for ($ii = 0; $ii <= $len_a; $ii++) {
      $matrix[$ii][0] = $ii*$d_c;   // 全削除
   }

   for ($i = 1; $i <= $len_a; $i++) for ($j = 1; $j <= $len_b; $j++) {
      $del = $matrix[$i-1][$j] + $d_c;   // 削除
      $ins = $matrix[$i][$j-1] + $i_c;   // 挿入
      $rep = $a[$i-1] == $b[$j-1] ? $matrix[$i-1][$j-1] : $matrix[$i-1][$j-1] + $r_c;   // 置換
      $matrix[$i][$j] = min($ins, $del, $rep);
   }
   return $matrix[$len_a][$len_b];
}

PHPにはそもそもlevenshtein()という関数があり、上記のような関数を自分で作る必要はないです。挿入コスト・置換コスト・削除コストをいろいろ変えたとき、上記関数はPHPの組込関数と結果が一致します。しかし、Wikipediaのものは一致しません。Wikipedia方式は次のような解釈と思います。

・Bからの削除によりAに近づく場合:
Bからb(i2)を削除すれば良いので、d[ i1, i2 ]はd[ i1, i2 – 1 ]にb(i2)を削除するコストを足したものに等しい。
・Aへの挿入によりBに近づく場合:
a(1)~a(i1 – 1)の文字列にb(i2)を挿入すればよいので、d[ i1, i2 ]はd[ i1 – 1, i2 ]にb(i2)を挿入するコストを足したものに等しい。
・Aの文字を置換することでBに近づく場合:
a(i1)をb(i2)に置換すればよいので、d[ i1, i2 ]はd[ i1 – 1, i2 – 1]にa(i1)を置換するコストを足したものに等しい。

この方が理解しやすいですが、両方の文字列を編集しているのでもともとの定義からは外れていると思います。ただし、たいていの場合に上記の2つの解釈は結果が一致します。というか、挿入コストと削除コストが等しい場合には同じになります。逆に言うと、コストが挿入と削除で対称でない場合は違う結果になります。
ただし、レーベンシュタイン距離が距離の公理を満たすためには挿入と削除に対して対称でなければならないため、結局どっちでもイイっていう。
あるいは基本的に私が勘違いしている可能性も高いです。

以上

PHPの文字列インクリメントについて極めて若干の補足

  • 2009-06-23 (火)
  • PHP

デクリメントはできない。


$a = "a";
$a++;
$a--;
echo $a;

結果:


b

以上

PHPで文字列をintにグレードアップする

  • 2009-06-21 (日)
  • PHP

PHPでは文字列もインクリメントすることができます。すごいです。


$a = "aaa";
echo ++$a;

結果:


aab

となります。もちろん繰り上がりも大文字小文字も考慮してくれます。


$a = "aaz";
echo ++$a;
echo "\n";
$aA = "AzZ";
echo  ++$aA;

結果:


aba
BaA

ただしひらがなは無理っぽい?カタカナはOKっぽい?感じはダメっぽい?って感じでこの辺はさすがに微妙です。

これとはちょっと違う話で、PHPは数値っぽい文字列を数値に変換して比較するという機能があります。
下記サイトに書いてある話です。
http://d.hatena.ne.jp/fbis/20090618/1245297557


if ("0x0A" == 10) {
   echo "true";
}

結果:


true

これは実は比較演算じゃなくても数値に変換されます。足し算とか、そうインクリメント演算子でも!
ということで、上記の文字列インクリメントと組み合わせると・・・


$a = "0w7";
for ($i = 0; $i < 5; $i++) {
   $a++;
   echo $a;
   echo "\n";
   echo var_dump($a);
}

結果:


0w8
string(3) "0w8"
0w9
string(3) "0w9"
0x0
string(3) "0x0"
1
int(1)
2
int(2)

途中で型がintに変化するという摩訶不思議。・・・はっちゃけすぎだろPHP。

以上

PHPのどうしようもなくくだらないテスト4 include_once

  • 2009-06-09 (火)
  • PHP

includeは何回でも読み込み直すし、include_onceは一回しか読み込まない(重複して読み込まない)、そんなことは私でも知っています。
気になったのは戻り値です。includeは読み込みに成功すると1を返します(マニュアル)。(ただしincludeするファイルの最後にreturn文を書いておけばなんでも返せる)

じゃあinclude_onceはどうなのか。特に2回読み込もうとしたときにどうなるのか。2回目は読み込まれないはずだからfalse返すんじゃね?という当然(?)の疑問に巡り会ったので確認してみました。


hoge.php
<?php
    echo "hoge\n";
?>

test.php:
<?php
    $result = include "hoge.php";
    echo $result . "\n";
    $result = include "hoge.php";
    echo $result . "\n";
    echo "\n------------------------\n";
    $result = include_once "hoge.php";
    echo $result . "\n";
    $result = include_once "hoge.php";
    echo $result . "\n";
?>

結果は


hoge
1
hoge
1
------------------------
1
1

つまり、読み込んではいないけど2回目以降も1を返す、ということみたいです。

ちなみに、includeもinclude_onceもファイルがなかったときにWarningを吐きますが、@includeってやれば黙ります。また、includeもinclude_onceも()はつけない方がいい感じです(include("hoge")でもいいけどあまり良くない)。

以上

Home > PHP Archive

Search
Feeds
Meta
 合計:064749
 今日:0162 昨日:0165

Return to page top