PHP
July 21, 2008
PHP - XML(3)
XMLにおける、複数のネームスペースと複数のスキーマによるバリデーションの考察。
user要素は以下のようなprofile要素(型)を持つとする。
profile.xsdにはprofile型(複合型)、age型(単純型)、prefecture型(単純型)が含まれるため、それらを宣言する。
userにprofile要素を追加する。
user要素は以下のようなprofile要素(型)を持つとする。
<profile> <age>18</age> <prefecture>Tokyo</prefecture> </profile>このprofile型は他でも使えるかもしれない、という場合、profile型を定義するスキーマ"profile.xsd"を作成する。この時、このprofile型はネームスペース"prf"に属することとする。
profile.xsdにはprofile型(複合型)、age型(単純型)、prefecture型(単純型)が含まれるため、それらを宣言する。
<element name="profile" type="prf:PROFILE_CTYPE" /> <element name="age" type="prf:PROFILE_AGE_STYPE" /> <element name="prefecture" type="prf:PROFILE_PREFECTURE_STYPE" />単純型の定義を行う。
<simpleType name="PROFILE_AGE_STYPE">
<restriction base="integer">
<minInclusive value="18" />
<maxInclusive value="120" />
</restriction>
</simpleType>
<simpleType name="PROFILE_PREFECTURE_STYPE">
<restriction base="string" />
</simpleType>
定義した単純型を参照し、profile型を定義する。
<complexType name="PROFILE_CTYPE">
<sequence>
<element ref="prf:age" />
<element ref="prf:prefecture" />
</sequence>
</complexType>
出来上がったprofile.xsdの全体。
<?xml version="1.0" encoding="UTF-8"?>
<schema targetNamespace="http://mydomain/profns"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:prf="http://mydomain/profns">
<element name="profile" type="prf:PROFILE_CTYPE" />
<element name="age" type="prf:PROFILE_AGE_STYPE" />
<element name="prefecture" type="prf:PROFILE_PREFECTURE_STYPE" />
<simpleType name="PROFILE_AGE_STYPE">
<restriction base="integer">
<minInclusive value="18" />
<maxInclusive value="120" />
</restriction>
</simpleType>
<simpleType name="PROFILE_PREFECTURE_STYPE">
<restriction base="string" />
</simpleType>
<complexType name="PROFILE_CTYPE">
<sequence>
<element ref="prf:age" />
<element ref="prf:prefecture" />
</sequence>
</complexType>
</schema>
XMLでuserは以下のように記述される(プロフィールは除外してある)。
<user> <id>1</id> <registeredDate>2008-01-01T10:00:00</registeredDate> </user>userはネームスペース"usr"に属することとした場合、XMLは次のように変更される。
<usr:user> <usr:id>1</usr:id> <usr:registeredDate>2008-01-01T10:00:00</usr:registeredDate> </usr:user>ネームスペース"usr"のスキーマ定義を行う。
<element name="users" type="usr:USERS_CTYPE" />
<element name="user" type="usr:USER_CTYPE" />
<element name="id" type="usr:USER_ID_STYPE" />
<element name="registeredDate" type="usr:USER_REGISTERED_DATE_STYPE" />
<complexType name="USERS_CTYPE">
<sequence>
<element ref="usr:user" minOccurs="1" maxOccurs="unbounded" />
</sequence>
</complexType>
<simpleType name="USER_ID_STYPE">
<restriction base="integer" />
</simpleType>
<simpleType name="USER_REGISTERED_DATE_STYPE">
<restriction base="dateTime" />
</simpleType>
<complexType name="USER_CTYPE">
<sequence>
<element ref="usr:id" />
<element ref="usr:registeredDate" />
</sequence>
</complexType>
次に、userはprofileを持つという定義の実装を行う。profile型は外部(profile.xsd)で定義されているため、それをインポートする。
<import namespace="http://mydomain/profns"
schemaLocation="http://mydomain/xmlSchema/profile.xsd" />
インポートするとprofile型が参照できるようになる。userにprofile要素を追加する。
<complexType name="USER_CTYPE">
<sequence>
...
<element ref="prf:profile" />
</sequence>
</complexType>
出来上がったuser.xsdの全体。
<?xml version="1.0" encoding="UTF-8"?>
<schema targetNamespace="http://mydomain/userns"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:usr="http://mydomain/userns"
xmlns:prf="http://mydomain/profns">
<import namespace="http://mydomain/profns"
schemaLocation="http://mydomain/xmlSchema/profile.xsd" />
<element name="users" type="usr:USERS_CTYPE" />
<element name="user" type="usr:USER_CTYPE" />
<element name="id" type="usr:USER_ID_STYPE" />
<element name="registeredDate" type="usr:USER_REGISTERED_DATE_STYPE" />
<complexType name="USERS_CTYPE">
<sequence>
<element ref="usr:user" minOccurs="1" maxOccurs="unbounded" />
</sequence>
</complexType>
<simpleType name="USER_ID_STYPE">
<restriction base="integer" />
</simpleType>
<simpleType name="USER_REGISTERED_DATE_STYPE">
<restriction base="dateTime" />
</simpleType>
<complexType name="USER_CTYPE">
<sequence>
<element ref="usr:id" />
<element ref="usr:registeredDate" />
<element ref="prf:profile" />
</sequence>
</complexType>
</schema>
これらのスキーマ定義に沿うXMLの例。
<?xml version="1.0" encoding="utf-8"?>
<usr:users xmlns:usr="http://mydomain/userns"
xmlns:prf="http://mydomain/profns"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://mydomain/xmlSchema/user.xsd">
<usr:user>
<usr:id>1</usr:id>
<usr:registeredDate>2008-01-01T10:00:00</usr:registeredDate>
<prf:profile>
<prf:age>20</prf:age>
<prf:prefecture>Tokyo</prf:prefecture>
</prf:profile>
</usr:user>
<usr:user>
<usr:id>2</usr:id>
<usr:registeredDate>2008-02-02T15:00:00</usr:registeredDate>
<prf:profile>
<prf:age>30</prf:age>
<prf:prefecture>Aichi</prf:prefecture>
</prf:profile>
</usr:user>
</usr:users>
バリデーションのコード例。
$doc = new DOMDocument();
$doc->loadXML(file_get_contents("/path/to/users.xml"));
$xpath = new DOMXPath($doc);
$res = $xpath->evaluate("//@xsi:schemaLocation");
$doc->schemaValidate($res->item(0)->nodeValue);
試しに年齢を200にするとWarningが発生する。スキーマ定義やprofile.xsdのインポートなど、機能していることがわかる。
Warning: DOMDocument::schemaValidate() [function.DOMDocument-schemaValidate]:
Element '{http://mydomain/profns}age': [facet 'maxInclusive'] The value '200' is greater than the maximum value allowed ('120').
in /usr/local/www/data/sabel/app/index/controllers/Index.php on line 17
July 20, 2008
PHP - XML(2)
XMLにおけるスキーマ定義の基本的な検証。
以下のようなXMLの有効性を検証するスキーマ定義を作成する。
以下のようなXMLの有効性を検証するスキーマ定義を作成する。
<?xml version="1.0" encoding="utf-8"?>
<users xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://mydomain/xmlSchema/user.xsd">
<user>
<id>1</id>
<registeredDate>2008-01-01T10:00:00</registeredDate>
</user>
<user>
<id>2</id>
<registeredDate>2008-02-02T15:00:00</registeredDate>
</user>
<user>
<id>3</id>
<registeredDate>2008-03-03T20:00:00</registeredDate>
</user>
</users>
まず、複合型となる、ドキュメント要素のusersと子要素のuserを定義する。
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:element name="users" type="DEF_USERS" /> <xsd:element name="user" type="DEF_USER" /> <xsd:complexType name="DEF_USERS"> </xsd:complexType> <xsd:complexType name="DEF_USER"> </xsd:complexType> </xsd:schema>次に、usersにuserが1つ以上含まれることを定義する。x以上はminOccursで、x以下はmaxOccursで指定する。最大値がいくらでも良い場合はunboundedとする。
...
<xsd:complexType name="DEF_USERS">
<xsd:sequence>
<xsd:element ref="user" minOccurs="1" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
...
次に、userが持つ単純型の要素(idやregisteredDate)の定義を行う。typeで、idは数値型、registeredDateは日付時刻型であることを示す。データ型はstring, boolean, floatなど色々ある。こちらを参照。
...
<xsd:complexType name="DEF_USER">
<xsd:sequence>
<xsd:element name="id" type="xsd:int" />
<xsd:element name="registeredDate" type="xsd:dateTime" />
</xsd:sequence>
</xsd:complexType>
...
できあがったスキーマ定義でバリデーションを行う。
$doc = new DOMDocument();
$doc->loadXML(file_get_contents("/path/to/users.xml"));
$xpath = new DOMXPath($doc);
$res = $xpath->evaluate("//@xsi:schemaLocation");
$doc->schemaValidate($res->item(0)->nodeValue);
userのidの値を"abc"に変更するとWarningが発生する。バリデーションが正常に機能していることが分かる。
Warning: DOMDocument::schemaValidate() [function.DOMDocument-schemaValidate]: Element 'id': 'abc' is not a valid value of the atomic type 'xs:int'. in /usr/local/www/data/sabel/app/index/controllers/Index.php on line 16userを0個にした時のエラー。
Warning: DOMDocument::schemaValidate() [function.DOMDocument-schemaValidate]: Element 'users': Missing child element(s). Expected is ( user ). in /usr/local/www/data/sabel/app/index/controllers/Index.php on line 16userがageを持つように変更。しかし、ageはなくても良いものとする。また、値は整数で18〜120とする(18や120という閾値を含まない場合、minExclusiveやmaxExclusiveにする)。この場合、データ型にintegerを指定するだけでは十分なルールでないため、年齢型を作成する。
...
<xsd:simpleType name="DEF_AGE">
<xsd:restriction base="xsd:integer">
<xsd:minInclusive value="18" />
<xsd:maxInclusive value="120" />
</xsd:restriction>
</xsd:simpleType>
...
userにageの定義を追加する。データ型には作成したDEF_AGEを指定し、必須要素ではないためminOccursで0を指定する。
...
<xsd:complexType name="DEF_USER">
<xsd:sequence>
...
<xsd:element name="age" type="DEF_AGE" minOccurs="0" />
</xsd:sequence>
</xsd:complexType>
...
これでuserにageを持たせられるようになった。不正な値、例えば121とすると以下のようなエラーになる。
Warning: DOMDocument::schemaValidate() [function.DOMDocument-schemaValidate]:
Element 'age': [facet 'maxInclusive'] The value '121' is greater than the maximum value allowed ('120').
in /usr/local/www/data/sabel/app/index/controllers/Index.php on line 16
今回作成したスキーマ定義の全体(user.xsd)
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="users" type="DEF_USERS" />
<xsd:element name="user" type="DEF_USER" />
<xsd:complexType name="DEF_USERS">
<xsd:sequence>
<xsd:element ref="user" minOccurs="1" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<xsd:simpleType name="DEF_AGE">
<xsd:restriction base="xsd:integer">
<xsd:minExclusive value="18"/>
<xsd:maxInclusive value="120"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="DEF_USER">
<xsd:sequence>
<xsd:element name="id" type="xsd:int" />
<xsd:element name="registeredDate" type="xsd:dateTime" />
<xsd:element name="age" type="DEF_AGE" minOccurs="0" />
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
July 19, 2008
PHP - XML
XMLにおけるネームスペースの基本的な検証。
以下のようなXMLがある。
以下のようなXMLがある。
<?xml version="1.0" encoding="utf-8"?>
<books>
<book>
<title>book title1</title>
<author>author name1</author>
</book>
<book>
<title>book title2</title>
<author>author name2</author>
</book>
</books>
タグ名による要素の取得。
$doc = new DOMDocument();
$doc->loadXML(file_get_contents("/path/to/test.xml"));
$books = $doc->documentElement;
$titles = $books->getElementsByTagName("title");
var_dump($titles->length); // int(2)
bookの1つ目と2つ目に、それぞれネームスペースを与える。ネームスペースのURIは単なる名前で、存在するリソースを示さなくても良い。
<?xml version="1.0" encoding="utf-8"?>
<books>
<book1:book xmlns:book1="http://www.example.com/ns1">
<book1:title>book title1</book1:title>
<book1:author>author name1</book1:author>
</book1:book>
<book2:book xmlns:book2="http://www.example.com/ns2">
<book2:title>book title2</book2:title>
<book2:author>author name2</book2:author>
</book2:book>
</books>
ネームスペース名(URI)を指定した、タグ名による要素の取得をする。
$doc = new DOMDocument();
$doc->loadXML(file_get_contents("/path/to/test.xml"));
$books = $doc->documentElement;
$titles = $books->getElementsByTagName("title");
var_dump($titles->length); // int(2)
$titles = $books->getElementsByTagNameNS("http://www.example.com/ns1", "title");
var_dump($titles->length); // int(1)
$titles = $books->getElementsByTagNameNS("http://www.example.com/ns2", "title");
var_dump($titles->length); // int(1)
親要素にネームスペースが与えられ、子要素のネームスペースが省略されている場合は、子要素は親要素のネームスペースに属することになる。この時、プレフィックスは省略できる。これは先程のXML文書と同等のものとなる。
<?xml version="1.0" encoding="utf-8"?>
<books>
<book xmlns="http://www.example.com/ns1">
<title>book title1</title>
<author>author name1</author>
</book>
<book xmlns="http://www.example.com/ns2">
<title>book title2</title>
<author>author name2</author>
</book>
</books>
子要素がさらに別のネームスペースに属する場合、親要素のネームスペースはそれ以上下位に適用されない。
<?xml version="1.0" encoding="utf-8"?>
<books>
<book xmlns="http://www.example.com/ns1">
<title>book title1</title>
<author>author name1</author>
<foo xmlns="http://www.example.com/foo">
<title>foo</title>
</foo>
</book>
</books>
$doc = new DOMDocument();
$doc->loadXML(file_get_contents("/path/to/test.xml"));
$books = $doc->documentElement;
$titles = $books->getElementsByTagName("title");
var_dump($titles->length); // int(2)
$titles = $books->getElementsByTagNameNS("http://www.example.com/ns1", "title");
var_dump($titles->length); // int(1)
July 14, 2008
Sabel AjaxUploader Sample
Sabel AjaxUploaderのサンプルを用意しました。
こちらで動作の様子を確認できます(100Mまで)。
コードは以下のような感じです。ジェネレータで生成することもできますが、コード量が少ないのでコピペでも楽かと思います。
コントローラ例

