Practical symfony

13日目: ユーザー

You are currently browsing
the website for symfony 1

Visit the Symfony2 website


About

You are currently reading "Practical symfony" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.

Jobeet Links

Master symfony

Be trained by SensioLabs experts (2 to 6 day sessions -- French or English).
trainings.sensiolabs.com

Books on symfony

Learn more about symfony with the official guides.
books.sensiolabs.com

L'audit Qualité par SensioLabs

200 points de contrôle de votre applicatif web.
audit.sensiolabs.com

Chapter Content

ユーザーflash

ユーザー属性

getAttribute()setAttribute()

myUserクラス

sfParameterHolder

アプリケーションのセキュリティ

認証

認証

プラグイン

バックエンドのセキュリティ

ユーザーのテスト

また明日

symfony training
Be trained by symfony experts
Feb 21: Köln (Getting Started with Symfony2 - English)
Feb 27: Köln (Mastering Symfony2 - English)
Mar 05: Köln (Web Development with Symfony2 - Deutsch)
Mar 05: Montreal (Web Development with Symfony2 - English)
Mar 05: Montreal (Getting Started with Symfony2 - English)
and more...

Search


powered by google
You are currently browsing "Practical symfony" in Japanese for the 1.2 version - Doctrine edition - Switch to version: - Switch to ORM: - Switch to language:
Creative Commons License This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License.
This version of symfony is not maintained anymore.
If some of your projects still use this version, consider upgrading as soon as possible.
Practical symfony (Doctrine edition)
Support symfony!
Buy this book
or donate.
Buy Practical symfony (Doctrine edition) from amazon.com

昨日はたくさんの情報が詰め込まれました。 ごくわずかなPHPコードとadminジェネレーターによって、短時間でバックエンドインターフェイスを作成できます。

今日は、HTTPリクエストの間の永続データを管理する方法を理解します。 ご存じのとおり、HTTPプロトコルはステートレスです。 それぞれのリクエストはその前後のリクエストから独立していることを意味します。 現代のWebサイトはユーザーエクスペリエンスを強化するためにリクエストの間のデータを一貫させる方法が必要です。

ユーザーセッションはCookieを利用して特定できます。 symfonyにおいて、開発者はセッションを直接操作する必要はありませんがむしろアプリケーションのエンドユーザーを表すsfUserオブジェクトを使うことが必要です。

ユーザーflash

flashつきのアクションでユーザーオブジェクトをすでに見てきました。 flashはユーザーセッションに保存される短期のメッセージです。 これはすぐ次のリクエストの後で自動的に削除されます。 リダイレクトした後でユーザーにメッセージを表示する必要があるときにとても役立ちます。 jobが保存される、削除もしくは延長されるときに、ユーザーにフィードバックを表示するためにadminジェネレーターはflashをたくさん使います。

flash

flashはsfUsersetFlash()メソッドを使って設定できます:

// apps/frontend/modules/job/actions/actions.class.php
public function executeExtend(sfWebRequest $request)
{
  $request->checkCSRFProtection();
 
  $job = $this->getRoute()->getObject();
  $this->forward404Unless($job->extend());
 
  $this->getUser()->setFlash('notice', sprintf('Your job validity has been extended until %s.', date('m/d/Y', strtotime($job->getExpiresAt()))));
 
  $this->redirect($this->generateUrl('job_show_user', $job));
}

最初の引数はflashの識別子で2番目は表示するメッセージです。 望むのであればflashはなんでも定義できますが、noticeerrorはもっとも共通するものの2つです (これらはadminジェネレーターによって広範囲で使われます)。

テンプレートにflashメッセージをインクルードするのは開発者しだいですが、Jobeetに関して、これらはlayout.phpによって出力されます:

// apps/frontend/templates/layout.php
<?php if ($sf_user->hasFlash('notice')): ?>
  <div class="flash_notice"><?php echo $sf_user->getFlash('notice') ?></div>
