ログインチェックをプラグインにて行う2

今回の認証クラスでは、DBを使って認証処理を行っています。
サンプルソースは以下になります。

説明を日本語で書くと、余計にわかりづらいくなってしまいましのたで、
コードとコメントを見て理解していただけたらと思います。

Zend_Loader::loadClass('Zend_DB_Table_Abstract');
Zend_Loader::loadClass('Zend_Auth_Adapter_DbTable');
Zend_Loader::loadClass('Zend_Auth_Storage_Session');

class authModel
{
 private $_authStorage;

 public function __construct()
 {
  $config = new Zend_Config_Ini('config/system.ini', 'default');
  // ログイン情報を格納するセッションを指定
  $this->_authStorage
   = new Zend_Auth_Storage_Session($config->session);
 }

 public function login($post)
 {
  $db = Zend_Db_Table_Abstract::getDefaultAdapter();
  $authAdapter = new Zend_Auth_Adapter_DbTable($db);
  $authAdapter->setTableName('Accounts')
         ->setIdentityColumn('login_id')   // IDカラムを指定
         ->setCredentialColumn('password'); // 認証用カラムを指定

  // 上記で指定したカラムに、認証したい値を設定
  $authAdapter->setIdentity($post['login_id']) 
        ->setCredential($post['password']); 

  // 認証実行
  $result = $authAdapter->authenticate();

  if ($result->isValid()){
   // 認証OKとなったレコードから、login_idとstatusを取りだす
   $row = $authAdapter->getResultRowObject(array(login_id, status));
   // ログイン情報格納用セッションに取得した情報を設定する
   $this->_authStorage->write($row);
  }

  return $result;
 }

 public function logout()
 {
  $this->_authStorage->clear();
 }

 public function isAuth()
 {
  return (isset($this->get()->login_id) && isset($this->get()->status));
 }

 public function get()
 {
  return $this->_authStorage->read();
 }
}

ログイン情報を設定するセッションに、デフォルト(Zend_Auth)を利用したり、
セッションに設定する情報が、認証に利用したID(上記だとlogin_id)だけでいい場合は、
より簡潔に記述することができます。

デフォルトのセッションを利用する場合は、コンストラクタ内の処理は必要ありません。
セッションに設定する情報が、認証に利用したIDのみでいい場合は、
認証処理を実行してOKだった場合に、自動的に設定されていますので、
わざわざ設定する処理を記述する必要はありません。

ログインチェックをプラグインにて行う

今回は、会員サイト等でよく使うログイン状態チェックを、
プラグインで実現するためのクラスを作りました。

ログイン状態チェックは、ログイン画面以外では必ず必要で、
しかも通常の処理(データ登録、参照等)とは関連しないので、
プラグインにするのに、うってつけだと思ったのです。
サンプルソースは以下になります。

Zend_Loader::loadClass('Zend_Controller_Plugin_Abstract');
Zend_Loader::loadClass('Zend_Session');
Zend_Loader::loadClass('Zend_Controller_Action_Helper_Redirector');

require_once('models/authModel.php');

class Zend_Controller_Plugin_Auth extends Zend_Controller_Plugin_Abstract
{
 public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
 {
   Zend_Session::start();

   $auth = new authModel();

   $controller = $request->getControllerName();
   $action   = $request->getActionName();

   if (($controller == 'index') && ($action == 'index'))
   {
    // ログイン画面の場合は、認証情報削除
    $auth->logout();
   }else{
    if (!$auth->isAuth()){
     // 認証エラーの場合は、認証情報を削除し、ログイン画面へ
     $auth->logout();
     $helper = new Zend_Controller_Action_Helper_Redirector();
     $helper->gotoUrl('/');
    }
   }
 }
}

この例では、http://test.com/にログイン画面があることを想定しています。
そのため、コントローラー名、アクション名がindexの場合は、
一旦、保持しているログイン状態を破棄しています。

今回は、DBデータに問い合わせる方法で、ログイン認証を行い、
ログインOKの場合は、ログイン情報(login_id等)を
セッションに保持するというサンプルソースを作りました。
そのため、このプラグインの中で、Zend_Session::start()も実行しています。

authModelは、認証系の処理を集めたモデルクラスです。
これについては、また後日サンプルソースを公開します。

DBアダプタ作成処理をプラグインにて行う

Dbハンドラの生成をmodelクラスで行っていたのですが、
複数のモデルクラスを使う処理の場合もあるので、どこか1か所に
生成処理をまとめられないかなと思って、たどりついたのがプラグインでした。