こちらで動作の様子を確認できます(100Mまで)。
コードは以下のような感じです。ジェネレータで生成することもできますが、コード量が少ないのでコピペでも楽かと思います。
コントローラ例
<?php
class Index_Controllers_Upload extends Sabel_Controller_Page
{
public function upload()
{
$this->uploadId = md5hash();
}
public function fetchStatus()
{
$status = apc_fetch("upload_" . $this->request->fetchGetValue("uploadId"));
echo json_encode($status);
exit;
}
public function uploaded()
{
echo "uploaded";
exit;
}
}
uploadアクションのテンプレート例(Sabelレンダラ使用)
<script type="text/javascript" src="<?= linkto("js/Sabel.js") ?>"></script>
<script type="text/javascript" src="<?= linkto("js/helpers/AjaxUploader.js") ?>"></script>
<style type="text/css">
@import url("<?= linkto("js/helpers/css/Sabel.css") ?>");
</style>
<div id="progressBar"></div>
<form id="upload_form" enctype="multipart/form-data" action="<?= uri("a: uploaded") ?>" method="post">
<p>
<input type="hidden" name="APC_UPLOAD_PROGRESS" value="<?= $uploadId ?>" />
<input type="file" name="upfile" /><br />
<input type="submit" value="upload" />
</p>
</form>
<script type="text/javascript">
new Sabel.PHP.AjaxUploader("upload_form", "<?= uri("a: fetchStatus") ?>?uploadId=<?= $uploadId ?>", "progressBar");
</script>