<?php endif; ?>
 
<?php if ($sf_user->hasFlash('error')): ?>
  <div class="flash_error"><?php echo $sf_user->getFlash('error') ?></div>
<?php endif; ?>

テンプレートにおいて、ユーザーは特別な変数sf_userをとおしてアクセス可能です.

symfonyオブジェクトの中にはアクションから明示的に渡さなくても常にテンプレートにアクセスできるものがあります: >sf_requestsf_usersf_response

ユーザー属性

不幸なことに、Jobeetユーザーのストーリーにはユーザーセッションに何かを保存する要件は含まれていません。 ですので新しい要件を追加しましょう: 求人の閲覧を楽にするために、ユーザーによって閲覧される最新の3件の求人は後で求人ページに戻れるリンクつきのメニューに表示されます。

ユーザーが求人ページにアクセスするとき、表示されるjobオブジェクトをユーザーの履歴に追加してセッションに保存する必要があります:

// apps/frontend/modules/job/actions/actions.class.php
class jobActions extends sfActions
{
  public function executeShow(sfWebRequest $request)
  {
    $this->job = $this->getRoute()->getObject();
 
    // 求人履歴にすでに保存された求人を取得する
    $jobs = $this->getUser()->getAttribute('job_history', array());
 
    // 配列の始めに現在の求人を追加する
    array_unshift($jobs, $this->job->getId());
 
    // 新しい求人履歴をセッションに保存し直す
    $this->getUser()->setAttribute('job_history', $jobs);
  }
 
  // ...
}

JobeetJobオブジェクトをセッションに直接保存するのはできますが非推奨です。 リクエストの間にセッション変数がシリアライズされるからです。 セッションがロードされるとき、JobeetJobオブジェクトはデシリアライズされその間にそれらが修正もしくは削除される場合に"盗まれます"。

getAttribute()setAttribute()

識別子として、sfUser::getAttribute()メソッドはユーザーセッションから値を取得します。 逆に言えば、識別子のためにsetAttribute()メソッドはPHP変数をセッションに保存します。

getAttribute()メソッドは識別子がまだ定義されていない場合に返すオプションのデフォルト値も受け取ります。

getAttribute()メソッドが受け取るデフォルトの値は次の内容のショートカットです:

if (!$value = $this->getAttribute('job_history'))
{
  $value = array();
}

myUserクラス

関心の分離をより順守するために、コードをmyUserクラスに移動させましょう。 myUserクラスはデフォルトのsfUser基底クラスをアプリケーション固有のふるまいでオーバーライドします:

// apps/frontend/modules/job/actions/actions.class.php
class jobActions extends sfActions
{
  public function executeShow(sfWebRequest $request)
  {
    $this->job = $this->getRoute()->getObject();
 
    $this->getUser()->addJobToHistory($this->job);
  }
 
  // ...
}
 
// apps/frontend/lib/myUser.class.php
class myUser extends sfBasicSecurityUser
{
  public function addJobToHistory(JobeetJob $job)
  {
    $ids = $this->getAttribute('job_history', array());
 
    if (!in_array($job->getId(), $ids))
    {
      array_unshift($ids, $job->getId());
 
      $this->setAttribute('job_history', array_slice($ids, 0, 3));
    }
  }
}

すべての要件を考慮するようにコードも変更されました:

レイアウトにおいて、変数$sf_contentが出力される前に次のコードを追加します:

// apps/frontend/templates/layout.php
<div id="job_history">
  Recent viewed jobs:
  <ul>
    <?php foreach ($sf_user->getJobHistory() as $job): ?>
      <li>
        <?php echo link_to($job->getPosition().' - '.$job->getCompany(), 'job_show_user', $job) ?>
      </li>
    <?php endforeach; ?>
  </ul>
</div>
 
<div class="content">
  <?php echo $sf_content ?>
</div>

