読者です 読者をやめる 読者になる 読者になる

Symfony Best Practice - 実践編 Chapter3-

hanahirodev.hatenablog.com

せっかくBest Practiceを翻訳したので、手を動かしながら中を見ていきます。 Chapter2まではプロジェクトの初期設定なので、割愛します。

いじった結果は随時GitHubにあげていきます。


DB接続設定

デフォルトではSQLiteを使うように設定されていますが、実際のお仕事ではMySQLを使うことが多いと思いますので、MySQLに切り替えます。

(積読消化。正月休みにがっつり読む予定。)

実践ハイパフォーマンスMySQL 第3版

実践ハイパフォーマンスMySQL 第3版

SQLアンチパターン

SQLアンチパターン

閑話休題。修正が必要なファイルは以下の2箇所。

# app/config/parameters.yml
# 削除
database_url: 'sqlite:///%kernel.root_dir%/data/blog.sqlite'
# 追加
database_host:     127.0.0.1
database_port:     null
database_name:     symfony
database_user:     root
database_password: null
# app/config/config.yml
# Doctrine Configuration (used to access databases and manipulate their information)
doctrine:
    dbal:
        # if you don't want to use SQLite, comment the two following lines
        # driver: "pdo_sqlite"
        # path: "%kernel.root_dir%/data/blog.sqlite"
        # uncomment the following lines to use a database different than SQLite
         driver:   pdo_mysql
         host:     "%database_host%"
         port:     "%database_port%"
         dbname:   "%database_name%"
         user:     "%database_user%"
         password: "%database_password%"
         charset:  UTF8 

デフォルトでMySQLの設定がされてるので、良心的。 ここで以下のコマンドを流すと、app/config/parameters.ymldatabase_nameで指定した名前のデータベースが作成されます。

$ php bin/console doctrine:database:create

さて、画面はどうなるかな。

f:id:hiroyuki-hanai:20161112025849p:plain

あ。。。データがないのか。。。 app/config/config.ymlコメントアウトした"%kernel.root_dir%/data/blog.sqlite"を読んでるっぽい。 SQLiteの中身をダンプして確認。

$ sqlite3 ./app/data/blog.sqlite 
SQLite version 3.8.5 2014-08-15 22:37:57
Enter ".help" for usage hints.
sqlite> .tables
symfony_demo_comment  symfony_demo_post     symfony_demo_user   
sqlite> .output ./dump.txt
sqlite> .dump
sqlite> .output stdout
sqlite>.exit

以下を参照して、SQLiteダンプをMySQLに突っ込む。

blog.gufii.net

$ mysql -uroot symfony < target.sql
ERROR 1064 (42000) at line 2: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CLOB NOT NULL, authorEmail VARCHAR(255) NOT NULL, publishedAt DATETIME NOT NULL,' at line 1

MySqlにCLOB型がないみたいなので、target.sqlのCLOBをTEXTに変更

$ mysql -uroot symfony < target.sql
ERROR 1064 (42000) at line 2: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'NOT NULL, authorEmail VARCHAR(255) NOT NULL, publishedAt DATETIME NOT NULL, PRIM' at line 1

NOT DEFERRABLE INITIALLY IMMEDIATEの指定がMySQLだと効かないみたいなので、この部分を削除して、SET FOREIGN_KEY_CHECKS=0;を追加。

詳細はapp/data/target.sql参照 これでMySQLでデモアプリが表示された。


アプリに関する設定

app/config/配下のconfig.ymlについて。「環境ごとに設定を書き換えられるように」Symfonyが以下を用意してくれている。 このコンセプトに反するから、「変更されることが予想されいてない設定は定数として定義しましょう。」ということ。

  • config_prod.ymlは本番環境の設定
  • config_dev.ymlは開発環境の設定
  • config_test.ymlはファンクショナルテスト用の環境設定。ブラウザからはアクセスできない。*1

環境設定ファイルの読み込みは、AppKernel.registerContainerConfiguration()で実行されている。

「semantic dependency injection」とはなんだろう

まずは辞書的な「semantic」の意味を調べる。

セマンティックとは、一般的には「意味」や「意味論」に関することを指す語である。IT用語としては、コンピュータに文書や情報の持つ意味を正確に解釈させ、文書の関連付けや情報収集などの処理を自動的に行わせる技術について用られる語である。