July 02, 2008
Sabel 1.1 Release Announcement
Sabel-1.1のリリースが7/7(月)に決定しました。
概要は以下の通りです。

概要は以下の通りです。
- (Core)Sabel::using()でクラスファイルを読み込めたかどうかに応じてbool値を返すように変更
- (Function)get_temp_dir()関数追加
- (Function)md5hash()関数追加
- (Function)remove_nullbyte()関数追加
- (DI)DIコンテナ追加
- (Aspect)Aspect追加(AOPアライアンス準準拠)
- (Mail)メール(送信/Mimeデコード)追加
- (Http)HTTPクライアント追加
- (Annotation)バックスラッシュによるクオートのエスケープに対応
- (Response)設計の改善・ステータスオブジェクト追加
- (Request-Validator)一つの入力に対し複数のバリデーションメソッドを登録できるように改善
- (Controller-Redirector)外部サイトにリダイレクトする際などに使用するurl()メソッドが正常に動作しない問題の修正
- (View-Pager)実装の改善(3割程度の高速化)
- (View-PageViewer)実装のスリム化(setPriorityPrevious(), setPriorityNext(), setIgnoreEmpty()メソッド削除)
- (Session-Memcache)createメソッドでポート番号を指定できるように改善, addServer()メソッド追加
- (Storage-Memcache)addServer()メソッド追加
- (Cache-Memcache)addServer()メソッド追加
- (Util-FileSystem)Sabel_Util_FileSystemによりディレクトリやファイルを作成する際のデフォルトのパーミッションを744から755に変更
- (DB)バイナリデータ(画像・ファイル)の保存・取得に対応
- (DB)行のバージョニング(楽観的ロック)に対応
- (DB)sabel.db.mssql(Microsoft SQL Server)パッケージの追加
- (DB)Sabel_DB_Modelの各メソッドに対するコールバックの対応が変更
- (DB)Joinオブジェクト使用時のカラムの指定を可能に改善
- (DB-Model)selectWithChildrenメソッドを削除
- (DB-Model)save()メソッドによりモデルの状態をデータベースに反映する際、プライマリキーの値が変更されている場合に例外を投げるように変更
- (Test)Sabel_Test_Fixtureクラス追加
- (Processor-Action)リクエストバリデーションがURIクエリに対応
- (Processor-Session)session.use_trans_sidがOnかつクライアントがCOOKIEを無効にしている時にセッションIDが2重に付加される問題の修正, セッションが開始されていない際にリンクやリダイレクト先URIにセッション名が付加される問題の修正
- (JS)Sabel.Environment追加
- (JS)Sabel.Class追加
- (JS)Sabel.KeyEvent追加
- (JS)Sabel.Number追加
- (JS)Ajaxアップローダ追加(apc必須)
- (JS)Dateピッカー(カレンダー)追加
- (JS)ElementにgetRegion(), replaceClass()メソッド追加
- (JS)Sabel.Util.UriをSabel.Uriに変更
- (JS)Element.getDimentionsメソッドのバグ修正
- (JS)Effectのバグ修正
- (JS)要素の絶対位置取得メソッドのバグ修正
- (JS)現在のスクロール量取得メソッド追加
- (JS)Stringクラスの大幅な改善(sprintf, htmlspecialchars, chr, repeat追加など)
- (JS)Ajaxのオプションにtimeout, scope追加
- (JS)Eventにscope追加
- (JS)widget.Overlayの改善(Widget.Overlayにリネーム)
- (Task)各種ジェネレータ追加
- (Task)バッチ実行ファイル(sabel.php)で正常なアプリケーションルートパスが定義されない問題の修正
- (Addon-Form)Formオブジェクトが保持するHTMLを書き出すオブジェクトの初期化に不具合があったのを修正, モデルにバージョンカラムがある場合にclose()メソッドで</form>タグとともにバージョン値をhiddenで書き出すように対応
- (Addon-Renderer)ショートタグ形式の場合にHTMLエスケープするようにSabelレンダラを改善, SabelレンダラのHTMLタグを抜き出す正規表現を最小マッチに変更
- (Lib-Paginate)setOrderColumn()メソッドをsetOrderColumns()に変更, setDefaultOrder()メソッド追加, uriの指定を省略可能に改善
- rewriteモジュールがロードされていない場合にInternal Server Errorになる問題の修正
- Scaffold(sabelコマンド)にlangオプション(ja)追加
- $_SERVER["HTTP_HOST"]を参照している箇所を$_SERVER["SERVER_NAME"]を参照するように変更