レイアウトは現在の求人履歴を読み取るために新しいgetJobHistory()メソッドを使います:

// apps/frontend/lib/myUser.class.php
class myUser extends sfBasicSecurityUser
{
  public function getJobHistory()
  {
    $ids = $this->getAttribute('job_history', array());
 
    if (!empty($ids))
    {
      return Doctrine::getTable('JobeetJob')
        ->createQuery('a')
        ->whereIn('a.id', $ids)
        ->execute();
    }
    else
    {
      return array();
    }
  }
 
  // ...
}

求人履歴

sfParameterHolder

求人履歴のAPIを完結させるために、履歴をリセットするメソッドを追加しましょう:

// apps/frontend/lib/myUser.class.php
class myUser extends sfBasicSecurityUser
{
  public function resetJobHistory()
  {
    $this->getAttributeHolder()->remove('job_history');
  }
 
  // ...
}

ユーザー属性はsfParameterHolderクラスのオブジェクトによって管理されます。 getAttribute()setAttribute()メソッドはgetParameterHolder()->get()getParameterHolder()->set()用のプロキシメソッドです。 remove()メソッドはsfUserのプロキシメソッドを持たないので、パラメーターホルダーオブジェクトを直接使う必要があります。

sfParameterHolderクラスはパラメーターを保存するためにsfRequestによっても使われます。

アプリケーションのセキュリティ

認証

他の多くのsymfonyの機能のように、セキュリティはYAMLファイルのsecurity.ymlで管理されます。 たとえば、デフォルトコンフィギュレーションはバックエンドアプリケーションのconfig/ディレクトリで見つかります:

# apps/backend/config/security.yml
default:
  is_secure: off

is_secureエントリーをonに切り替える場合、バックエンドアプリケーション全体でユーザーを認証することが求められます。

ログイン

YAMLファイルにおいて、booleanはtruefalse、もしくはonoffの文字列で表現されます。

Webデバッグツールバーのログを見ると、ページにアクセスしようとするたびにdefaultActionsクラスのexecuteLogin()メソッドが呼び出されることに気がつきます。

Webデバッグツールバー

認証されていないユーザーがセキュアなアクションにアクセスしようとすると、symfonyはsettings.ymlで設定されるloginアクションにリクエストを転送します:

all:
  .actions:
    login_module: default
    login_action: login

無限の再帰呼び出しを回避するためにloginアクションをセキュアにするのは不可能です。

4日目でみたように、同じ設定ファイルを複数の場所で定義できます。 これはsecurity.ymlにもあてはまります。 単独のアクションもしくはモジュール全体をセキュアにするもしくはセキュアにしないのみであれば、モジュールのconfig/ディレクトリでsecurity.ymlを作成します:

index:
  is_secure: off
 
all:
  is_secure: on

デフォルトでは、myUserクラスはsfBasicSecurityUserを継承し、sfUserは継承しません。 sfBasicSecurityUserはユーザーの認証と認可を管理するための追加メソッドを提供します。

ユーザーの認証を管理するには、isAuthenticated()setAuthenticated()メソッドを使います:

if (!$this->getUser()->isAuthenticated())
{
  $this->getUser()->setAuthenticated(true);
}

認証

ユーザーが認証されたとき、アクションへのアクセスはクレデンシャル(credential)を定義することでより制限できます。 ユーザーはページにアクセスするために要求されるクレデンシャルを持たなければなりません:

default:
  is_secure:   off
  credentials: admin

symfonyのクレデンシャルシステムはとてもシンプルで強力です。 クレデンシャルは(グループもしくはパーミッションのように)アプリケーションのセキュリティモデルを記述するために必要なものを表現できます。

ユーザークレデンシャルを管理するために、sfBasicSecurityUserはいくつかのメソッドを提供します:

// 1つもしくは複数のクレデンシャルを追加する
$user->addCredential('foo');
$user->addCredentials('foo', 'bar');
 