う〜ん。わからん。Best Practiceでは*Extensionを使うのを「semantic dependency injection」と言っているので、「How to Load Service Configuration inside a Bundle」をチェック。

Symfonyでは、様々なサービスを使っていることに気づくでしょう。これらのサービス群は、あなたのアプリのapp/config/ディレクトリに登録されていることでしょう。しかし他のプロジェクトから、そのバンドルを使いたくなったときには、バンドル自体にサービス設定が内包されていた方が良いでしょう。

ふむ。バンドル単体を公開したい人に向けたBestPracticeですかね。バンドルの利用者が設定をごにょごにょしなくても済むように、バンドルの中にサービスの設定を閉じ込めておこうという趣旨であることは理解できました。

Symfony Meetup #15参加記録

Symfony Meetup#15に参加してきました。

symfony.connpass.com

Symfony Best Practice座談会

前半の座談会では、昨日まで翻訳していたBest Practiceを使っていただけました。 お役に立ててよかったです。

speakerdeck.com

数カ所、訳がわかりにくいところあったので、写経しながら直していきます。(アドベントカレンダーまでにやる。)

qiita.com

LT大会

@malloc007 さん

学びの広げ方の考察がすごいよかった。 Ajax呼び出しの仕組みに感動して、フロントエンドからサーバサイドまでのレイヤー間の連携について理解したのに飽き足らず、 CPUの回路にまで理解を広げようとする貪欲さ本当にすごい。見習いたい。

@polidog さん

Bundleを作る話。 app.phpからSymfonyでリクエストを処理している一連のの流れを一回おさらいしたくなった。 アドベントカレンダー投稿し終わったらやる。

@ganchikuさん

Unitテストの守備範囲について議論。 どんなテストで何を担保したいのかを意識する必要性を再認識。

メモ

SRP(Single Responsibility Principal) Unitテストの範囲外:API/DB/Mail/ファイルシステムと接続する・スレッドをまたぐ

RequestStackのInject TokenStrageInterfaceのInject メソッドチェーンをしないようにサービスをインジェクトする。オブジェクトが知っているべき範囲を明確にする。 ユニットテストを書いているから安心してリファクタリングできない場合もあるのではないか?(振る舞い) ユニットテストの改善とリファクタリングは表裏一体になるのかについて議論


@ttskch さん

サーバーで直接触ったコードをgitにcommitする仕組みのはなし。

アフタートーク

BuisinessLogicが何を指すのか。についての議論。 BestPracticeではwikipeiaの記事にリンクが貼ってあって、BuisinessLogic = CRUDに関わるものという理解。

今回も学びの多い、自分の理解不足を痛感する勉強会でした。

Symfony Best Practice 訳してみた - Chapter11 -

Symfony Best Practiceの翻訳をしてます。 9日目−3です。これでBestPracticeは最終章です。

前回の記事

hanahirodev.hatenablog.com

※多分に意訳が入っておりますので、誤訳がある場合はご指摘ください。

※読み進めていくSymfony Best Practiceは 2016/10/23時点の情報です。

※写経をする場合は、事前にPHPの実行ができる環境をご用意ください。

3行まとめ

  • ファンクショナルテストを飛ばすことなかれ。

  • 簡単でもいいからファンクショナルテストを書こう。必ず将来役に立つ。

  • ファンクショナルテストではURLをハードコーディングしよう。


第11章 テスト

大雑把に言えば、テストには2種類あります。ユニットテストでは、ある機能の入出力をテストできます。ファンクショナルテストでは「ブラウザ」から操作(リンクのクリックやフォームへの入力とそのチェック)した結果を判定することができます。

ユニットテスト

ユニットテストは、Symfonyからは独立して存在するべき、「ビジネスロジック」をテストするものです。このため、Symfonyユニットテストのためにどういったツールを使うかといった制約を持っていません。最もポピュラーなものは、PhpUnit*1PhpSpec*2です。

ファンクショナルテスト

良いファンクショナルテストを書くのは大変なので、テストをしていない開発者もいます。ファンクショナルテストを飛ばしてはいけません!簡単なファンクショナルテストを書くことで、デプロイする前に致命的なエラーを発見できるのです。

  • 最低でも作ったページがきちんとロードされるかを確認するファンクショナルテストを書きましょう。

ファンクショナルテストはこんなに簡単です。

