エントリー

タグ「PHP」の検索結果は以下のとおりです。

Google App EngineでPHPを動作させるメモ

Google App Engineを少しだけ触ってみたのでメモ。

参考サイト

参考にはなったけど、色々端折られた個人メモみたいなので、自分用に改めてメモ。

環境構築

あらかじめWindowsに、Python2.7.xをインストールしておく。(GAEのツール自体がPythonで書かれているため、PHPを動作させる場合でも必要。)

プロジェクト作成

https://console.developers.google.com/project から、プロジェクト名「GAE Test Project」、プロジェクトID「refirio-php-test」で新規にプロジェクトを作成する。(プロジェクト名とプロジェクトIDは任意のものを指定する。)

次に https://console.developers.google.com/project/refirio-php-test/start/appengine にアクセスして解説に従う。

Google App Engine SDK for PHP をインストールし、サンプルコードをダウンロードする。サンプルコードは C:\localhost\home\gae\public_html\refirio-php-test とか、ローカルの適当なディレクトリ内に配置する。

次にサンプルコードを編集する。.playground の内容は以下のとおり。

{
  "project_name": "PHP TEST - refirio",
  "project_description": "Starter code for a Google App Engine app in PHP.",
  "show_files": [
    "helloworld.php",
    "app.yaml"
  ],
  "read_only_demo_url": "https://refirio-php-test.appspot.com/",
  "download_filename": "appengine-try-php.zip"
}

app.yaml の内容は以下のとおり。

application: refirio-php-test
version: 1
runtime: php55
api_version: 1

handlers:
- url: /.*
  script: helloworld.php

helloworld.php の内容は以下のとおり。

<?php

echo 'Hello, GAE!';

公開

デスクトップに作成されたショートカットから「Google App Engine Launcher」を起動する。ここからローカル環境でのテスト実行や、本番環境へのデプロイができる。

デプロイが完了すると、以下のURLからアクセスできる。

Basic認証設定ツール

100番煎じどころではないと思われるツールを作ったので置いておきます。

Basic認証をかけたいディレクトリ内に basic.php などの名前でサーバにアップロードしてアクセスすると、作るべき .htaccess.htpasswd の場所と内容を表示します。先頭に並んでいる test1 とかの部分は、認証させるユーザ名とパスワードです。適当に変更&増減できます。

.htaccess の雛形を作ってくれたり .htpasswd の内容を作ってくれたりするツールは多いけど、AuthUserFile の内容を調べたりするのも面倒なのでそれも自動で。_(:3 」∠ )_

<?php

$users = array(
  'test1' => '1234',
  'test2' => '2345',
  'test3' => '3456',
);
$path = dirname($_SERVER['SCRIPT_FILENAME']);

?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Basic Authentication</title>
</head>
<body>
<h1>Basic Authentication</h1>
<h2><?php h($path) ?>/.htaccess</h2>
<pre>AuthUserFile <?php h($path) ?>/.htpasswd
AuthGroupFile /dev/null
AuthName "Basic"
AuthType Basic
require valid-user
&lt;Files ~ "^.(htpasswd|htaccess)$"&gt;
deny from all
&lt;/Files&gt;</pre>
<h2><?php h($path) ?>/.htpasswd</h2>
<pre><?php

foreach ($users as $username => $password) {
  t($username . ':' . crypt($password) . "\n");
}

?></pre>
</body>
</html>
<?php

function h($data)
{
  $data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
  $data = nl2br($data);

  echo $data;
}
function t($data)
{
  $data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8');

  echo $data;
}

?>

Webアプリケーションへの同時アクセス対策メモ

Webアプリケーションへの同時アクセス対策メモ

XAMPPの Apache Bench で同時アクセスを再現するメモ。テストした環境はWindows7。Apache Bench の使い方は以下を参考に。

XAMPPをCドライブ直下にインストールした場合、以下の手順で Apache Bench を使える。

> cd C:\xampp\apache\bin
> ab -n 15 -c 15 http://localhost/~test/ab/

「-n 15 -c 15」で「15人が同時にアクセスする(合計15回アクセス)」状況がテストできる。(「-n 15 -c 1」なら「1人が合計15回アクセスする」状況になるので、同時アクセスにはならない。)

以下で同時アクセスのテストとその対策を行う。

ファイルを使った例