// ユーザーがクレデンシャルを持つかチェックする
echo $user->hasCredential('foo');                      =>   true
 
// ユーザーが両方のクレデンシャルを持つかチェックする
echo $user->hasCredential(array('foo', 'bar'));        =>   true
 
// ユーザーがクレデンシャルの1つを持つかチェックする
echo $user->hasCredential(array('foo', 'bar'), false); =>   true
 
// クレデンシャルを削除する
$user->removeCredential('foo');
echo $user->hasCredential('foo');                      =>   false
 
// すべてのクレデンシャルを削除する(ログアウト処理の際に便利)
$user->clearCredentials();
echo $user->hasCredential('bar');                      =>   false

Jobeetバックエンドに関して、プロファイルは1つ: 管理者しかないのでクレデンシャルは使いません。

プラグイン

車輪の再発明をしたくないので、1からログインアクションを開発しません。 代わりに、プラグインをインストールします。

symfonyフレークワークの大きな強みの1つはプラグインのエコシステムです。 来たる日に見ますが、プラグインを作るのはとても簡単です。 プラグインはコンフィギュレーションからモジュールとアセットまで任意のものを格納できるのでとても強力です。

今日は、バックエンドアプリケーションをセキュアにするためにsfDoctrineGuardPluginをインストールします

$ php symfony plugin:install sfDoctrineGuardPlugin

plugin:installタスクは名前でプラグインをインストールします。 すべてのプラグインはplugins/ディレクトリの下に保存されそれぞれのプラグインはプラグインの名前から名づけた独自のディレクトリを持ちます。

plugin:installタスクを動作させるにはPEARをインストールしなければなりません。

plugin:installタスクでプラグインをインストールするとき、symfonyは最新の安定版をインストールします。 プラグインの特定バージョンをインストールするには、--releaseオプションを渡します。

プラグインページ はsymfonyのバージョンでグループ化された利用可能なすべてのバージョンの一覧を表示します。

プラグインはディレクトリに内蔵され、symfony公式サイトからパッケージをダウンロードして展開することが可能で、代わりにSubversionリポジトリへのsvn:externalsプロパティを作成します。

config/ProjectConfiguration.class.phpクラスのenableAllPluginsExcept()メソッドを使わない場合、プラグインをインストールした後でかならずプラグインを有効にすることを覚えておいてください。

バックエンドのセキュリティ

それぞれのプラグインには設定方法を説明しているREADMEファイルが含まれます。

新しいプラグインの作り方を見てみましょう。 ユーザー、グループとパーミッションを管理する新しいモデルクラスを提供するので、モデルをリビルドする必要があります:

$ php symfony doctrine:build-all-reload

doctrine:build-all-reloadタスクは既存のすべてのテーブルを再生成する前にこれらを削除することを覚えておいてください。 これを避けるには、モデル、フォーム、フィルターをビルドし、data/sql/に保存されている生成SQLステートメントを実行して新しいテーブルを作成します。

新しいクラスを作るときは、常にsymfonyのキャッシュをクリアする必要があります:

$ php symfony cc

sfDoctrineGuardPluginはユーザークラスにいくつかのメソッドを追加するので、myUserの基底クラスをsfGuardSecurityUserに変更する必要があります:

// apps/backend/lib/myUser.class.php
class myUser extends sfGuardSecurityUser
{
}

sfDoctrineGuardPluginはユーザーを認証するsigninアクションをsfGuardAuthモジュールに提供します。

ログインページに使われるデフォルトのアクションを変更するためにsettings.ymlファイルを編集します:

# apps/backend/config/settings.yml
all:
  .settings:
    enabled_modules: [default, sfGuardAuth]
 
    # ...
 
  .actions:
    login_module:    sfGuardAuth
    login_action:    signin
 
    # ...

プラグインは1つのプロジェクトのすべてのアプリケーションで共有されるので、モジュールをenabled_modules設定|enabled_modules(設定)に追加することで使いたいモジュールを明示的に有効にする必要があります。