// tests/AppBundle/ApplicationAvailabilityFunctionalTest.php
namespace Tests\AppBundle;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class ApplicationAvailabilityFunctionalTest extends WebTestCase
{
    /**
     * @dataProvider urlProvider
     */
    public function testPageIsSuccessful($url)
    {
        $client = self::createClient();
        $client->request('GET', $url);

        $this->assertTrue($client->getResponse()->isSuccessful());
    }

    public function urlProvider()
    {
        return array(
            array('/'),
            array('/posts'),
            array('/post/fixture-post-1'),
            array('/blog/category/fixture-category'),
            array('/archives'),
            // ...
        );
    }
}

このテストでは、HTTPレスポンスが200または299であることを確かめることで、URLのロードが成功したか判定しています。ここだけ見ると意味のあることには思えないかもしれませんが、テストを書くのにかかる労力に比べれば、アプリケーションにファンクショナルテストがあることには価値があります。 コンピューターソフトウェアにおいて、こうしたテストはスモークテスト*3と呼ばれ、将来のリリースに備えて、単純なミスを発見する予備的なテストで構成されます。

ファンクショナルテストにURLをハードコーディングする

先ほどのファンクショナルテストでURLジェネレーターを使わないことに疑問を持った方がいるでしょう。

  • ファンクショナルテストで使うURLはURLジェネレーターを使うのではなく、ハードコーディングすべし。

テストするページのURL生成にrouterサービスを使った以下の例で考えてみましょう。

public function testBlogArchives()
{
    $client = self::createClient();
    $url = $client->getContainer()->get('router')->generate('blog_archives');
    $client->request('GET', $url);

    // ...
}

これは動きますが、1つだけとんでもない欠点があります。開発者が誤って、blog_archivesのルーティングを変えてしまったとしましょう。テストは通りますが、もともと(修正前の)のURLでは動かなくなってしまいます!そのURLに付けられたブックマークは機能しなくなり、検索エンジンのランキングも落ちてしまいます。

JavaScriptのファンクショナルテスト

標準搭載のテストクライアントは素晴らしいですが、JavaScriptの挙動をテストすることはできません。もしテストする必要があるなら、PHPUnitに搭載されているMink*4というライブラリを利用してみてはいかがでしょう。 もちろん、巨大なJavaScriptによるフロントエンドをお持ちなら、JavaScriptベースのテストツールを使った方が良いでしょう。

ファンクショナルテストについてさらに詳しく

HautelookAliceBundle*5はFaker*6とAlice*7を使って、データを作り実運用に近い状態でテストすると良いでしょう。


感想

「意味のある」ファンクショナルテストは絶対に書こう。 ルーティングと「検索エンジンのランキング」の関係は確かに盲点だった。

Symfony Best Practice 訳してみた - Chapter10 -

Symfony Best Practiceの翻訳をしてます。 9日目−2です。

前回の記事

hanahirodev.hatenablog.com

※多分に意訳が入っておりますので、誤訳がある場合はご指摘ください。

※読み進めていくSymfony Best Practiceは 2016/10/23時点の情報です。

※写経をする場合は、事前にPHPの実行ができる環境をご用意ください。

3行まとめ

  • Standard Edition ではSymfony2.8~ Asseticは標準搭載ではなくなった。

  • フロントエンドアプリケーションを作るのでなければ、Asseticを使う。

  • AsseticはTwigのタグで囲むだけで簡単に圧縮・最小化ができる。


第10章 Webアセット

アセットとは、CSSJavaScript、イメージファイルのように、フロントエンドの見た目と操作を構成するものです。 これまでは、バンドルごとのResources/public/ディレクトリ配下に配置していました。

バンドルごとにアセットを格納して散らばった状態では、管理が難しくなります。デザイナーにとっては1カ所に配置されている方がずっと幸せです。 リンクが簡潔になるので、テンプレートファイルもアセットと一緒にしておくのが良いでしょう。

<link rel="stylesheet" href="{{ asset('css/bootstrap.min.css') }}" />
<link rel="stylesheet" href="{{ asset('css/main.css') }}" />

