いつのまにかZF2が出てたので、今さら手元に残ってたZF1の未公開記事を大放出しちゃうシリーズ。
ページャーはPear::Pagerが優秀すぎてとりあえずそれ使っとけば安泰ではあるのですが、ZFにもZend_Paginatorが存在します。
特徴はZend_Db_Selectあたりと連携すると、全件数全ページ数などの取得も自動的に行ってくれることです。
公式の使用例は何故かルータやZend_Viewなどの使用が前提になっていて面倒なので、とりあえずZend_Paginatorのみ使う方向で作ってみます。
素材は昔作ったページャー。
配列ならいったんZend_Paginator_Adapter_Arrayに突っ込んでからZend_Paginatorを呼び出すという形になります。
他のアダプタとしては、Zend_Db_Selectインスタンスをそのまま渡すだけでcountとかlimitとかを自動的に発行してページャーやデータを取得したりしてくれるすごいアダプタZend_Paginator_Adapter_DbSelect、単にページャーだけが必要でデータそのものは不要なときに使うZend_Paginator_Adapter_Nullなどがあります。
アダプタの指定がよくわからないという場合は、コンストラクタのかわりにZend_Paginator::factory()メソッドを使うと、概ねよきにはからってくれます。
なお、こちらはPear::Pagerと違って、HTMLのリンクを作成したりしてくれる機能はありません。
そこらへんは自力で実装することになります。
Zend_Viewを使用している場合は、Zend_View_Helper_PaginationControlというヘルパーでできるようになるみたいです。
まあ、機能の単位という点では確かにこちらの実装の方が正しいのかもしれませんが、やはり具体的なリンクまで簡単に作ってくれるPear::Pagerのほうが便利ですね。
ページャーはPear::Pagerが優秀すぎてとりあえずそれ使っとけば安泰ではあるのですが、ZFにもZend_Paginatorが存在します。
特徴はZend_Db_Selectあたりと連携すると、全件数全ページ数などの取得も自動的に行ってくれることです。
公式の使用例は何故かルータやZend_Viewなどの使用が前提になっていて面倒なので、とりあえずZend_Paginatorのみ使う方向で作ってみます。
素材は昔作ったページャー。
<?php
require_once('Zend/Paginator.php');
require_once('Zend/Paginator/Adapter/Array.php');
//適当な配列
$arr=array();
for($i=1;$i<1000;$i++){
$arr[]=array(
'id'=>$i
,'md5'=>md5($i)
);
}
//ページャー
$zendPaginator = new Zend_Paginator(new Zend_Paginator_Adapter_Array($arr));
//設定
//現在のページ数
$zendPaginator->setCurrentPageNumber(5);
//1ページに表示する件数
$zendPaginator->setItemCountPerPage(20);
//前後合わせて表示するページ数
$zendPaginator->setPageRange(7);
//ページング情報を取得
$pager = $zendPaginator->getPages();
print('全ページ数:' . $pager->pageCount);
print('次のページ:' . $pager->next);
//アイテムを取得
//現在のページのアイテムを取得
$zendPaginator->getCurrentItems();
//指定したページのアイテムを取得
$zendPaginator->getItemsByPage(15);
//JSON形式で取得
$zendPaginator->toJson();
//foreachすると、現在のページのアイテムだけをforeachする
foreach($zendPaginator as $key=>$val){
print($val['id'] . ' : ' . $val['md5']);
}
Zend_Paginatorのインスタンスには配列を渡すことはできず、アダプタのインスタンスを渡さなければなりません。配列ならいったんZend_Paginator_Adapter_Arrayに突っ込んでからZend_Paginatorを呼び出すという形になります。
他のアダプタとしては、Zend_Db_Selectインスタンスをそのまま渡すだけでcountとかlimitとかを自動的に発行してページャーやデータを取得したりしてくれるすごいアダプタZend_Paginator_Adapter_DbSelect、単にページャーだけが必要でデータそのものは不要なときに使うZend_Paginator_Adapter_Nullなどがあります。
アダプタの指定がよくわからないという場合は、コンストラクタのかわりにZend_Paginator::factory()メソッドを使うと、概ねよきにはからってくれます。
なお、こちらはPear::Pagerと違って、HTMLのリンクを作成したりしてくれる機能はありません。
そこらへんは自力で実装することになります。
Zend_Viewを使用している場合は、Zend_View_Helper_PaginationControlというヘルパーでできるようになるみたいです。
まあ、機能の単位という点では確かにこちらの実装の方が正しいのかもしれませんが、やはり具体的なリンクまで簡単に作ってくれるPear::Pagerのほうが便利ですね。
PR
いつのまにかZF2が出てたので、今さら手元に残ってたZF1の未公開記事を大放出しちゃうシリーズ。
http://framework.zend.com/manual/ja/zend.config.writer.introduction.html
> Zend_Config_Writer は、設定ファイルに Zend_Config オブジェクトを書き出します。
config.iniとかconfig.xmlとかを勝手に作ってくれるというものです。
これ単品ではあまり使用価値はありませんが、大規模なアプリを作った際にプログラムから設定を変更したい、といったときに重宝すると思われます。
さっそく使ってみる。
config.ini
config.json
config.xml
config.yaml
config.php
Zend_Configの素材として使った配列はこちらです。
見事に設定ファイルの形式になりました。
つまりはZend_Configのちょうど正反対のことをやってくれるというわけです。
なにげにjson_encode()やsimplexml_load_string()、yaml_emit()といった統一性のない関数を使わずに、Zend_ConfigとZend_Config_Writerで統一的なフォーマット変換が行えるので、むしろそっち用途としてのほうが便利なのではなかろうか。
http://framework.zend.com/manual/ja/zend.config.writer.introduction.html
> Zend_Config_Writer は、設定ファイルに Zend_Config オブジェクトを書き出します。
config.iniとかconfig.xmlとかを勝手に作ってくれるというものです。
これ単品ではあまり使用価値はありませんが、大規模なアプリを作った際にプログラムから設定を変更したい、といったときに重宝すると思われます。
さっそく使ってみる。
<?php
header('Content-type: text/html; charset=UTF-8');
require_once('Zend/Config.php');
require_once('Zend/Config/Writer/Ini.php');
require_once('Zend/Config/Writer/Json.php');
require_once('Zend/Config/Writer/Xml.php');
require_once('Zend/Config/Writer/Yaml.php');
require_once('Zend/Config/Writer/Array.php');
//適当なコンフィグを作成
$zendConfig = new Zend_Config(array(), true);
//stagingはproductionを継承する
$zendConfig->setExtend('staging', 'production');
//production、stagingの設定
$configProductionArray = array(
'webhost' => 'www.example.com',
'database' => array(
'adapter' => 'pdo_mysql',
'params' => array(
'host' => 'db.example.com',
'username' => 'dbuser',
'password' => 'secret',
'dbname' => 'dbname'
)
)
);
$configStagingArray = array(
'database' => array(
'params' => array(
'username' => 'staging.example.com',
'password' => 'staginguser',
'dbname' => 'stagingsecret'
)
)
);
$zendConfig->production = $configProductionArray;
$zendConfig->staging = $configStagingArray;
//config.iniに保存
$zendConfigWriterIni = new Zend_Config_Writer_Ini(array(
'filename' => 'config.ini'
,'config' => $zendConfig
));
$zendConfigWriterIni->write();
//config.jsonに保存
$zendConfigWriterJson = new Zend_Config_Writer_Json(array(
'filename' => 'config.json'
,'config' => $zendConfig
));
$zendConfigWriterJson->write();
//config.xmlに保存
$zendConfigWriterXml = new Zend_Config_Writer_Xml(array(
'filename' => 'config.xml'
,'config' => $zendConfig
));
$zendConfigWriterXml->write();
//config.yamlに保存
$zendConfigWriterYaml = new Zend_Config_Writer_Yaml(array(
'filename' => 'config.yaml'
,'config' => $zendConfig
));
$zendConfigWriterYaml->write();
//config.phpに保存
$zendConfigWriterArray = new Zend_Config_Writer_Array(array(
'filename' => 'config.php'
,'config' => $zendConfig
));
$zendConfigWriterArray->write();
config.ini
[production] webhost = "www.example.com" database.adapter = "pdo_mysql" database.params.host = "db.example.com" database.params.username = "dbuser" database.params.password = "secret" database.params.dbname = "dbname" [staging : production] database.params.username = "dev.example.com" database.params.password = "devuser" database.params.dbname = "devsecret"
config.json
{
"production":{
"webhost":"www.example.com","database":{
"adapter":"pdo_mysql","params":{
"host":"db.example.com","username":"dbuser","password":"secret","dbname":"dbname"
}
}
},"staging":{
"database":{
"params":{
"username":"dev.example.com","password":"devuser","dbname":"devsecret"
}
},"_extends":"production"
}
}
config.xml
<?xml version="1.0"?>
<zend-config xmlns:zf="http://framework.zend.com/xml/zend-config-xml/1.0/">
<production>
<webhost>www.example.com</webhost>
<database>
<adapter>pdo_mysql</adapter>
<params>
<host>db.example.com</host>
<username>dbuser</username>
<password>secret</password>
<dbname>dbname</dbname>
</params>
</database>
</production>
<staging zf:extends="production">
<database>
<params>
<username>dev.example.com</username>
<password>devuser</password>
<dbname>devsecret</dbname>
</params>
</database>
</staging>
</zend-config>
config.yaml
production:
webhost: www.example.com
database:
adapter: pdo_mysql
params:
host: db.example.com
username: dbuser
password: secret
dbname: dbname
staging:
database:
params:
username: dev.example.com
password: devuser
dbname: devsecret
_extends: production
config.php
<?php
return array (
'production' =>
array (
'webhost' => 'www.example.com',
'database' =>
array (
'adapter' => 'pdo_mysql',
'params' =>
array (
'host' => 'db.example.com',
'username' => 'dbuser',
'password' => 'secret',
'dbname' => 'dbname',
),
),
),
'staging' =>
array (
'database' =>
array (
'params' =>
array (
'username' => 'dev.example.com',
'password' => 'devuser',
'dbname' => 'devsecret',
),
),
),
);
Zend_Configの素材として使った配列はこちらです。
見事に設定ファイルの形式になりました。
つまりはZend_Configのちょうど正反対のことをやってくれるというわけです。
なにげにjson_encode()やsimplexml_load_string()、yaml_emit()といった統一性のない関数を使わずに、Zend_ConfigとZend_Config_Writerで統一的なフォーマット変換が行えるので、むしろそっち用途としてのほうが便利なのではなかろうか。
前回Smarty3のSmartyBCクラスを利用して、Smarty2形式の書き方をしました。
今回は同じものをSmarty3形式で書いてみます。
はい、そのとおりです。
テンプレート側については何一つ変更の必要はありません。
これは、Smarty3のテンプレートは、ロジック側と違って後方互換性を残しているからです。
実際は{foreach}の推奨される使い方も変わっているのですが、Smarty2形式でも全く問題無く動作します。
ですが、せっかくなのでSmarty3形式にしてみましょう。
index.html
配列の要素に.でアクセスできるのはこれまでどおりですが、それとは別に@を繋ぐことで特殊な用途に使用できます。
@keyで配列のキーに、@firstで初回のループだけtrueにといった、これまで$smarty.foreachという遠回りな使い道だった要素も直接使用できるようになります。
.keyではきちんと$hoge['key']にアクセスしてくれるので、うっかりキーが被ったなどという事故もありません。
Smarty2と同じ使い方をするかぎり、使い勝手が微妙によくなったマイナーバージョンアップと考えても問題なさそうです。
今回は同じものをSmarty3形式で書いてみます。
<?php
//Smartyパス
$smartyDir = dirname(__FILE__) . '/../../fw/smarty/';
$smartyFile = $smartyDir . 'Smarty.class.php';
$smartyTemplateDir = $smartyDir . 'templates/';
$smartyCompileDir = $smartyDir . 'templates_c/';
//Smarty
require_once($smartyFile);
$smarty = new Smarty();
$smarty->template_dir = $smartyTemplateDir;
$smarty->compile_dir = $smartyCompileDir;
//フィルタ
$smarty->registerFilter('pre', 'smartyPreFilter');
$smarty->registerFilter('post', 'smartyPostFilter');
$smarty->registerFilter('output', 'smartyOutputFilter');
function smartyPreFilter($tpl_source, $smarty){
//テンプレートをコンパイルする前に行う処理
//{}等のテンプレートがそのまま渡される
return $tpl_source;
}
function smartyPostFilter($tpl_source, $smarty){
//テンプレートをコンパイルした後に行う処理
//{}等のSmarty関数、修正子等はパースされPHPに置換されている
return $tpl_source;
}
function smartyOutputFilter($tpl_source, $smarty){
//HTMLの出力直前に行う処理
//Smarty関数、PHP共に処理された後で、ただのHTMLの状態
return $tpl_source;
}
//修正子登録
$smarty->registerPlugin('modifier', 'testModifier', 'smartyTestModifier');
function smartyTestModifier($name, $param1, $param2, $param3 = NULL){
//修正子testModifierが使用されたときに呼び出される
return $param1 . strip_tags($name) . $param2;
}
//変数セット
$smarty->assign('title' , 'タイトル');
$smarty->assign('meta' , array('description'=>'description'));
$smarty->assign('hoge' , array('foo', 'bar', 'baz'));
//表示
$smarty->display('index.html');
メソッド名が変わっただけじゃないか。はい、そのとおりです。
テンプレート側については何一つ変更の必要はありません。
これは、Smarty3のテンプレートは、ロジック側と違って後方互換性を残しているからです。
実際は{foreach}の推奨される使い方も変わっているのですが、Smarty2形式でも全く問題無く動作します。
ですが、せっかくなのでSmarty3形式にしてみましょう。
index.html
{include file='header.html'}
<body>
$hogeを表示<br />
{foreach $hoge as $item}
KEY:{$item@key} - ITEM:{$item}<br />
{if $item@last}最後のループ<br />{/if}
{/foreach}
<br />
{'XSS!!<script>alert("xss");</script>XSS!!'|testModifier:"aaa":"bbb"}
</body>
</html>
foreachはPHPと同くasを使うようになっています。配列の要素に.でアクセスできるのはこれまでどおりですが、それとは別に@を繋ぐことで特殊な用途に使用できます。
@keyで配列のキーに、@firstで初回のループだけtrueにといった、これまで$smarty.foreachという遠回りな使い道だった要素も直接使用できるようになります。
.keyではきちんと$hoge['key']にアクセスしてくれるので、うっかりキーが被ったなどという事故もありません。
Smarty2と同じ使い方をするかぎり、使い勝手が微妙によくなったマイナーバージョンアップと考えても問題なさそうです。
これまでずっとSmarty2を使っていたんですが、いいかげんそろそろSmarty3に乗り換えてみます。
執筆時点での最新版は3.1.5でした。
ダウンロード・解凍後、libsフォルダを適当なところに突っ込みます。
中にはSmarty.phpおよびSmartyBC.phpがありますが、どうも中身を見るかぎりSmartyBC.phpは後方互換のために用意されたクラスみたいです。
Smarty2ではメソッド名などがスネークケースでしたが、Smarty3ではキャメルケースに改められています。
その橋渡しをするためのラッパクラスのようで、中身は大体こんなかんじです。
public function assign_by_ref($tpl_var, &$value){
$this->assignByRef($tpl_var, $value);
}
なのでSmarty2から3にバージョンアップする際はとりあえずSmartyBCにしておいて、徐々にメソッドなどをSmarty3仕様に差し替えていくといいんじゃないかな。
ということでひとまずSmartyBCを使ってみます。
しかしびっくりするほど検索にひっかからんね、SmartyBC。
テンプレート側には大きな変更が加えられています。
変数のネストや複雑な構文などが色々できるようになっているみたいですが、とりあえずSmarty2のテンプレートを移植するにあたって気をつけるところはスペースです。
Smarty2では{ $hoge }とか書いても有効だったんですが、3では{$hoge }と前を詰めないといけなくなっているようです。
後ろはいくら開いていても大丈夫みたいです。
どこか設定があるのかもしれませんがよくわかりませんでした。
とりあえずSmarty2形式でサンプルを作成。
index.php
試してないんですがSmarty2の機能しか使ってないのでSmarty2でも動くと思います。
普通にSmarty2の使い方でほぼ問題無く使えてしまった。
執筆時点での最新版は3.1.5でした。
ダウンロード・解凍後、libsフォルダを適当なところに突っ込みます。
中にはSmarty.phpおよびSmartyBC.phpがありますが、どうも中身を見るかぎりSmartyBC.phpは後方互換のために用意されたクラスみたいです。
Smarty2ではメソッド名などがスネークケースでしたが、Smarty3ではキャメルケースに改められています。
その橋渡しをするためのラッパクラスのようで、中身は大体こんなかんじです。
public function assign_by_ref($tpl_var, &$value){
$this->assignByRef($tpl_var, $value);
}
なのでSmarty2から3にバージョンアップする際はとりあえずSmartyBCにしておいて、徐々にメソッドなどをSmarty3仕様に差し替えていくといいんじゃないかな。
ということでひとまずSmartyBCを使ってみます。
しかしびっくりするほど検索にひっかからんね、SmartyBC。
テンプレート側には大きな変更が加えられています。
変数のネストや複雑な構文などが色々できるようになっているみたいですが、とりあえずSmarty2のテンプレートを移植するにあたって気をつけるところはスペースです。
Smarty2では{ $hoge }とか書いても有効だったんですが、3では{$hoge }と前を詰めないといけなくなっているようです。
後ろはいくら開いていても大丈夫みたいです。
どこか設定があるのかもしれませんがよくわかりませんでした。
とりあえずSmarty2形式でサンプルを作成。
index.php
<?php
//Smartyパス
$smartyDir = dirname(__FILE__) . '/../../fw/smarty/';
$smartyFile = $smartyDir . 'SmartyBC.class.php';
$smartyTemplateDir = $smartyDir . 'templates/';
$smartyCompileDir = $smartyDir . 'templates_c/';
//Smarty
require_once($smartyFile);
$smarty = new SmartyBC();
$smarty->template_dir = $smartyTemplateDir;
$smarty->compile_dir = $smartyCompileDir;
//フィルタ
$smarty->register_prefilter('smartyPreFilter');
$smarty->register_postfilter('smartyPostFilter');
$smarty->register_outputfilter('smartyOutputFilter');
function smartyPreFilter($tpl_source, $smarty){
//テンプレートをコンパイルする前に行う処理
//{}等のテンプレートがそのまま渡される
return $tpl_source;
}
function smartyPostFilter($tpl_source, $smarty){
//テンプレートをコンパイルした後に行う処理
//{}等のSmarty関数、修正子等はパースされPHPに置換されている
return $tpl_source;
}
function smartyOutputFilter($tpl_source, $smarty){
//HTMLの出力直前に行う処理
//Smarty関数、PHP共に処理された後で、ただのHTMLの状態
return $tpl_source;
}
//修正子登録
$smarty->register_modifier('testModifier', 'smartyTestModifier');
function smartyTestModifier($name, $param1, $param2, $param3 = NULL){
//修正子testModifierが使用されたときに呼び出される
return $param1 . strip_tags($name) . $param2;
}
//変数セット
$smarty->assign('title' , 'タイトル');
$smarty->assign('meta' , array('description'=>'description'));
$smarty->assign('hoge' , array('foo', 'bar', 'baz'));
//表示
$smarty->display('index.html');
index.html
{include file='header.html'}
<body>
$hogeを表示<br />
{foreach from=$hoge key=key item=item name=loop}
KEY:{$key} - ITEM:{$item}<br />
{if $smarty.foreach.loop.last}最後のループ<br />{/if}
{/foreach}
<br />
修正子を実行<br />
{'XSS!!<script>alert("xss");</script>XSS!!'|testModifier:"aaa":"bbb"}
XSSの部分は$name、"aaa"が$param1、"bbb"が$param2というふうに割り当てられる
</body>
</html>
header.html
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta http-equiv=" Content-Type" content="text/html; charset=UTF-8">
<title>{$title|escape|default:''}</title>
<meta name="description" content="{$meta.description|escape|default:''}">
<meta name="keywords" content="{$meta.keywords|escape|default:''}">
</head>
register_prefilterとか普段は使わないんですが、今回はサンプルなのでとりあえず置いてます。試してないんですがSmarty2の機能しか使ってないのでSmarty2でも動くと思います。
普通にSmarty2の使い方でほぼ問題無く使えてしまった。
あ、みんな忘れてると思うけどここPHPのブログです。
MODX EvolutionにはDBAPIというデータベース接続クラスが用意されています。
が、中を見てみたところ相当がっかりな作りでした。
function delete($from,$where='',$limit=''){
if($where != '') $where = "WHERE {$where}";
これはひどい。
escape()の引数はバラで与えないといけないのに、select()やdelete()のwhere句は文字列をそのまま使うので、呼び出し側でいちいち組み立てないといけません。
こんな危険な作りだったら最初から使わない方が安全ですし、どうせ作るのであればbindValue()くらい実装してくれと。
まあ、いちいち接続を確立しなくても$modx->dbから常に使えるのが利点ですので、危険性を理解した上で使うならいいのではないでしょうか。
getColumn()やprepareDate()のような意味のわからないメソッドがある一方、beginやprepare、bindといった必須機能がないなど、
正直素直にPear::MDB2でも使ってくれと言わざるをえない。
MODX EvolutionにはDBAPIというデータベース接続クラスが用意されています。
が、中を見てみたところ相当がっかりな作りでした。
function delete($from,$where='',$limit=''){
if($where != '') $where = "WHERE {$where}";
これはひどい。
escape()の引数はバラで与えないといけないのに、select()やdelete()のwhere句は文字列をそのまま使うので、呼び出し側でいちいち組み立てないといけません。
こんな危険な作りだったら最初から使わない方が安全ですし、どうせ作るのであればbindValue()くらい実装してくれと。
まあ、いちいち接続を確立しなくても$modx->dbから常に使えるのが利点ですので、危険性を理解した上で使うならいいのではないでしょうか。
| DBAPI ( [ $host='',$dbase='', $uid='',$pwd='',$prefix=NULL,$charset='',$connection_method='SET CHARACTER SET' ] ) | |
|---|---|
|
コンストラクタ 接続自体は行わないので別途connect()を呼ぶ必要がある |
|
| 引数 | 解説 |
| $host |
ホスト名。 未指定の場合global $database_server |
| $dbase |
データベース名。 未指定の場合global $dbase |
| $uid |
データベースユーザ名。 未指定の場合global $database_user |
| $pwd |
データベースユーザのパスワード。 未指定の場合global $database_password |
| $prefix |
テーブル名の接頭辞。 未指定の場合global $table_prefix |
| $charset |
MySQLの文字コード。 未指定の場合global $database_connection_charset。 下記$connection_methodおよびmysql_set_charset()で使用する。 |
| $connection_method |
'SET CHARACTER SET' . $charsetコマンド。 global $database_connection_methodが優先。 mysql_set_charset()が存在する場合はそちらも実行するが、$connection_methodも必ず実行されてしまう。 |
| connect ( [ $host = '', $dbase = '', $uid = '', $pwd = '', $persist = 0 ] ) | |
|---|---|
| データベースサーバに接続する | |
| 引数 | 解説 |
| $host |
ホスト名。 未指定の場合コンストラクタで設定した値。 |
| $dbase |
データベース名。 未指定の場合コンストラクタで設定した値。 |
| $uid |
データベースユーザ名。 未指定の場合コンストラクタで設定した値。 |
| $pwd |
データベースユーザのパスワード。 未指定の場合コンストラクタで設定した値。 |
| $persist | 0以外の場合mysql_pconnect()で接続する。 |
| disconnect () | |
|---|---|
| データベースサーバへの接続を閉じる |
| escape ( $s ) | |
|---|---|
| 文字列をエスケープする | |
| 引数 | 解説 |
| $s | 引数の文字列をエスケープして返す |
| query ( $sql ) | |
|---|---|
| 任意のSQLを実行する | |
| 引数 | 解説 |
| $sql | 引数のSQLをmysql_query()した結果を返す。 |
| delete ( $from, [ $where='',$limit='' ] ) | |
|---|---|
| deleteを実行する | |
| 引数 | 解説 |
| $from | テーブル名。 |
| $where |
where句。 未指定の場合全部消える。 |
| $limit | limit句。 |
| select ( [ $fields = "*", $from = '', $where = '', $orderby = '', $limit = '' ] ) | |
|---|---|
| selectを実行する | |
| 引数 | 解説 |
| $fields | 取得するカラム名を文字列で指定。 |
| $from | テーブル名。 |
| $where |
where句。 文字列で指定。 |
| $orderby |
oder by句。 文字列で指定。 |
| $limit |
limit句。 文字列で指定。 |
| update ( $fields, $table, [ $where = '' ] ) | |
|---|---|
| updateを実行する | |
| 引数 | 解説 |
| $fields |
アップデートする内容。 文字列の場合UPDATE文の後にそのまま入る。 配列の場合'key'=>'value'に展開される。 |
| $table | テーブル名。 |
| $where |
where句。 文字列で指定。 |
| insert ( $fields, $intotable, [ $fromfields = "*", $fromtable = '', $where = '', $limit = '' ] ) | |
|---|---|
| insertを実行し、mysql_insert_id()を返す。 | |
| 引数 | 解説 |
| $fields |
インサートする内容。 文字列の場合UPDATE文の後にそのまま入る。 配列の場合'key'と'value'に展開される。 |
| $intotable | insertするテーブル名。 |
| $fromfields | INSERT SELECTを使用する場合のFROMカラムを文字列で指定。 |
| $fromtable |
INSERT SELECTを使用する場合のFROMテーブル。 指定するとINSERT SELECTになる。 |
| $where | INSERT SELECTを使用する場合のwhere句を文字列で指定。 |
| $limit | INSERT SELECTを使用する場合のlimit句を文字列で指定。 |
| getInsertId ( [ $conn=NULL ] ) | |
|---|---|
| mysql_insert_id()を返す。 | |
| 引数 | 解説 |
| $conn | 指定すると該当リソースのmysql_insert_id()を返す。 |
| getAffectedRows ( [ $conn=NULL ] ) | |
|---|---|
| mysql_affected_rows()を返す。 | |
| 引数 | 解説 |
| $conn | 指定すると該当リソースのmysql_affected_rows()を返す。 |
| getLastError ( [ $conn=NULL ] ) | |
|---|---|
| mysql_error()を返す。 | |
| 引数 | 解説 |
| $conn | 指定すると該当リソースのmysql_error()を返す。 |
| getLastError ( $ds ) | |
|---|---|
| 引数リソースの行数を返す。 | |
| 引数 | 解説 |
| $ds | 該当リソースのmysql_num_rows()を返す。 |
| getRow ( $ds, [ $mode = 'assoc' ] ) | |
|---|---|
| 引数リソースから1行取得して返す。 | |
| 引数 | 解説 |
| $ds | 該当リソースを処理する。 |
| $mode |
フェッチモード。 'assoc','num','both'から指定。デフォルト'assoc' |
| getColumn ( $name, $dsq ) | |
|---|---|
| ひとつの列を取得して返す。 | |
| 引数 | 解説 |
| $name | 指定した列を返す。 |
| $dsq |
リソースであればそのリソースを処理。 文字列であればSQLとして実行し、その結果に対して処理。 |
| 例 |
$modx->db->getColumn('id', 'SELECT * FROM table');とすると、 SQLを実行してid列のみを拾って連想配列にして返す。 正直使いどころがわからん。 |
| getColumnNames ( $dsq ) | |
|---|---|
| リソースに含まれる列名を取得 | |
| 引数 | 解説 |
| $dsq |
リソースであればそのリソースを処理。 文字列であればSQLとして実行し、その結果に対して処理。 |
| 例 | $modx->db->getColumnNames('SELECT * FROM table');とすると、 テーブルtableの列名のリストが返る。 |
| getValue ( $dsq ) | |
|---|---|
| リソースの最初の列の最初の行を取得 | |
| 引数 | 解説 |
| $dsq |
リソースであればそのリソースを処理。 文字列であればSQLとして実行し、その結果に対して処理。 |
| 例 |
$modx->db->getValue('SELECT id,name FROM table');とすると、1行目のidが返る。 繰り返し呼ぶと次の行のidが返る。かなり存在意義不明。 |
| getXML ( $dsq ) | |
|---|---|
| リソースの取得結果をXMLにして返す | |
| 引数 | 解説 |
| $dsq |
リソースであればそのリソースを処理。 文字列であればSQLとして実行し、その結果に対して処理。 何故DB接続クラスに置いているのか。 |
| getTableMetaData ( $table ) | |
|---|---|
| テーブルのメタデータを返す。 | |
| 引数 | 解説 |
| $table | テーブルに対して'SHOW METADATA'を実行し、返り値を連想配列で返す。 |
| prepareDate ( $timestamp, [ $fieldType = 'DATETIME' ] ) | |
|---|---|
| タイムスタンプを日時形式にして返す | |
| 引数 | 解説 |
| $timestamp | UNIXタイムスタンプ |
| $fieldType |
タイムスタンプをdate()した結果を返す。 フォーマットは以下の四種類だけ。 'DATE'=>'Y-m-d'、'TIME'=>'H:i:s'、'YEAR'=>'Y'、その他=>'Y-m-d H:i:s' |
| getHTMLGrid ( $dsq, $params ) | |
|---|---|
|
DataGridを使用して表を作成する DataGrid::render()した結果を返す。 |
|
| 引数 | 解説 |
| $dsq |
リソースであればそのリソースを処理。 文字列であればSQLとして実行し、その結果に対して処理。 |
| $params |
DataGridで使用するパラメータを配列で指定。 詳細はDataGridを参考にすること。 |
| makeArray ( $rs ) | |
|---|---|
|
引数リソースの全行を取得して返す。 名前が物凄くわかりにくいがfetchAll()。 |
|
| 引数 | 解説 |
| $rs | 処理するリソース。 |
| getVersion () | |
|---|---|
| mysql_get_server_info()を返す。 |
getColumn()やprepareDate()のような意味のわからないメソッドがある一方、beginやprepare、bindといった必須機能がないなど、
正直素直にPear::MDB2でも使ってくれと言わざるをえない。
http://yuubiseiharukana.blog.shinobi.jp/Entry/724/
> とても簡単に認可制御が行えました。
> あとは各ユーザ毎にmemberかadminかを示すフラグを設定すれば完成、めでたしめでたし。
なわけがない。
今時権限をソースにベタ書きとかねーよ普通管理画面から制御してDB保存だろ。
とまあそこらへんの大事なところを
http://framework.zend.com/manual/ja/zend.acl.advanced.html
> Zend_Acl はシリアライズ可能なので、ACL オブジェクトを PHP の ≫ serialize() 関数でシリアライズできます。
> シリアライズした結果を、 ファイルやデータベースあるいはキャッシュなどのお好みの場所に保存できます。
とかたった数行で流してくれやがっています。
ていうかシリアライズした結果をDBに保存とかそんな馬鹿な設計を勧めないでください。
なお、resourcetableテーブルのカラムがリソース、レコードがひとつのロールとなります。
あとは認可制御したいプログラムの冒頭に$acl->isAllowed($user->getRole(), 'hogeResource' )とか書けば簡単に認可制御することが可能です。
いや、あれだ、('SELECT sampleResource FROM resourceTable WHERE role = ? ', $user->getRole())とか書いたほうがわざわざ権限テーブル全取得してZend_Aclに突っ込んだりするとかいう無駄がないぶんよっぽど早いとかそういうことは秘密だ。
> とても簡単に認可制御が行えました。
> あとは各ユーザ毎にmemberかadminかを示すフラグを設定すれば完成、めでたしめでたし。
なわけがない。
今時権限をソースにベタ書きとかねーよ普通管理画面から制御してDB保存だろ。
とまあそこらへんの大事なところを
http://framework.zend.com/manual/ja/zend.acl.advanced.html
> Zend_Acl はシリアライズ可能なので、ACL オブジェクトを PHP の ≫ serialize() 関数でシリアライズできます。
> シリアライズした結果を、 ファイルやデータベースあるいはキャッシュなどのお好みの場所に保存できます。
とかたった数行で流してくれやがっています。
ていうかシリアライズした結果をDBに保存とかそんな馬鹿な設計を勧めないでください。
CREATE TABLE `resourcetable` ( `roleName` varchar(255) NOT NULL, `sampleResource` tinyint(1) NOT NULL, `hogeResource` tinyint(1) NOT NULL, `fugaResource` tinyint(1) NOT NULL, PRIMARY KEY (`roleName`) )
<?php
//Zend_Acl
require_once('Zend/Acl.php');
$acl = new Zend_Acl();
//DB
require_once('Zend/Db.php');
$db=Zend_Db::factory('Pdo_Mysql', array( 'host'=>'localhost', 'username'=>'testuser', 'password'=>'testpass', 'dbname'=>'testdb' ));
//リソーステーブルを取得
$select = $db->select()->from('resourcetable');
$stmt = $select->query();
$result = $stmt->fetchAll();
//リソースをZend_Aclに登録
foreach($result[0] as $key=>$val){
if($key==='roleName'){continue;}
$acl->add(new Zend_Acl_Resource($key));
}
//ロールと権限をZend_Aclに登録
foreach($result as $record){
foreach($record as $resource=>$column){
if($resource==='roleName'){
$role = $column;
$acl->addRole(new Zend_Acl_Role($role));
continue;
}
switch($column){
case 1:
$acl->allow($role, $resource);
break;
default:
$acl->deny( $role, $resource);
break;
}
}
}
//確認
var_dump($acl->isAllowed('role1', 'sampleResource'));
任意にロールやリソースの種類を増やしても問題無く動作するようになりました。なお、resourcetableテーブルのカラムがリソース、レコードがひとつのロールとなります。
あとは認可制御したいプログラムの冒頭に$acl->isAllowed($user->getRole(), 'hogeResource' )とか書けば簡単に認可制御することが可能です。
いや、あれだ、('SELECT sampleResource FROM resourceTable WHERE role = ? ', $user->getRole())とか書いたほうがわざわざ権限テーブル全取得してZend_Aclに突っ込んだりするとかいう無駄がないぶんよっぽど早いとかそういうことは秘密だ。
Zend_Aclは認可を扱うクラスです。
Zend_Authの認証とどう違うの?というと、認証はログインできるかどうかの判断、すなわち本人確認を行います。
それに対して認可は認証した後のアクセス権限を意味します。
ブログシステムがあったとして、Zend_Authは管理画面にログインできるかどうかをチェックします。
Zend_Aclは、私は自分のブログを編集できますが他人のブログは編集できず、管理者は誰のブログでも編集できる、といったログイン後に必要となる権限を操作します。
そういった立場上Zend_Authと同時に使用するべきなのですが、まあ今回はとりあえず単品で使ってみます。
リファレンスガイドではのっけからロールの多重継承とか複数のロールに属するとか面倒な解説がなされていますが、いきなりそんなことやったって理解できるわけなかろうがと。
ということでまずは単純な例を作ってみます。
上記では、memberロールを持つユーザはサーバ管理者にしか許されない操作は行えませんが、サーバ管理者はブログ管理者が行える操作は全て行える、という設定になります。
とても簡単に認可制御が行えました。
あとは各ユーザ毎にmemberかadminかを示すフラグを設定すれば完成、めでたしめでたし。
Zend_Authの認証とどう違うの?というと、認証はログインできるかどうかの判断、すなわち本人確認を行います。
それに対して認可は認証した後のアクセス権限を意味します。
ブログシステムがあったとして、Zend_Authは管理画面にログインできるかどうかをチェックします。
Zend_Aclは、私は自分のブログを編集できますが他人のブログは編集できず、管理者は誰のブログでも編集できる、といったログイン後に必要となる権限を操作します。
そういった立場上Zend_Authと同時に使用するべきなのですが、まあ今回はとりあえず単品で使ってみます。
リファレンスガイドではのっけからロールの多重継承とか複数のロールに属するとか面倒な解説がなされていますが、いきなりそんなことやったって理解できるわけなかろうがと。
ということでまずは単純な例を作ってみます。
<?php
//Zend_Acl
require_once('Zend/Acl.php');
$acl = new Zend_Acl();
//ロールを作成
$acl->addRole(new Zend_Acl_Role('admin'))
->addRole(new Zend_Acl_Role('member'));
//リソースを作成
$resourceAdmin = new Zend_Acl_Resource('adminResource');
$resourceMember = new Zend_Acl_Resource('memberResource');
//Aclにリソースを付与
$acl->add($resourceAdmin);
$acl->add($resourceMember);
//ロールに、リソースに対する権限を付与
$acl->allow('admin', 'adminResource');
$acl->deny ('member','adminResource');
$acl->allow('admin', 'memberResource');
$acl->allow('member','memberResource');
//権限があるかチェック
var_dump($acl->isAllowed('admin', 'memberResource')); //true
var_dump($acl->isAllowed('member','adminResource')); //false
memberはブログ管理者つまり私、adminはサーバ管理者つまり忍者ブログの中の人を想定しています。上記では、memberロールを持つユーザはサーバ管理者にしか許されない操作は行えませんが、サーバ管理者はブログ管理者が行える操作は全て行える、という設定になります。
とても簡単に認可制御が行えました。
あとは各ユーザ毎にmemberかadminかを示すフラグを設定すれば完成、めでたしめでたし。
Zend_Dom_Queryは、HTMLファイルにDOM形式でアクセスできるようになるよ、というクラスです。
これまでもSimpleXMLとかTidyとか使っていましたが、これらと違う最大の特徴が、「CSSパスでもXpathでもアクセスできる」という点です。
セレクタとしてはjQueryレベルの簡単最強な使い勝手を誇ります。
例としてこちらのページをパースしてみます。
http://framework.zend.com/manual/ja/manual.html
結果、Zend_AclからZendX_JQueryまでをリストアップすることができます。
なお、CSSパス「body div[id="page"] div#main-holder div.content [class*="content"] div #manual div.info ul>li[3] ul>li」をXPathに変換した結果はこんなことになります。
//body//div[@id='page']//div[@id='main-holder']//div[contains(concat(' ', normalize-space(@class), ' '), ' content ')]//*[contains(@class, 'content')]//div//*[@id='manual']//div[contains(concat(' ', normalize-space(@class), ' '), ' info ')]//ul/li[3]//ul/li|//body//div[@id='page']//div[@id='main-holder']//div[contains(concat(' ', normalize-space(@class), ' '), ' content ')][contains(@class, 'content')]//div//*[@id='manual']//div[contains(concat(' ', normalize-space(@class), ' '), ' info ')]//ul/li[3]//ul/li
なんだこりゃ。
道理で誰もXPath使わないわけだ。
これまでもSimpleXMLとかTidyとか使っていましたが、これらと違う最大の特徴が、「CSSパスでもXpathでもアクセスできる」という点です。
セレクタとしてはjQueryレベルの簡単最強な使い勝手を誇ります。
例としてこちらのページをパースしてみます。
http://framework.zend.com/manual/ja/manual.html
<?php
//クラス
require_once('Zend/Dom/Query.php');
$domQuery = new Zend_Dom_Query();
//パース
$text = file_get_contents('http://framework.zend.com/manual/ja/manual.html');
$domQuery->setDocument($text, 'UTF-8');
//CSSパスを指定
$domResult = $domQuery->query('body div[id="page"] div#main-holder div.content [class*="content"] div #manual div.info ul>li[3] ul>li');
//中身を順に処理
foreach($domResult as $key=>$domElement){
//DOMElementが入ってくる
print($domElement->nodeValue . " : " . $domElement->firstChild->getAttribute('href') . '<br />');
}
//CSSパス→XPathに変換できる
print($domResult->getXpathQuery());
この長いCSSパス指定で、「Zend Framework リファレンス」の各Zend Frameworkのリストを指定しています。結果、Zend_AclからZendX_JQueryまでをリストアップすることができます。
なお、CSSパス「body div[id="page"] div#main-holder div.content [class*="content"] div #manual div.info ul>li[3] ul>li」をXPathに変換した結果はこんなことになります。
//body//div[@id='page']//div[@id='main-holder']//div[contains(concat(' ', normalize-space(@class), ' '), ' content ')]//*[contains(@class, 'content')]//div//*[@id='manual']//div[contains(concat(' ', normalize-space(@class), ' '), ' info ')]//ul/li[3]//ul/li|//body//div[@id='page']//div[@id='main-holder']//div[contains(concat(' ', normalize-space(@class), ' '), ' content ')][contains(@class, 'content')]//div//*[@id='manual']//div[contains(concat(' ', normalize-space(@class), ' '), ' info ')]//ul/li[3]//ul/li
なんだこりゃ。
道理で誰もXPath使わないわけだ。
実は今まで全く知らなかったのだが、dataスキームというものが存在するそうな。
どんなものかというとこれ→
こちら、画像を別途読んでいるわけではなく、以下のテキストをHTMLの中に書いてあります。
<img src="data:image/png;base64,R0lGODlhEQATAKIHAP/MzP7pwP7amO4AAGYpBf8AAP///////yH5BAEAAAcALAAAAAARABMAAANeeHrWbiuqRmp1khkyhrvQQnWPURCNyJFPcaYj67gvgxqDO9P3ttPAl68RLAAMAGEvB5z1Kp1o5wjSbCzYXuQjCAQEt8yE0P2GxZuyOZRJl8FsCUVQRokn1+rdilkkAAA7" />
これはなんなのかというと、単純に画像ファイルをBASE64エンコードしてるだけです。
いやー、こんな便利なものがあったんですね。
もちろんあまり大きなファイルには使えませんが、ちょっとした装飾とかには十分役立ちそうです。
しかし改行されないせいでえらいことになってるな。
word-wrap: break-wordとか効かないみたいだしどうすれバインダー
どんなものかというとこれ→
こちら、画像を別途読んでいるわけではなく、以下のテキストをHTMLの中に書いてあります。
<img src="data:image/png;base64,R0lGODlhEQATAKIHAP/MzP7pwP7amO4AAGYpBf8AAP///////yH5BAEAAAcALAAAAAARABMAAANeeHrWbiuqRmp1khkyhrvQQnWPURCNyJFPcaYj67gvgxqDO9P3ttPAl68RLAAMAGEvB5z1Kp1o5wjSbCzYXuQjCAQEt8yE0P2GxZuyOZRJl8FsCUVQRokn1+rdilkkAAA7" />
これはなんなのかというと、単純に画像ファイルをBASE64エンコードしてるだけです。
いやー、こんな便利なものがあったんですね。
もちろんあまり大きなファイルには使えませんが、ちょっとした装飾とかには十分役立ちそうです。
しかし改行されないせいでえらいことになってるな。
word-wrap: break-wordとか効かないみたいだしどうすれバインダー
前回「最初から用意しろよ」とか言いましたが、実はZend_Applicationで実現できます。
クイックスタートを利用して前回までのZend_Controllerを書き換えてみます。
クイックスタートは2種類ありますが、Zend_Toolを利用する方法は新規プロジェクト用なので後者を使用します。
まずBootstrapを作成。
application/Bootstrap.php
html/index.php
順番に見ていきましょう。
Bootstrap.phpは、毎回必ず呼ばれるクラスになります。
前回作ったBaseController.phpのようなものですが、予めシステムに組み込まれているぶんrequire_onceを書いたりextendsしたりしなくてすむようになります。
これ一体何処からどうやって呼ばれているのかさっぱりわからない。
application.iniは設定ファイルです。
書き方はZend_Configの記事を参照してください。
'production'、'staging'、'testing'、'development'の4環境を設置しています。
まあ大抵は'production''development'の二つしか使いませんが。
public/index.phpはインデックスです。
BaseControllerではrun()するだけでしたが、Zend_Applicationは幾つか事前準備が必要です。
定数APPLICATION_ENVはapplication.iniに対するどの環境かを設定しています。
定数APPLICATION_ENVの中身は環境変数APPLICATION_ENVから取得しており、その環境変数APPLICATION_ENVはhtml/.htaccessで'development'と指定しているので開発環境となります。
従って本番環境ではhtml/.htaccessの中身が'production'となります。
これでとりあえず準備ができたのでブラウザで実行。
Warning: require_once(APP_DIRcontrollers/BaseController.php) [function.require-once]: failed to open stream: No such file or directory in C:\hoge\fuga\application\controllers\IndexController.php on line 3
お?
クイックスタートを利用して前回までのZend_Controllerを書き換えてみます。
クイックスタートは2種類ありますが、Zend_Toolを利用する方法は新規プロジェクト用なので後者を使用します。
まずBootstrapを作成。
application/Bootstrap.php
<?php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap{}
コンフィグファイルを作成。
[production] phpSettings.display_startup_errors = 0 phpSettings.display_errors = 0 includePaths.library = APPLICATION_PATH "/../library" bootstrap.path = APPLICATION_PATH "/Bootstrap.php" bootstrap.class = "Bootstrap" resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers" [staging : production] [testing : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 [development : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1インデックスを書き換え。
html/index.php
<?php
// アプリケーション・ディレクトリへのパスを定義します
defined('APPLICATION_PATH')
|| define('APPLICATION_PATH',
realpath(dirname(__FILE__) . '/../application'));
// アプリケーション環境
defined('APPLICATION_ENV')
|| define('APPLICATION_ENV',
(getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV')
: 'production'));
// 必要があればinclude_pathを追加
set_include_path(implode(PATH_SEPARATOR, array(
dirname(dirname(__FILE__)) . '/library',
get_include_path(),
)));
// Zend_Applicationを実行
require_once 'Zend/Application.php';
$application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_PATH . '/configs/application.ini'
);
$application->bootstrap()
->run();
html/.htaccessに一行追加。
SetEnv APPLICATION_ENV developmentほとんどコピペしただけです。
順番に見ていきましょう。
Bootstrap.phpは、毎回必ず呼ばれるクラスになります。
前回作ったBaseController.phpのようなものですが、予めシステムに組み込まれているぶんrequire_onceを書いたりextendsしたりしなくてすむようになります。
これ一体何処からどうやって呼ばれているのかさっぱりわからない。
application.iniは設定ファイルです。
書き方はZend_Configの記事を参照してください。
'production'、'staging'、'testing'、'development'の4環境を設置しています。
まあ大抵は'production''development'の二つしか使いませんが。
public/index.phpはインデックスです。
BaseControllerではrun()するだけでしたが、Zend_Applicationは幾つか事前準備が必要です。
定数APPLICATION_ENVはapplication.iniに対するどの環境かを設定しています。
定数APPLICATION_ENVの中身は環境変数APPLICATION_ENVから取得しており、その環境変数APPLICATION_ENVはhtml/.htaccessで'development'と指定しているので開発環境となります。
従って本番環境ではhtml/.htaccessの中身が'production'となります。
これでとりあえず準備ができたのでブラウザで実行。
Warning: require_once(APP_DIRcontrollers/BaseController.php) [function.require-once]: failed to open stream: No such file or directory in C:\hoge\fuga\application\controllers\IndexController.php on line 3
お?