ソースは、以下

Zend_Loader::loadClass('Zend_Controller_Plugin_Abstract');

Zend_Loader::loadClass('Zend_Db');
Zend_Loader::loadClass('Zend_Db_Table_Abstract');
Zend_Loader::loadClass('Zend_Config_Ini');

class Zend_Controller_Plugin_DbAdapter extends Zend_Controller_Plugin_Abstract
{
 public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
 {
  // DBアダプタ生成
  $config = new Zend_Config_Ini('config/system.ini', 'default');
  $db = Zend_Db::factory($config->database->adapter,
              $config->database->params->toArray());
  $db->setFetchMode(Zend_Db::FETCH_ASSOC);

  // Db Tableクラス利用DBアダプタ設定
  Zend_Db_Table_Abstract::setDefaultAdapter($db);
 }
}

こうやっておけば、どこのクラスでも

Zend_Loader::loadClass('Zend_Db_Table_Abstract');

$db = Zend_Db_Table_Abstract::getDefaultAdapter();

とすると、Dbアダプターが取得できます。
Dbアダプターを作成するモデルクラスを基底クラスとして、作っていたのですが、
こちらの方が全然いいなと思いました。

プラグインを有効にするには、フロントコントローラに登録する必要があります。
index.phpで以下の行が必要です。

require_once('Custom/Controller/Plugin/DbAdapter.php');

$front = Zend_Controller_Front::getInstance();
$front->registorPlugin(new Zend_Controller_Plugin_DbAdapter());

プラグインで、ログイン状態判定もできそうだなと思っています。

Zend_ViewでSmartyをラップする6

今回は、やりたいことの2つ目、3つ目に入っていた
2.プログラムが必要ない場合は、Actionコントローラーを書きたくない
 (Actionメソッドがなくても、テンプレートが表示されるようにしたい)
3.テンプレートもないURLが指定されたら、トップページなど指定のURLに遷移させたい
を実現する方法です。

Zendは、初期状態で、ErrorHandlerプラグインというプログラムが有効になっています。
これは、プログラム実行中に、エラーが発生した場合、
ErrorActionコントローラーのerrorActionというメソッドをよびだしてくれます。

そこで、コントローラークラスが見つからなかった場合、
アクションメソッドが見つからなかった場合の判別が可能です。
私が作った、ErrorActionクラスから必要な部分を抜き出したのが以下です。

class ErrorController extends Zend_Controller_Action
{
 public function errorAction()
 {
  $errors = $this->_getParam('error_handler');

  switch ($errors->type)
  {
   case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
    if ($this->_getParam('action') == 'index')
    {
     $view = $this->getHelper('viewRenderer');
     $view->setNoController()
         ->setScriptAction($this->_getParam('controller'));
    }

   case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
    if (!isset($view))
    {
     $view = $this->getHelper('viewRenderer');
     $this->getRequest()->setControllerName($this->_getParam('controller'));
     $this->getRequest()->setActionName($this->_getParam('action'));
    }

    $template = $view->getViewBasePathSpec() . $view->getViewScript();
    if (!is_readable($template)) $this->_redirect('/');
    break;

   default:
    break;
  }
 }
}

Zend_ViewでSmartyをラップする5

前回で、Smartyの利用と、ViewRendererヘルパーを利用して、
Smartyを利用した画面の自動描画が有効になる方法を紹介しました。

今回は、やりたいことの1つ目にあげていた
1.テンプレートの配置をURLからイメージしやすいようにしたい
ということを実現する方法です。

この方法を実行するのは、各コントローラーのindexActionのみです。
そこで、このどちらかを記述すればOKです。

$this->getHeler('viewRenderer')
   ->setNoController()
   ->setScriptAction($this->getRequest()->getControllerName());

または

$this->render($this->getRequest()->getControllerName(), null, true);

これはどちらも同じ意味になります。

こうすることで、
http://test.com/aaa/ というURLでアクセスされた場合、
templates/aaa/index.htmlではなく、templates/aaa.htmlを読み込んでくれます。

Zend_ViewでSmartyをラップする4

前回の記事で書いたことを試してみました。
私がsmartyとZendを合わせて使う上で、実現したいのは以下

1.テンプレートの配置をURLからイメージしやすいようにしたい
 http://test.com/aaa/ の場合は、templates/aaa.html
 http://test.com/aaa/bbb/の場合は、templates/aaa/bbb.html