{# ... #}

<script src="{{ asset('js/jquery.min.js') }}"></script>
<script src="{{ asset('js/bootstrap.min.js') }}"></script>
  • web/は公開ディレクトリであることを忘れないでください。配下に格納されているファイルは変換前のファイルであってもアクセスできてしまいます。(例:Sass, LESS ,CoffeeScript)
アセティックを使う
  • Symfony2.8からアセティックはSymfonyスタンダードエディションでは標準搭載されていません。インストールと有効化の方法はドキュメント*1を確認してください。

最近では、静的なCSSJavaScriptを作ってテンプレートに読み込むということはしないかもしれません。その代わりにクライアント側の性能を出すために、結合してミニファイした状態で使いたいかもしれません。あるいはLESSやSassを使いたいかもしれません。ということは、アセットをCSSに変換する方法が必要になるということです。 実現するためのツール、例えばGruntJSのようにフロントエンド用(PHPではない)ツールは、たくさんあります。

  • Webアセットをコンパイルして、結合、最小化するのにGruntJSのようなフロントエンドツールに満足いかないのであれば、アセティックを使いましょう。

アセティックはLESS、SaasCoffeeScriptといった様々な技術で開発されたアセットをコンパイルすることができるアセットマネージャーです。アセティックでアセットを統合するには、1つのTwigタグで全てのアセットを囲みます。

{% stylesheets
    'css/bootstrap.min.css'
    'css/main.css'
    filter='cssrewrite' output='css/compiled/app.css' %}
    <link rel="stylesheet" href="{{ asset_url }}" />
{% endstylesheets %}

{# ... #}

{% javascripts
    'js/jquery.min.js'
    'js/bootstrap.min.js'
    output='js/compiled/app.js' %}
    <script src="{{ asset_url }}"></script>
{% endjavascripts %}
フロントエンドベースのアプリケーション

近年、AngularJSのようなAPIと通信するフロントエンド技術が一般化してきています。 そういったアプリケーションを作る場合は、BowerやGruntJSのような、それぞれの技術で推奨されているツールを使うようにしてください。フロントエンドアプリはSymfonyとは分けて(リポジトリすら分けて)開発するべきです。

アセティックについてさらに詳しく

アセティックはUglifyCssやUglifyJSをつかう*2ことで、CSSJavaScriptを最小化して、Webサイトの高速化を実現しています。また、アセティックは画像を圧縮して*3ユーザーに返すこともできます。公式ドキュメント*4から利用できる機能を確認してください。


感想

「フロントエンドベースのアプリ」の理解ができないので、ちょっと調べる。 AsseticはGruntよりだいぶ手軽な印象。

(2016/11/5 更新)

next

hanahirodev.hatenablog.com

Symfony Best Practice 訳してみた - Chapter9 -

Symfony Best Practiceの翻訳をしてます。 9日目です。

前回の記事

hanahirodev.hatenablog.com

※多分に意訳が入っておりますので、誤訳がある場合はご指摘ください。

※読み進めていくSymfony Best Practiceは 2016/10/23時点の情報です。

※写経をする場合は、事前にPHPの実行ができる環境をご用意ください。

3行まとめ

  • パスワード生成にはbcryptエンコーダーを使おう

  • security.yml,@Securityアノテーション,security.authorization_checkerサービス,Voterを使って制限をかける。

  • ユーザー登録/パスワード再発行/ユーザーのなりすまし機能/ログイン状態の継続機能は標準/FOSUserBundleでサポートされている。


第9章 セキュリティ

認証とファイアウォール(たとえばユーザーの資格情報取得)

あるユーザーが任意の方法で、任意のソースにアクセスできるようにSymfonyを設定することができます。内容は複雑ですが、セキュリティガイドに詳しく掲載されています。 要不要に関わらず、認証に関する設定はsecurity.ymlfirewallsキー配下に設定されています。

  • 正規に許可された2つの異なる認証システムとユーザが無い限り (たとえば、メインのサイトへのログインと API のためだけのトークンシステム)、 anonymous キーを有効にしたファイヤーウォールを1つだけ設けることを推奨します。

ほとんどのアプリケーションでは1つの認証システムと1組のユーザーだけを持っています。このため、1つのファイヤーウォールの設定だけで事足りていました。もちろん、WebサイトとAPIを分けたい場合は例外です。シンプルにしておくことが重要です。 加えて、ファイヤーウォールの内側ではanonymousキーを使うようにしましょう。ログ採取のために、サイト内のセクションごとに(あるいはほとんどすべてのセクション)ユーザーの情報が必要なのであれば、access_controlエリアを利用しましょう。

  • ユーザーパスワードの生成にはbcryptエンコーダーを使いましょう。

ユーザーがパスワードを保持する場合、SHA-512ハッシュエンコーダーではなく、bcryptエンコーダーを使うことをお勧めします。bcryptをお勧めする理由は、salt値を含むため、レインボーテーブル攻撃対策*1になりますし、総当り攻撃を遅延させる効果も有ります。

以上を念頭に置いて、ログインフォームでデータベースからユーザーを読み込むための認証設定をしていきます。

# app/config/security.yml
security:
    encoders:
        AppBundle\Entity\User: bcrypt

    providers:
        database_users:
            entity: { class: AppBundle:User, property: username }

    firewalls:
        secured_area:
            pattern: ^/
            anonymous: true
            form_login:
                check_path: login
                login_path: login

            logout:
                path: security_logout
                target: homepage

# ... access_control exists, but is not shown here
  • Blogアプリのサンプルコードには各パートを説明するために、コメントが記載されています。
認可(たとえばアクセスの拒否)*2

Symfonyは認可のための仕組みを、security.yml*3access_controlセクション、@Securityアノテーション*4security.authorization_checkerサービスのisGrantedメソッド*5のように複数提供しています。

  • 一般的なURLパターンによる保護にはaccess_controlを使う。
  • 利用可能である場所では、@Securityアノテーションを使う。
  • 複雑な状況にある場合は、security.authorization_checkerサービスで直接セキュリティをチェックする。

カスタムセキュリティVoter*6ACLによって、認可ロジックを1箇所に集中させる方法もあります。

  • 細かな制限のためにセキュリティVoterを定義する。
  • 管理機能を経由したユーザーがあらゆるオブジェクトにアクセスするの制限するために、Symfony ACLを利用する。
@Securityアノテーション

コントローラーごとにアクセス制御をするためには、可能な限り、@Securityアノテーションを利用します。こうすることで、読みやすく、アクションごとに一貫性のある設定が出来ます。

Blogアプリでは、記事を投稿するためにROLE_ADMINが必要です。@Securityアノテーションを使うことで、以下のようになります。

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
// ...

/**
 * Displays a form to create a new Post entity.
 *
 * @Route("/new", name="admin_post_new")
 * @Security("has_role('ROLE_ADMIN')")
 */
public function newAction()
{
    // ...
}
複雑なセキュリティの設定に対応する

小さいながらもさらに込み入ったセキュリティを実装したい場合、@Securityアノテーションの中でexpression*7を利用できます。以下の例では、getAuthorEmailメソッドの返り値にPostオブジェクトに格納されたメールアドレスがマッチしたユーザーだけがコントローラーにアクセスできます。

use AppBundle\Entity\Post;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;

/**
 * @Route("/{id}/edit", name="admin_post_edit")
 * @Security("user.getEmail() == post.getAuthorEmail()")
 */
public function editAction(Post $post)
{
    // ...
}

自動的にPostオブジェクトを、 $postに設定するためにはParamComverter*8が必要です。 この方法には1つ大きな欠点があります。アノテーション内に記述してしまうと、他の場所で同じ設定を使いたくなった時に再利用しづらくなってしまいます。ブログアプリで、投稿者だけが参照できる画面へのリンクを追加したい場合を考えてみましょう。以下の状態だと、Twigの条件を繰り返し記述しないといけません。

{% if app.user and app.user.email == post.authorEmail %}
    <a href=""> ... </a>
{% endif %}

シンプルなロジックであれば、もっとも簡単な方法は、Postエンティティにユーザーが投稿者かどうか判定するメソッドを追加することです。

// src/AppBundle/Entity/Post.php
// ...

class Post
{
    // ...

    /**
     * Is the given User the author of this Post?
     *
     * @return bool
     */
    public function isAuthor(User $user = null)
    {
        return $user && $user->getEmail() == $this->getAuthorEmail();
    }
}

これでテンプレート内からも、Securityアノテーションからもメソッドにアクセスすることができます。

use AppBundle\Entity\Post;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;

/**
 * @Route("/{id}/edit", name="admin_post_edit")
 * @Security("post.isAuthor(user)")
 */
public function editAction(Post $post)
{
    // ...
}
{% if post.isAuthor(app.user) %}
    <a href=""> ... </a>
{% endif %}
@Security を使わずにアクセス権をチェックする

上記の例では@SecurityアノテーションはParamConverterを使っているからこそ、変数postにアクセスできる状態になっていました。アノテーションを使わない場合または、複雑なユースケースである場合、PHP側でセキュリティチェックを書くこともできます。

/**
 * @Route("/{id}/edit", name="admin_post_edit")
 */
public function editAction($id)
{
    $post = $this->getDoctrine()->getRepository('AppBundle:Post')
        ->find($id);

    if (!$post) {
        throw $this->createNotFoundException();
    }

    if (!$post->isAuthor($this->getUser())) {
        $this->denyAccessUnlessGranted('edit', $post);

        // or without the shortcut:
        //
        // use Symfony\Component\Security\Core\Exception\AccessDeniedException;
        // ...
        //
        // if (!$this->get('security.authorization_checker')->isGranted('edit', $post)) {
        //    throw $this->createAccessDeniedException();
        // }
    }

    // ...
}
セキュリティVoter

複雑なセキュリティロジックを実装する必要があって、isAuthor()のようなメソッドにまとめられない場合は、カスタムVoterを活用しましょう。簡単な順に、メソッド、カスタムVoter、ACLです。いずれの方法も、大抵の要求に対応できる程度には柔軟です。 まずVoterクラスを作りましょう。以下の例は上記で実装してきたgetAuthorEmailと同じ内容を実装しています。

namespace AppBundle\Security;

use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;
use AppBundle\Entity\Post;

class PostVoter extends Voter
{
    const CREATE = 'create';
    const EDIT   = 'edit';

    /**
     * @var AccessDecisionManagerInterface
     */
    private $decisionManager;

    public function __construct(AccessDecisionManagerInterface $decisionManager)
    {
        $this->decisionManager = $decisionManager;
    }

    protected function supports($attribute, $subject)
    {
        if (!in_array($attribute, array(self::CREATE, self::EDIT))) {
            return false;
        }

        if (!$subject instanceof Post) {
            return false;
        }

        return true;
    }

    protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
    {
        $user = $token->getUser();
        /** @var Post */
        $post = $subject; // $subject must be a Post instance, thanks to the supports method

        if (!$user instanceof UserInterface) {
            return false;
        }

        switch ($attribute) {
            case self::CREATE:
                // if the user is an admin, allow them to create new posts
                if ($this->decisionManager->decide($token, array('ROLE_ADMIN'))) {
                    return true;
                }

                break;
            case self::EDIT:
                // if the user is the author of the post, allow them to edit the posts
                if ($user->getEmail() === $post->getAuthorEmail()) {
                    return true;
                }

                break;
        }

        return false;
    }
}

セキュリティVoter を有効化するには、以下のように新しいサービスを定義します。

# app/config/services.yml
services:
    # ...
    post_voter:
        class:      AppBundle\Security\PostVoter
        arguments: ['@security.access.decision_manager']
        public:     false
        tags:
           - { name: security.voter }

これで、@SecurityアノテーションからVoterクラスを利用できます。

/**
 * @Route("/{id}/edit", name="admin_post_edit")
 * @Security("is_granted('edit', post)")
 */
public function editAction(Post $post)
{
    // ...
}

security.authorization_checkerサービスやコントローラー経由で直接アクセスすることもできます。

/**
 * @Route("/{id}/edit", name="admin_post_edit")
 */
public function editAction($id)
{
    $post = ...; // query for the post

    $this->denyAccessUnlessGranted('edit', $post);

    // or without the shortcut:
    //
    // use Symfony\Component\Security\Core\Exception\AccessDeniedException;
    // ...
    //
    // if (!$this->get('security.authorization_checker')->isGranted('edit', $post)) {
    //    throw $this->createAccessDeniedException();
    // }
}
さらに詳しく

Symfonyのコミュニティーで開発されたFOSUserBundle*9は、Symfonyから使えるデータベースによるユーザーシステムを提供しています。このバンドルはユーザー登録やパスワードの再発行といった一般的な機能も取り扱います。 Remember Me機能 *10を有効化することで、しばらくの間ログインした状態を保持できます。 カスタマーサポート機能では、問題を再現するために別のユーザーになりすましてアプリにアクセスする必要が出てきます。Symfonyはユーザーのなりすまし機能*11を提供しています。 Symfonyがサポートしていないログイン方法を提供している場合、自前のユーザープロバイダー*12認証プロバイダー*13を開発することもできます。


感想

標準でかなり高機能なセキュリティ機能が提供されている。 Voterについてもうちょっと調べる。

(2016/11/5 更新)

next

hanahirodev.hatenablog.com