sfGuardPluginのログイン

最後のステップは管理者ユーザーを作成することです:

$ php symfony guard:create-user fabien SecretPass
$ php symfony guard:promote fabien

sfGuardPluginはコマンドラインからユーザー、グループとパーミッションを管理するタスクを提供します。 guard名前空間に所属するすべてのタスクの一覧を表示するにはlistタスクを使います:

$ php symfony list guard

ユーザーが認証されていないとき、メニューバーを隠す必要があります:

// apps/backend/templates/layout.php
<?php if ($sf_user->isAuthenticated()): ?>
  <div id="menu">
    <ul>
      <li><?php echo link_to('Jobs', '@jobeet_job_job') ?></li>
      <li><?php echo link_to('Categories', '@jobeet_category_category') ?></li>
    </ul>
  </div>
<?php endif; ?>

ユーザーが認証されたとき、メニューにログアウトリンクを追加する必要があります:

// apps/backend/templates/layout.php
<li><?php echo link_to('Logout', '@sf_guard_signout') ?></li>

sfGuardPluginによって提供されるすべてのルートの一覧を表示するには、app:routesタスクを使います。

Jobeetバックエンドにさらに磨きをかけるには、管理者ユーザーを管理する新しいモジュールを追加しましょう。 ありがたいことに、sfGuardPluginはそのようなモジュールを提供してくれます。 sfGuardAuthモジュールに関して、settings.ymlでこれを有効にする必要があります:

// apps/backend/config/settings.yml
all:
  .settings:
    enabled_modules: [default, sfGuardAuth, sfGuardUser]

メニューにリンクを追加します:

// apps/backend/templates/layout.php
<li><?php echo link_to('Users', '@sf_guard_user') ?></li>

バックエンドのメニュー

やりました!

ユーザーのテスト

ユーザーのテストの話をしていないので今日のチュートリアルは終わっていません。 symfonyブラウザーはCookieをシミュレートするので、組み込みのsfTesterUserテスターによって使われるユーザーのふるまいをテストするのはとても簡単です。

今日追加したメニュー機能用の機能テストを更新しましょう。 jobモジュールの機能テストの末尾に次のコードを追加します:

// test/functional/frontend/jobActionsTest.php
$browser->
  info('4 - User job history')->
 
  loadData()->
  restart()->
 
  info('  4.1 - When the user access a job, it is added to its history')->
  get('/')->
  click('Web Developer', array(), array('position' => 1))->
  get('/')->
  with('user')->begin()->
    isAttribute('job_history', array($browser->getMostRecentProgrammingJob()->getId()))->
  end()->
 
  info('  4.2 - A job is not added twice in the history')->
  click('Web Developer', array(), array('position' => 1))->
  get('/')->
  with('user')->begin()->
    isAttribute('job_history', array($browser->getMostRecentProgrammingJob()->getId()))->
  end()
;

テスト作業を楽にするには、フィクスチャデータをリロードしてクリーンなセッションで始めるためにブラウザーを再起動します。

isAttribute()メソッドは渡されたユーザー属性をチェックします。

sfTesterUserテスターはユーザーの認証と認可をテストするためにisAuthenticated()hasCredential()メソッドも提供します。

また明日

symfonyのユーザークラスはPHPセッションの管理を抽象化するためのよい手段です。 symfonyの偉大なプラグインシステムとsfGuardPluginプラグインを結びつけることで短時間でJobeetバックエンドをセキュアにすることができました。 またプラグインによって提供されたモジュールのおかげで、自由に管理者ユーザーを管理できるクリーンなインターフェイスも追加しました。

14日目: 休日 »
« 12日目: Adminジェネレーター

Questions & Feedback

If you find a typo or an error, please register and open a ticket.

If you need support or have a technical question, please post to the official user mailing-list.