2.プログラムが必要ない場合は、Actionコントローラーを書きたくない
 (Actionメソッドがなくても、テンプレートが表示されるようにしたい)

3.テンプレートもないURLが指定されたら、トップページなど指定のURLに遷移させたい

1.は、renderのnoControllerがtrueのときのテンプレート作成ルールを、
コントローラー名+指定拡張子とすることで解決。
ただし、render呼び出しの際に、
$this->render(コントローラー名, null, true)
とする必要がありますが、この部分を簡略化できないかが今後の課題です。

2.はまだ試せてません。しかし、前々回の記事の方法が応用できそうです。
3.も2.と同様に前々回の記事の方法で実現できそうです。

しかし、前回と同様の方法だと、Zend_Controller_Actionのサブクラスを基底クラスとして
作成することになるので、これはできれば避けたいところです。
会員サイトとかなら、基底クラス作るのはいいのですが、Smarty使いたいだけで、
基底クラスを作るとなると、ログインセッション管理をやろうと思ったときに、
テンプレートエンジンに関わるファイルを修正することになりかねません。

index.phpにつぎたした部分は以下です。

require_once('Zend/Controller/Action/Helper/ViewRenderer.php');
require_once('Custom/View/Smarty.php');

$view = new Zend_View_Smarty(null, $smartyConfig);

$viewHelper = new Zend_Controller_Action_Helper_ViewRenderer($view);
$viewHelper->setViewBasePathSpec($smartyConfig['template_dir'])
      ->setViewSuffix('html');
Zend_Controller_Action_HelperBroker::addHelper($viewHelper);

$smartyConfigは、Smartyの設定値の配列です。
Custom/View/Smarty.phpは、Zend_View_Interfaceに沿って、
Smartyが使えるようにしたViewクラスです。
中身は、ここの最後の方にのっているSmartyラッパークラスと同様です。
ただ、function getScriptPaths()に誤りがあります。

return $this->_smarty->template_dir;
  ↓
return array($this->_smarty->template_dir);

とする必要があります。
getScriptPaths()なので、パスが配列で複数取得されることが想定されて、
Zend_Controller_Action_Helper_ViewRendererが作成されているためです。

これでより少ないソースで、smartyと連携できるようになりました。
より手間を少なくする方法がないか、もうちょっと試してみる予定です。
ViewRendererヘルパーと、ErrorHanderプラグインあたりを組み合わせると、
なんとかなりそうな気がドキュメントを読んでいるとするのですが、どうなんでしょう。
あたりがついたら、試してみます。

Zend_ViewでSmartyをラップする3

昨日、ActionコントローラのinitViewをオーバーライドが簡単って
書いたばかりですが、こんなドキュメントを見つけました。
別件でヘルパーかプラングインを作ってみようとしてたときです。

ここの【例 7.10. パスの指定方法の変更】の部分です。

テンプレートエンジンをSmartyにするだけなら、これがより簡単です。
見つけたとき、軽く衝撃でした。

Smartyを使いつつ、私がやりたいこと(現在の方法で実現していること)が
この方法を使ったときに実現できて、かつ簡単なら乗り換えようかと。

結果は、またこのブログに書きたいと思います。

Zend_ViewでSmartyをラップする2

ドキュメント読んだり、ソースおったりしているうちに、
Zend_Controller_ActionクラスのinitViewをオーバーライドして、
Front Controllerにて、noViewRendererをtrueに設定して、
各アクションの最後に、$this->render()で、画面表示するのが、
一番楽なんじゃないかという結論に達しました。

重要なのは、initViewの中で、$this->viewに、Zend_View_Interfaceに
沿って作成したSmartyクラスを設定すること。
これがあって初めて、既存の$this->render()などが利用できます。

なぜこの方法にいきついたかの理由は、デフォルトのテンプレートエンジンである
Zend_Viewを利用する際には、ほとんど利用されていないメソッドだからです。
Zend_Viewを利用する場合、Zend_Controller_Action_Helper_ViewRendererという
ヘルパークラスが利用されています。

Frontコントローラーで、noViewRendererが設定されるなどして、
自動でレンダリング(画面描画)する機能がOFFにされていない限り、
上記のヘルパーを介して、Zend_Viewクラスを利用しています。
Zend_Controller_Actionの中の以下のメソッドでは、
上記ヘルパー内の同名のメソッドを呼び出します。

  ・render
  ・renderScript
  ・getViewScript