<?php

/*
 * No.2をカウントアップ(最大値100)
 */

if ($fp = fopen('logs/count.log', 'r+')) {
  $data = '';
  while ($line = fgets($fp)) {
    list($id, $count) = explode("\t", rtrim($line));

    if ($id == 2) {
      if ($count < 100) {
        $data .= "$id\t" . ++$count . "\n";
      } else {
        exit('Limit');
      }
    } else {
      $data .= $line;
    }
  }

  rewind($fp);
  fwrite($fp, $data);
  ftruncate($fp, ftell($fp));

  fclose($fp);
} else {
  exit('Error');
}

exit('Complete');

logs/count.log の内容は以下のとおり。

1	10
2	20
3	30
4	40
5	50

これで上のPHPプログラムにアクセスすると、2の行が1カウントアップされる。何度もリロードすると、その回数だけカウントアップされる。

> ab -n 30 -c 30 http://localhost/~test/ab/count.php

30人の同時アクセスを発生させてみると、正しくカウントできないのが確認できる。

<?php

/*
 * No.2をカウントアップ(最大値100)
 */

if ($fp = fopen('logs/count.log', 'r+')) {
  flock($fp, LOCK_EX);  //ファイルロック

  $data = '';
  while ($line = fgets($fp)) {
    list($id, $count) = explode("\t", rtrim($line));

    if ($id == 2) {
      if ($count < 100) {
        $data .= "$id\t" . ++$count . "\n";
      } else {
        exit('Limit');
      }
    } else {
      $data .= $line;
    }
  }

  rewind($fp);
  fwrite($fp, $data);
  ftruncate($fp, ftell($fp));

  fclose($fp);
} else {
  exit('Error');
}

exit('Complete');

ファイルロックを行えば、正しくカウントされるようになる。

データベースを使った例

<?php

/*
 * No.2をカウントアップ(最大値100)
 */