June 26, 2008
Quoted-Printable Encode/Decode
phpにquoted_printable_decode()関数はあるけど、quoted_printable_encode()関数はないので、エンコードは次のように行う(mb拡張があればmb_encode_mimeheader()でQエンコーディング使えば良い)。
変換したりしなかったりなので、ヘッダでもボディでも、とにかくアンダースコアは=5Fに変換しておけば良い。
$fp = fopen("php://temp", "r+");
stream_filter_appand($fp, "convert.quoted-printable-encode",
STREAM_FILTER_READ,
array("line-length" => 74,
"line-break-chars" => "\r\n")
);
fputs($fp, $str);
rewind($fp);
$encoded = stream_get_contents($fp);
fclose($fp);
多くのMUAがヘッダに含まれるアンダースコアをスペースに変換する(ボディのアンダースコアはそのまま)。変換するMUAは、確認したところで、Outlook Express6, Thunderbird, Mail, Becky, Sylpheed, Eudora, Entourage。変換しないMUAはShuriken。変換したりしなかったりなので、ヘッダでもボディでも、とにかくアンダースコアは=5Fに変換しておけば良い。
$encoded = str_replace("_", "=5F", $encoded);
デコード時は、先程挙げた多くのMUAと同様、ヘッダの場合にのみアンダースコアをスペースに変換する。=5Fはquoted_printable_decode()関数がアンダースコアに変換するので、気にしなくて良い。
if ($isHeader) {
$str = str_replace("_", " ", $str);
}
$decoded = quoted_printable_decode($str);
June 10, 2008
Postfix - pass a mail to the php script
ある特定のユーザ、もしくはドメイン宛のメールをPHPに渡す場合のPostfixの設定。
まず、transportの設定をする。全ユーザの場合はドメイン名だけで良い。
まず、transportの設定をする。全ユーザの場合はドメイン名だけで良い。
$ vi /etc/postfix/transport user@example.com phpscript: # example.com phpscript:main.cfでこのファイルを指定する。
$ vi /etc/postfix/transport transport_maps = hash:/etc/postfix/transportpostmapコマンドでtransport.dbを更新する。
$ /usr/sbin/postmap /etc/postfix/transportmaster.cfで"phpscript"サービスの設定をする。
$ vi /etc/postfix/master.cf phpscript unix - n n - - pipe flags= user=nobody argv=/path/to/php /path/to/script.phppostfixを再起動する。
$ /etc/init.d/postfix restartphpではstdinからメールのソースを取得できる。
$ vi /path/to/script.php
<?php
$mail = file_get_contents("php://stdin");
June 09, 2008
Use sequence in each database
MySQLはカラムをAUTO_INCREMENTで定義すれば、新規連番を振ってくれる。振られた連番はLAST_INSERT_ID()関数で取得できる。
CREATE TABLE foo
(
id INTEGER PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(24) NOT NULL
)
-------------------------------------------
mysql_query("INSERT INTO foo(name) VALUES('test')", $conn);
$res = mysql_query("SELECT LAST_INSERT_ID() AS id", $conn);
$row = mysql_fetch_assoc($res);
var_dump($row["id"]);
PostgreSQLはカラムをSERIAL、またはBIGSERIALで定義すれば、新規連番を振ってくれる。振られた連番はLASTVAL()関数で取得できる。
CREATE TABLE foo
(
id SERIAL NOT NULL,
name VARCHAR(24) NOT NULL
)
-------------------------------------------
pg_query($conn, "INSERT INTO foo(name) VALUES('test')");
$res = pg_query($conn, "SELECT LASTVAL() AS id");
$row = pg_fetch_assoc($res);
var_dump($row["id"]);
SQLiteはカラムを"INTEGER PRIMARY KEY"または"INTEGER NOT NULL PRIMARY KEY"で定義すれば、新規連番を振ってくれる。振られた連番はPDOのlastInsertId()メソッドで取得できる。
CREATE TABLE foo
(
id INTEGER PRIMARY KEY,
name VARCHAR(24) NOT NULL
)
-------------------------------------------
$stmt = $pdo->prepare("INSERT INTO foo(name) VALUES('test')");
$stmt->execute();
var_dump($pdo->lastInsertId());
Firebirdではシーケンス(旧ジェネレータ)を作成し、GEN_ID()関数にそのシーケンス名を渡し新規連番を取得する。その値でINSERTする。
CREATE TABLE FOO
(
ID INTEGER NOT NULL PRIMARY KEY,
NAME VARCHAR(24) NOT NULL
)
CREATE SEQUENCE FOO_ID_SEQ;
-------------------------------------------
$res = ibase_query($conn, 'SELECT GEN_ID(FOO_ID_SEQ, 1) AS ID FROM RDB$DATABASE');
$row = ibase_fetch_assoc($res);
$lastId = $row["ID"];
ibase_query($conn, "INSERT INTO FOO(ID, NAME) VALUES({$lastId}, 'test')");
var_dump($lastId);
Oracleではシーケンスオブジェクトを作成し、NEXTVALにより新規連番を取得する。その値でINSERTする。
CREATE TABLE FOO
(
ID INTEGER NOT NULL PRIMARY KEY,
NAME VARCHAR(24) NOT NULL
)
CREATE SEQUENCE FOO_ID_SEQ;
-------------------------------------------
$stmt = oci_parse($conn, "SELECT FOO_ID_SEQ.NEXTVAL AS ID FROM DUAL");
oci_execute($stmt, OCI_COMMIT_ON_SUCCESS);
$row = oci_fetch_assoc($stmt);
$lastId = $row["ID"];
$stmt = oci_parse($conn, "INSERT INTO FOO(ID, NAME) VALUES({$lastId}, 'test')");
oci_execute($stmt, OCI_COMMIT_ON_SUCCESS);
var_dump($lastId);
SQL ServerはカラムをIDENTIFYで定義すれば、新規連番を振ってくれる。振られた連番はSCOPE_IDENTITY()関数で取得できる。
CREATE TABLE foo
(
id INTEGER IDENTITY(1, 1) NOT NULL,
name VARCHAR(24) NOT NULL
)
-------------------------------------------
mssql_query("INSERT INTO foo(name) VALUES('test')", $conn);
$res = mssql_query("SELECT SCOPE_IDENTITY() AS id", $conn);
$row = mssql_fetch_assoc($res);
var_dump($row["id"]);
June 08, 2008
php - cloning an object
あるクラスの多くのインスタンスを必要とする時、newでそれぞれのインスタンスを生成するよりも、一つのインスタンスを複製(クローン)した方がパフォーマンスが良い。
ただ、「使えるところ」はあまりないかも。
class Foo
{
public $obj = null;
public $var = "foo";
public function __construct()
{
$obj = new stdClass();
$obj->a = 10;
$obj->b = 20;
$this->obj = $obj;
}
}
まずは一つ一つ、それぞれnewでインスタンスを生成する。
$objs = array();
$start = microtime();
for ($i = 0; $i < 10000; $i++) {
$objs[] = new Foo();
}
var_dump(microtime() - $start);
var_dump(memory_get_peak_usage(true));
上記コードの所要時間と使用メモリ量。
float(0.0505634) // 50.6 msec int(6553600) // 6554 KB次は最初にインスタンスを生成し、それを複製する。
$objs = array();
$start = microtime();
$foo = new Foo();
for ($i = 0; $i < 10000; $i++) {
$objs[] = clone $foo;
}
var_dump(microtime() - $start);
var_dump(memory_get_peak_usage(true));
結果、所要時間は先程の半分以下、メモリ使用量は約半分になる。
float(0.019881) // 19.9 msec int(3407872) // 3408 KBメモリ使用量が減るのは、それぞれのインスタンスが保持する$objが同じオブジェクトを参照しているため。以下のコードを実行することでそれを確認できる。
$foo = new Foo(); $foo2 = clone $foo; $foo2->var = "bar"; $foo2->obj->a = 100; $foo2->obj->b = 200; var_dump($foo->var); // "foo" var_dump($foo2->var); // "bar" var_dump($foo->obj->a); // 100 var_dump($foo2->obj->a); // 100 var_dump($foo->obj->b); // 200 var_dump($foo2->obj->b); // 200この挙動だと困る場合は__clone()メソッド(クローンの時にコールされるマジックメソッド)を実装し、内部のオブジェクトも複製する。
class Foo
{
...
public function __clone()
{
$this->obj = clone $this->obj;
}
}
$foo = new Foo(); $foo2 = clone $foo; $foo2->var = "bar"; $foo2->obj->a = 100; $foo2->obj->b = 200; var_dump($foo->var); // "foo" var_dump($foo2->var); // "bar" var_dump($foo->obj->a); // 10 var_dump($foo2->obj->a); // 100 var_dump($foo->obj->b); // 20 var_dump($foo2->obj->b); // 200ただ、これだと一つ一つインスタンスを生成するのとそう変わらない。
float(0.0442957) // 44.3 msec int(6029312) // 6030 KB使えるところでは使ったほうが吉。
ただ、「使えるところ」はあまりないかも。
June 03, 2008
Regexp - 文字列の否定
過去に正規表現で文字列の否定はできないというのをどこかで見て、そういうものだと思っていたのだが、実はできるようだ。エンジンによって、できるのとできないのがあるのかもしれない。
<?php $regexp = "/^foo(?!123).*$/"; var_dump(preg_match($regexp, "foo123bar") === 1); // false var_dump(preg_match($regexp, "foo456bar") === 1); // true var_dump(preg_match($regexp, "foo789bar") === 1); // true $regexp = "/^foo(?!123|789).*$/"; var_dump(preg_match($regexp, "foo123bar") === 1); // false var_dump(preg_match($regexp, "foo456bar") === 1); // true var_dump(preg_match($regexp, "foo789bar") === 1); // false $regexp = "/^foo[^(123)].*$/"; var_dump(preg_match($regexp, "foo123bar") === 1); // false var_dump(preg_match($regexp, "foo456bar") === 1); // true var_dump(preg_match($regexp, "foo789bar") === 1); // true $regexp = "/^foo[^(123|789)].*$/"; var_dump(preg_match($regexp, "foo123bar") === 1); // false var_dump(preg_match($regexp, "foo456bar") === 1); // true var_dump(preg_match($regexp, "foo789bar") === 1); // false