initViewに関しては、Actionクラスの生成時に同時に生成される
上記ヘルパークラスを、リターンするという内容になっています。

noViewRendererがONになっているときに、これらのメソッドを自分でプログラムに記述し、
Zend_Viewクラスを生成して、画面描画することもできますが、Zend_Viewを使うなら、
自動で行ってくれる処理を、なぜ手動にするか理由が見当たりません。
(自動描画は、必要な時だけOFFにすることもできるのです)

なので、別のテンプレートエンジンを利用するときに、オーバーライドするなら、ここかなと思ったのです。
ヘルパーを作るっていう手段もありますが、オーバーライドした方が簡単そうだったからです。
そのうち、勉強をかねて、ヘルパーを作る方法を試すかもしれません。

Smartyを利用することで、テンプレートの拡張子もデフォルトの”.pthml”でないものを
使いたい方もいるでしょう。その場合は、Actionクラス内のpublic変数である $viewSuffixを
書き換えることで変更可能です。

initViewを再定義した場合、Actionクラスのinit()などで呼び出さないと、
renderするタイミングまでインスタンスが生成されないため、テンプレートに
値を設定することができませんので、お忘れなく。

メール送信の注意点(Zend_Mail)

以前の記事で、Zend_Mailを使って、メールサーバにSMTP認証し、
メールを送信する方法を書きましたが、そこの記述で問題がある部分がありました。

Zend_Mailを生成する際に、パラメタとして ‘UTF-8’を設定しています。
これでも、正常に送信できることはできるんですが、件名が日本語で、
かつ、ある一定以上の長さになった場合に、途中で切れてしまう現象が発生しました。

Zend_Mailのソースを追おうかと思いましたが、面倒なのでやめました。

件名、本文を、mb_convert_encodingで、’ISO-2022-JP’にエンコードし、
Zend_Mailを生成する際のパラメタも同じく ‘ISO-2022-JP’にすることで、
件名が途中で切れる問題は回避できました。

結局、エンコードが必要なのかって思ってしまいますが、
大した手間ではないし、いいかなと。

=====

解決と思ったら、今度は送信者名の末尾に文字化けのような文字がついてしまいました。
Zend_Mailのパラメタを’UTF-8’にしていなかったときは、化けていなかたのに。
送信者名は必ずつけたいというわけではないので、
削除してISO-2022-JPで、今回はいくことにします。

解決策をご存じの方は、ぜひコメントください。助かります。

Zend_ViewでSmartyをラップする

これを知るまで、Contorollerで、Smartyクラスを生成して使っていたけど、
Zend_Viewのドキュメントに記載されていた、Smartyをラップする
Zend_Viewクラス(ex Zend_View_Smarty)を作成して、これを利用することで、
Smartyの便利さはそのままに、テンプレートへの値の設定がさらに楽に!!!

簡単に言うと、$key っていう変数に、$valueを設定しようとした場合、
$this->_smarty->assign($key, $value);
と書いていたのが、
$this->_view->$key = $value;
ってかけるようになるんです。
assignの記述で、よくスペルミスしていた私には、重宝する機能です。

smartyの設定(delimiterとか)も、連想配列にして、Zend_View_Smartyクラスの
生成時に、第2パラメタとして渡したら有効になります。
テンプレートへのパスも、setScriptPathではなく、生成時に設定した方が楽です。

すでに、Zendで、Smartyを使っている方でも、変更量は少なくてすみますので、
試す価値有りと思った方は、ぜひおためしくださーい。

この方法を採用していると、テンプレートエンジンを変更しようとおもった際の
ソースの改修もラッパークラスを変更するだけですみます。
テンプレートは大幅な改修になることは、避けられませんが。

DB操作にしても、変数に代入するイメージで、設定できるっていう
Zendの特徴を生かしたSmartyとの連携方法だと思います。

これをやったら、既存のZend_Viewのように、Zend Frameworkが、
Zend_View_Smartyを生成して、テンプレート特定して、画面表示までを
やってくれないかなーという欲がでてきました。
Zend_Controller_Actionを継承して、initViewあたりを再定義したら、
できそうだけど、他にもっと楽な方法がないか探して見ます。

存在しない、URLが指定された場合、action名の方は任意の処理を
入れれる方法がわかっているのですが、controller名が存在しない場合は、
エラーになるだけなので、ここをどうにかしたいところです。

Front Controllerを継承したクラスを作るのはちょっとおっくうなので、他の方法で。