try {
  $pdo = new PDO('mysql:dbname=test;host=localhost', 'root', '1234', array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
  $pdo->query('SET NAMES utf8');

  $stmt = $pdo->prepare('SELECT count FROM count_test WHERE id = :id');
  $stmt->bindValue(':id', 2, PDO::PARAM_INT);
  $stmt->execute();

  $data = $stmt->fetch(PDO::FETCH_ASSOC);

  if ($data['count'] < 100) {
    $stmt = $pdo->prepare('UPDATE count_test SET count = count + 1 WHERE id = :id');
    $stmt->bindValue(':id', 2, PDO::PARAM_INT);
    $stmt->execute();
  }
} catch (PDOException $e) {
  exit($e->getMessage());
}

exit('Complete');

count_test の定義は以下のとおり。

CREATE TABLE count_test(
  id    INT UNSIGNED NOT NULL,
  count INT UNSIGNED,
  PRIMARY KEY(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

これで上のPHPプログラムにアクセスすると、2の行が1カウントアップされる。何度もリロードすると、その回数だけカウントアップされる。

ab -n 200 -c 200 http://localhost/~test/ab/count.php

200人の同時アクセスを発生させてみると、正しくカウントできないのが確認できる。

<?php

/*
 * No.2をカウントアップ(最大値100)
 */

try {
  $pdo = new PDO('mysql:dbname=test;host=localhost', 'root', '1234', array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
  $pdo->query('SET NAMES utf8');

  $pdo->beginTransaction();  //トランザクション開始

  $stmt = $pdo->prepare('SELECT count FROM count_test WHERE id = :id FOR UPDATE');  //行ロック
  $stmt->bindValue(':id', 2, PDO::PARAM_INT);
  $stmt->execute();

  $data = $stmt->fetch(PDO::FETCH_ASSOC);

  if ($data['count'] < 100) {
    $stmt = $pdo->prepare('UPDATE count_test SET count = count + 1 WHERE id = :id');
    $stmt->bindValue(':id', 2, PDO::PARAM_INT);
    $stmt->execute();
  }

  $pdo->commit();  //コミット
} catch (PDOException $e) {
  $pdo->rollBack();  //ロールバック

  exit($e->getMessage());
}

exit('Complete');

トランザクションと行ロックを行えば、正しくカウントされるようになる。

Windows + コマンドプロンプト + PHPUnit でユニットテスト

今更ながらPHPUnitを試したのでメモ。

概要

PHPUnit3で始めるユニットテストが解りやすかったが情報が古い。でも、概要をつかむには良さそうです。

大まかな内容としては「◯◯の命令を△△の形式で呼び出して、□□の結果が返ってくればOK」というコードをたくさん書いたプログラムを準備しておき、それを実行すれば各命令が正しく実行されたかを知ることができるというもの。(プログラム作成中に不具合を作ってしまっても、即座に検知できる…かもしれない。)

大人数で各々が大量の部品を作るような開発では、上手く導入するととても活躍してくれそう。一人/少人数で作成しているようなプログラムでは、使いどころが難しいかも。(自分が解っていないだけかも。)

導入自体は簡単。

導入

自分の環境に合わせて「Windows+コマンドプロンプト+PHPUnit」でPHPプログラムのテストをしてみたので、手順をメモ。PHPUnit のインストールを主に参考にしています。

まずは公式サイトから phpunit.phar の安定版を入手する。

任意のディレクトリ内(C:\xxx\phpunit とする)に、入手した phpunit.phar を配置する。

C:\xxx\phpunit 内に phpunit.cmd を作成し、以下の内容を記述する。(PHPへのパスは環境に合わせて設定する。)

@C:\xampp\php\php.exe "%~dp0phpunit.phar" %*

コマンドプロンプトで C:\xxx\phpunit 内に移動し、以下のコマンドを実行。以下のようにバージョン情報が表示されれば成功。

C:\xxx\phpunit>phpunit --version
PHPUnit 4.5.0 by Sebastian Bergmann and contributors.

サンプルで動作確認

Getting Started with PHPUnit を参考にテスト。

上ページの内容を参考に C:\xxx\phpunit\src\Money.php (テストしたいクラス)を作成。次に C:\xxx\phpunit\tests\MoneyTest.php (テスト用プログラム)も作成。

コマンドプロンプトから、以下のようにコマンドを実行するとテストできる。

C:\xxx\phpunit>phpunit --bootstrap src/Money.php tests/MoneyTest
PHPUnit 4.5.0 by Sebastian Bergmann and contributors.

.

Time: 31 ms, Memory: 4.00Mb

OK (1 test, 1 assertion)

上のように表示されればテスト成功。

自作のプログラムで動作確認

PHPUnit 用のテストの書き方 を参考にテスト。

まずは C:\xxx\phpunit\src\calculate.php を作成し、掛け算と割り算の計算結果を返す命令を定義。これらの命令が正しく動作しているかテストするものとする。

<?php
function multiplication($x, $y) {
  return $x * $y;
}
function division($x, $y) {
  return $x / $y;
}

C:\xxx\phpunit\tests\calculate.php を作成し、各命令をテストするコードを書く。(計算結果が正しく返ってくればテスト成功とする。)

<?php
require_once 'C:/localhost/home/test/public_html/phpunit/src/calculate.php';
class Calculate extends PHPUnit_Framework_TestCase
{
    public function testCalculate()
    {
        $this->assertEquals(8, multiplication(4, 2));
        $this->assertEquals(2, division(4, 2));
    }
}

コマンドプロンプトからテスト実行。

C:\xxx\phpunit>phpunit tests/calculate
PHPUnit 4.5.0 by Sebastian Bergmann and contributors.

.

Time: 30 ms, Memory: 4.00Mb

OK (1 test, 2 assertions)

上のように表示されればテスト成功。試しに関数の内容を間違ったものにしてテストすると。

FAILURES!
Tests: 1, Assertions: 2, Failures: 1.

のような結果が表示されるので、正しくテストできていることが確認できる。

PHPでセッションハンドラを自作

PHPは標準機能でセッション管理ができますが、何故かセッション管理のみが正しく動作しないサーバーがあったので、セッションハンドラで対応してみた。

PHPでは標準命令の session_set_save_handler() を使用すれば、セッションを扱う際の挙動を自分で決めることができます。(セッションをMySQLで管理する仕組みを自作したり、など。)

以下はファイルでセッションを管理するサンプル。

<?php

define('SESSION_HANDLER_PATH', dirname(__FILE__) . '/sessions/');
define('SESSION_HANDLER_PREFIX', 'session_');
define('SESSION_HANDLER_EXTENSION', '.log');

if (SESSION_HANDLER_PATH != '') {
  $session_handler_path = SESSION_HANDLER_PATH;
} else {
  $session_handler_path = '';
}

function session_handler_open($save_path, $session_name) {
  global $session_handler_path;

  if ($session_handler_path == '') {
    $session_handler_path = $save_path;
  }

  return true;
}

function session_handler_close() {
  global $session_handler_path;

  return true;
}

function session_handler_read($session_id) {
  global $session_handler_path;

  $file = $session_handler_path . SESSION_HANDLER_PREFIX . $session_id . SESSION_HANDLER_EXTENSION;

  if (file_exists($file)) {
    return file_get_contents($file);
  } else {
    return null;
  }
}

function session_handler_write($session_id, $data) {
  global $session_handler_path;

  $file = $session_handler_path . SESSION_HANDLER_PREFIX . $session_id . SESSION_HANDLER_EXTENSION;

  return file_put_contents($file, $data);
}

function session_handler_destroy($session_id) {
  global $session_handler_path;

  $file = $session_handler_path . SESSION_HANDLER_PREFIX . $session_id . SESSION_HANDLER_EXTENSION;

  unlink($file);

  return true;
}

function session_handler_gc($lifetime) {
  global $session_handler_path;

  if ($dir = scandir($session_handler_path)) {
    foreach ($dir as $entry) {
      if (is_file($session_handler_path . $entry) and preg_match('/^' . preg_quote(SESSION_HANDLER_PREFIX, '/') . '\w+' . preg_quote(SESSION_HANDLER_EXTENSION, '/') . '$/i', $entry) and time() - filemtime($session_handler_path . $entry) > $lifetime) {
        unlink($session_handler_path . $entry);
      }
    }
  }

  return true;
}

session_set_save_handler(
  'session_handler_open',
  'session_handler_close',
  'session_handler_read',
  'session_handler_write',
  'session_handler_destroy',
  'session_handler_gc'
);

?>

上のコードを一例ですが session_handler.php という名前で保存し、同じディレクトリ内に sessions というディレクトリを作成します。この中にセッションの情報が保存されます。

session_set_save_handler() 関数によって、「セッションを開始した時は session_handler_open() が呼ばれる」「セッションを保存する時は session_handler_write() が呼ばれる」のような設定を行っています。

以降は、以下のコードで自作のセッション管理を利用できます。

<?php

include_once('session_handler.php');
session_start();

if (isset($_SESSION['count'])) {
  $_SESSION['count']++;
} else {
  $_SESSION['count'] = 1;
}

?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>サンプル</title>
</head>
<body>
<?php

echo $_SESSION['count'] . '回目のアクセスです。';

?>
</body>
</html>

include_once('session_handler.php'); でファイルを読み込む以外は、ごく普通のセッション利用サンプルです。

急きょ書いたコードなので変な箇所があるかもしれません。また、名前の重複を避けるためにもクラス化した方が良かったかも。と思っていたら、公式サイトにクラスのサンプルがあった…。またそのうち調べる予定。

PostgreSQLメモ

テーブルを扱うためのメモ。

SQLでテーブル一覧を表示

SELECT
  pg_class.relname AS relname
FROM
  pg_class INNER JOIN pg_namespace
ON
  pg_class.relnamespace = pg_namespace.oid
WHERE
  pg_class.relkind = 'r' AND pg_namespace.nspname = 'public';

SQLでテーブル仕様を表示

SELECT
  column_name, data_type, is_nullable
FROM
  information_schema.columns
WHERE
  table_schema = 'public' AND table_name = 'テーブル名';

SQLでテーブル作成コードを表示

SELECT
  CASE
    WHEN tb.relkind = 'r' THEN(
      SELECT 'CREATE TABLE "テーブル名"(' || chr(10) || array_to_string(
        ARRAY(
          SELECT ' "' || "Column" || '" '|| "Type" || "Modifiers" || "Index" FROM(
            /* Column */
            SELECT
              at.attnum, ns.nspname AS schema, tb.relname AS table, at.attname AS "Column",
              /* Type */
              CASE
                WHEN at.attinhcount <> 0 OR at.attisdropped THEN null
                ELSE
                  CASE
                    WHEN tp.typname = 'int2'   THEN 'SMALLINT'
                    WHEN tp.typname = 'int4'   THEN 'INTEGER'
                    WHEN tp.typname = 'int8'   THEN 'BIGINT'
                    WHEN tp.typname = 'float4' THEN 'REAL'
                    WHEN tp.typname = 'float8' THEN 'DOUBLE PRECISION'
                    WHEN tp.typname = 'bpchar' THEN 'CHAR'
                    ELSE UPPER(tp.typname)
                  END ||
                  CASE
                    WHEN at.attlen >= 0             THEN ''
                    WHEN at.atttypmod < 4           THEN ''
                    WHEN tp.typname <> 'numeric'    THEN '(' || at.atttypmod - 4 || ')'
                    WHEN (at.atttypmod & 65535) = 4 THEN '(' || (at.atttypmod >> 16) || ')'
                    ELSE '(' || (at.atttypmod >> 16) || ',' || (at.atttypmod & 65535) - 4 || ')'
                  END
              END AS "Type",
              /* Modifiers */
              CASE
                WHEN at.attnotnull THEN ' NOT NULL'
                ELSE ''
              END ||
              CASE
                WHEN ad.adbin IS NULL THEN ''
                ELSE ' DEFAULT ' || UPPER(pg_get_expr(ad.adbin, tb.oid))
              END AS "Modifiers",
              /* one-column Index */
              CASE
                WHEN ix.indexrelid IS NULL THEN ''
                ELSE
                  CASE
                    WHEN ix.indisprimary THEN ' PRIMARY KEY'
                    WHEN ix.indisunique  THEN ' UNIQUE'
                    ELSE ''
                  END
              END AS "Index"
            FROM
              pg_attribute at
              INNER JOIN pg_type tp ON at.atttypid = tp.oid
              LEFT OUTER JOIN pg_attrdef ad ON ad.adrelid = tb.oid AND ad.adnum = at.attnum
              LEFT OUTER JOIN pg_index ix ON ix.indrelid = tb.oid AND ix.indnatts = 1 AND at.attnum = ix.indkey[0]
              LEFT OUTER JOIN pg_class ic ON ix.indexrelid = ic.oid
              LEFT OUTER JOIN pg_am    am ON ic.relam = am.oid
            WHERE
              tb.oid = at.attrelid AND at.attnum >= 1
          ) AS columns ORDER BY attnum
        ), ',' || chr(10)
      )
      ||
      (
        SELECT
          CASE
            WHEN COUNT(*) = 0 THEN ''
            ELSE ',' || chr(10) || ' ' || array_to_string(
              ARRAY(
                SELECT
                  CASE
                    WHEN indisprimary THEN 'PRIMARY KEY '
                    ELSE 'UNIQUE '
                  END
                  || substr(indexdef, strpos(indexdef, '('), strpos(indexdef, ')') - strpos(indexdef, '(') + 1) || ' /* '||index||' */'
                FROM
                (
                  SELECT
                    ic.relname AS index, ns.nspname AS schema, tb.relname AS table --, ia.attname, ia.attnum
                    , ix.indnatts, ix.indisunique, ix.indisprimary, am.amname
                    , ix.indkey
                    , pg_get_indexdef(ic.oid) AS indexdef
                  FROM
                    pg_index ix
                    INNER JOIN pg_class ic ON ix.indexrelid = ic.oid
                    INNER JOIN pg_am    am ON ic.relam = am.oid
                  WHERE
                    ix.indrelid = tb.oid AND ix.indnatts > 1 AND (ix.indisprimary OR ix.indisunique)
                ) AS def ORDER BY indisprimary desc, index
              ), ','||chr(10)
            )
          END
        FROM
          pg_index ix
        WHERE
          ix.indrelid = tb.oid AND ix.indnatts > 1 AND (ix.indisprimary OR ix.indisunique)
      ) || chr(10) || ')'
    )
    END
FROM
  pg_class tb INNER JOIN pg_namespace ns ON tb.relnamespace = ns.oid
WHERE
  tb.relname = 'テーブル名'

コマンドラインでデータベースの内容をエクスポート

pg_dump -U postgres -d データベース名 > C:\~エクスポート先~\任意のファイル名.sql

コマンドラインでデータベースの内容をインポート

psql -U postgres -d データベース名 -f C:\~インポート元~\インポートするファイル

PHPでデータベースの内容をエクスポート

putenv('PGHOST=ホスト名');
putenv('PGPORT=ポート番号');
putenv('PGUSER=ユーザー名');
putenv('PGPASSWORD=パスワード');
putenv('PGDATABASE=データベース名');
print('<pre>');
passthru('"C:\\postgresql\\bin\\pg_dump.exe" -i --inserts');
print('</pre>');
exit;

WindowsのXAMPPにPostgreSQLを導入するメモ

PostgreSQLを導入してみたので、忘れないうちにメモ。

主な参考サイト

トラブル

PHPからPostgreSQLに接続する際、以下のようなエラーになった。

Call to undefined function pg_connect()

php.iniphp_pdo_pgsql.dllphp_pgsql.dll を有効にする必要がある。それでもダメなら、Windowsの環境変数にPHPへのパス(C:\xampp\php など)を書き込めば回避できるみたい。

コマンドプロンプトからの操作例

#ユーザー名を指定してPostgreSQLにログイン
psql -U postgres

#ユーザー名とデータベースを指定してPostgreSQLにログイン
psql -U postgres -d test

#データベース作成
CREATE DATABASE test;

#データベース一覧
¥l

#データベース選択
¥c test

#テーブル作成
CREATE TABLE sample1(
  id   integer,
  name text
);

#テーブル一覧
\dt

#テーブル定義確認
\d sample1

#SQLを発行
INSERT INTO sample1 VALUES(2, 'test2');
SELECT * FROM sample1;

PHPからの、pg_connect()での接続例

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>テスト</title>
</head>
<body>
<?php

$con = pg_connect('host=localhost dbname=test user=postgres password=1234');
if (!$con) {
  exit('データベースに接続できませんでした。');
}

$result = pg_query($con, 'SELECT * FROM sample1');
while ($data = pg_fetch_array($result)) {
  echo '<p>' . $data['id'] . ':' . $data['name'] . "</p>\n";
}

$con = pg_close($con);
if (!$con) {
  exit('データベースとの接続を閉じられませんでした。');
}

?>
</body>
</html>

PHPからの、PDOでの接続例

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>テスト</title>
</head>
<body>
<?php

try {
  $pdo = new PDO('pgsql:dbname=test;host=localhost', 'postgres', '1234');
} catch (PDOException $e) {
  exit('データベースに接続できませんでした。' . $e->getMessage());
}

$stmt = $pdo->query('SELECT * FROM sample1');
if (!$stmt) {
  $info = $pdo->errorInfo();

  exit($info[2]);
}

while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) {
  echo '<p>' . $data['id'] . ':' . $data['name'] . "</p>\n";
}

$pdo = null;

?>
</body>
</html>

フレームワーク

オレオレフレームワークを作る機会があったので置いておきます。

本格的なフレームワークを導入するほどの規模ではないが、それなりに多機能なのである程度の処理は任せたい。今更PHP4に対応させる必要があるが、PHP5でも動作させる必要がある。本番環境のテストが公開直前までできないので、ひととおり内部を見渡せる軽量なフレームワークが好ましい。ついでに、簡易なデータベース管理ツールも欲しい。

…という場合に役に立つかもしれません。というか、そんなことがあったために自作することにした。_(:3 」∠ )_

PHPでオブジェクトを配列に変換

ググると色々出てきたものの、「オブジェクトの中に配列があって、さらにその中にオブジェクトがあって、さらにその中に配列がある」のような場合に正しく変換できなかったり、privateな値もまとめて取得したい場合に対応できなかったり…ばかりだったので書いてみた。

privateな値に直接アクセスできるようにするのは邪道な気もするけど、どうしても必要な時があったりなかったり。

function object2array($data)
{
  if (is_object($data)) {
    $data = (array)$data;
  }

  if (is_array($data)) {
    foreach ($data as $key => $value) {
      $key1 = (string)$key;
      $key2 = preg_replace('/\W/', ':', $key1);

      if (is_object($value) or is_array($value)) {
        $data[$key2] = object2array($value);
      } else {
        $data[$key2] = (string)$value;
      }

      if ($key1 != $key2) {
        unset($data[$key1]);
      }
    }
  }

  return $data;
}

使う時は

$array = object2array($object);

のように呼び出します。まだまだ調整の余地がありそうですが、ひとまずこれで。

PHPで可逆暗号化

PHPでは mcrypt_generic() を使えば可逆暗号化ができ、mdecrypt_generic() で復号化できます。…が、使い方がちょっとややこしかったのでメモ。関連する関数を色々と呼び出す必要があります。

具体的には、以下のコードで暗号化ができます。

<?php

//暗号化するデータ
$plain_text = 'これは秘密のメッセージです。';

//暗号化&復号化キー
$key = md5('KQAHGOEUXD');

//暗号化モジュール使用開始
$td  = mcrypt_module_open('des', '', 'ecb', '');
$key = substr($key, 0, mcrypt_enc_get_key_size($td));
$iv  = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);

//暗号化モジュール初期化
if (mcrypt_generic_init($td, $key, $iv) < 0) {
  exit('error.');
}

//データを暗号化
$crypt_text = base64_encode(mcrypt_generic($td, $plain_text));

//暗号化モジュール使用終了
mcrypt_generic_deinit($td);
mcrypt_module_close($td);

//結果を表示
echo "<!DOCTYPE html>\n";
echo "<html lang=\"ja\">\n";
echo "<head>\n";
echo "<meta charset=\"utf-8\" />\n";
echo "<title>暗号化テスト</title>\n";
echo "</head>\n";
echo "<body>\n";
echo "<h1>暗号化テスト</h1>\n";
echo "<dl>\n";
echo "<dt>暗号化前</dt><dd>" . $plain_text . "</dd>";
echo "<dt>暗号化後</dt><dd>" . $crypt_text . "</dd>";
echo "</dl>\n";
echo "</body>\n";
echo "</html>\n";

?>

この場合、これは秘密のメッセージです。 という文字が暗号化されます。KQAHGOEUXD は、暗号化と復号化に使うパスワードのようなものです。この値を知っている人だけが、このデータを復号化できます。

実行すると、以下のように暗号化前の文字と暗号化後の文字が表示されます。

暗号化テスト

暗号化前
  これは秘密のメッセージです。
暗号化後
  eVVX5OPG0Vj4L49cuq8A3KT1R2nljAIPn74vAw0d/QbYQ1CfUApMlqSpSkyDjDvi

復号化は以下のようにします。

<?php

//復号化するデータ
$crypt_text = 'eVVX5OPG0Vj4L49cuq8A3KT1R2nljAIPn74vAw0d/QbYQ1CfUApMlqSpSkyDjDvi';

//暗号化&復号化キー
$key = md5('KQAHGOEUXD');

//暗号化モジュール使用開始
$td  = mcrypt_module_open('des', '', 'ecb', '');
$key = substr($key, 0, mcrypt_enc_get_key_size($td));
$iv  = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);

//暗号化モジュール初期化
if (mcrypt_generic_init($td, $key, $iv) < 0) {
  exit('error.');
}

//データを復号化
$plain_text = mdecrypt_generic($td, base64_decode($crypt_text));

//暗号化モジュール使用終了
mcrypt_generic_deinit($td);
mcrypt_module_close($td);

//結果を表示
echo "<!DOCTYPE html>\n";
echo "<html lang=\"ja\">\n";
echo "<head>\n";
echo "<meta charset=\"utf-8\" />\n";
echo "<title>復号化テスト</title>\n";
echo "</head>\n";
echo "<body>\n";
echo "<h1>復号化テスト</h1>\n";
echo "<dl>\n";
echo "<dt>復号化前</dt><dd>" . $crypt_text . "</dd>";
echo "<dt>復号化後</dt><dd>" . $plain_text . "</dd>";
echo "</dl>\n";
echo "</body>\n";
echo "</html>\n";

?>

eVVX5OPG0Vj4L49cuq8A3KT1R2nljAIPn74vAw0d/QbYQ1CfUApMlqSpSkyDjDvi は、先のコードで暗号化した文字列です。KQAHGOEUXD は、暗号化したときと同じ値を指定します。

実行すると、以下のように表示されます。

復号化テスト

復号化前
eVVX5OPG0Vj4L49cuq8A3KT1R2nljAIPn74vAw0d/QbYQ1CfUApMlqSpSkyDjDvi
復号化後
これは秘密のメッセージです。

ページ移動

ユーティリティ

カテゴリー

検索

エントリー検索フォーム
キーワード

過去ログ

過去ログ表示フォーム
キーワード

Feed