12. 12日目: Sonata Admin Bundle

  • この記事は Symfony 1.4 向けのオリジナルの Jobeet Tutorial の一部分です。
Jobeet の 11 日目に行った追加により、アプリケーションは求職者とジョブの投稿者の両方とも使用可能になります。
それでは、アプリケーションの管理領域について少し話をする時間です。
今日では、 SonataAdminBundle のおかげで、1 時間未満で Jobeet のための完全な管理画面を開発します。

12.1. Sonata Core バンドルのインストール

はじめに、 Core バンドルをインストールします。

$ php composer.phar require sonata-project/core-bundle:~2.2

AppKernel.php に登録します。

app/AppKernel.php

// ...
    public function registerBundles()
    {
        $bundles = array(
            // ...
            new Sonata\CoreBundle\SonataCoreBundle(),
            // ...
        );
    }
// ...

設定ファイルを作成します。

app/config/sonata_core.yml

sonata_core: ~

config.ymlの imports に sonata_core.yml を追加いたします。

app/config/config.yml

imports:
    #...
    - { resource: sonata_core.yml }

12.2. Sonata Admin バンドルのインストール

vendor のディレクトリに SonataAdminBundle とその依存するものをダウンロードを開始します。

$ php composer.phar require sonata-project/admin-bundle

SonataAdminBundle とその依存関係の最新バージョンをインストールするには、 * を入力します。

ibw@ubuntu:/var/www/jobeet$ php composer.phar require sonata-project/admin-bundle
Please provide a version constraint for the sonata-project/admin-bundle requirement: *

また SonataDoctrineORMADminBundle をインストールする必要があります。

$ php composer.phar require sonata-project/doctrine-orm-admin-bundle

ここで、新しいバンドルとその依存関係を宣言するため、 AppKernel.php ファイルを開いて、次のコードを追加します。

app/AppKernel.php

// ...
    public function registerBundles()
    {
        $bundles = array(
            // ...
            new Sonata\AdminBundle\SonataAdminBundle(),
            new Sonata\BlockBundle\SonataBlockBundle(),
            new Sonata\DoctrineORMAdminBundle\SonataDoctrineORMAdminBundle(),
            new Knp\Bundle\MenuBundle\KnpMenuBundle(),
        );
    }

// ...

設定ファイルを変更します。最後に以下を追加します。

app/config/config.yml

# ...
sonata_admin:
    title: Jobeet Admin

sonata_block:
    default_contexts: [cms]
    blocks:
        sonata.admin.block.admin_list:
            contexts:   [admin]

        sonata.block.service.text:
        sonata.block.service.action:
        sonata.block.service.rss:

また、translator キーを探して、コメント化されてる場合はコメント解除します。

app/config/config.yml

# ...
framework:
    # ...
    translator: { fallback: %locale%}
    # ...
#...

アプリケーションを動作させるためには、アプリケーションのルーティング·ファイルに admin ルートをインポートします。

app/config/routing.yml

admin:
    resource: '@SonataAdminBundle/Resources/config/routing/sonata_admin.xml'
    prefix: /admin

_sonata_admin:
    resource: .
    type: sonata_admin
    prefix: /admin

# ...

さて、バンドルからアセットをインストールします。

$ php app/console assets:install web --symlink

キャッシュを削除することを忘れないでください。

$ php app/console cache:clear --env=dev
$ php app/console cache:clear --env=prod
これで、次のURLを使用して admin のダッシュボードにアクセスできるはずです。
_images/Day-12-sonata_installed.png

12.3. CRUD コントローラー

CRUD コントローラは、基本的な CRUD アクションが含まれています。
コントローラー名を正しい Admin インスタンスにマッピングすることで、 Admin クラスに関連付けます。
一部またはすべてのアクションは、プロジェクトの要件に合わせて上書きすることができます。
コントローラは、さまざまなアクションを構築するために、 Admin クラスを使用します。
コントローラの内部では、 Admin オブジェクトは、構成プロパティを介してアクセスできます。
それでは、各エンティティのためのコントローラを作成してみましょう。まず、カテゴリ・エンティティから。

src/Ibw/JobeetBundle/Controller/CategoryAdminController.php

<?php
namespace Ibw\JobeetBundle\Controller;

use Sonata\AdminBundle\Controller\CRUDController as Controller;

class CategoryAdminController extends Controller
{
    // Your code will be here
}

そして、次にジョブ。

src/Ibw/JobeetBundle/Controller/JobAdminController.php

<?php
namespace Ibw\JobeetBundle\Controller;

use Sonata\AdminBundle\Controller\CRUDController as Controller;

class JobAdminController extends Controller
{
    // Your code will be here
}

12.4. Admin クラスの作成

Admin クラスは、モデルと管理領域(forms, list, show)のマッピングを表します。
お使いのモデルのための Admin クラスを作成する最も簡単な方法は、 Sonata\AdminBundle\Admin\Admin クラスを拡張することです。
バンドルの Admin フォルダに Admin クラスを作成します。
Admin ディレクトリを作成し、カテゴリのための Admin クラスを作成します。

src/Ibw/JobeetBundle/Admin/CategoryAdmin.php

<?php
namespace Ibw\JobeetBundle\Admin;

use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
use Sonata\AdminBundle\Form\FormMapper;

class CategoryAdmin extends Admin
{
    // Your code will be here
}

そして、ジョブにも同様に Admin クラスを作成します。

src/Ibw/JobeetBundle/Admin/JobAdmin.php

<?php
namespace Ibw\JobeetBundle\Admin;

use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use Ibw\JobeetBundle\Entity\Job;

class JobAdmin extends Admin
{
    // Your code will be here
}

各 admin クラスを設定ファイルである services.yml·に以下のように追加する必要があります。

src/Ibw/JobeetBundle/Resources/config/services.yml

services:
    ibw.jobeet.admin.category:
        class: Ibw\JobeetBundle\Admin\CategoryAdmin
        tags:
            - { name: sonata.admin, manager_type: orm, group: jobeet, label: Categories }
        arguments:
            - ~
            - Ibw\JobeetBundle\Entity\Category
            - 'IbwJobeetBundle:CategoryAdmin'

    ibw.jobeet.admin.job:
        class: Ibw\JobeetBundle\Admin\JobAdmin
        tags:
            - { name: sonata.admin, manager_type: orm, group: jobeet, label: Jobs }
        arguments:
            - ~
            - Ibw\JobeetBundle\Entity\Job
            - 'IbwJobeetBundle:JobAdmin'

この時点で、ダッシュボードに Jobeet のグループを見ることができ、その中に、ジョブとカテゴリモジュールそれぞれに addlist のリンクをもちます。

_images/Day-12-sonata_interface.jpg

12.5. Admin クラスの構成

現在どのリンクをたどっても、何も起こりません。
リストやフォームに属しているフィールドを設定していない為です。
まずはカテゴリに対して、基本的な設定を行いましょう。

src/Ibw/JobeetBundle/Admin/CategoryAdmin.php

namespace Ibw\JobeetBundle\Admin;

use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
use Sonata\AdminBundle\Form\FormMapper;

class CategoryAdmin extends Admin
{
    // setup the default sort column and order
    protected $datagridValues = array(
        '_sort_order' => 'ASC',
        '_sort_by' => 'name'
    );

    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper
            ->add('name')
            ->add('slug')
        ;
    }

    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    {
        $datagridMapper
            ->add('name')
        ;
    }

    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper
            ->addIdentifier('name')
            ->add('slug')
        ;
    }
}

そしてジョブの設定をします。

src/Ibw/JobeetBundle/Admin/JobAdmin.php

namespace Ibw\JobeetBundle\Admin;

use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use Ibw\JobeetBundle\Entity\Job;

class JobAdmin extends Admin
{
    // setup the defaut sort column and order
    protected $datagridValues = array(
        '_sort_order' => 'DESC',
        '_sort_by' => 'created_at'
    );

    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper
            ->add('category')
            ->add('type', 'choice', array('choices' => Job::getTypes(), 'expanded' => true))
            ->add('company')
            ->add('file', 'file', array('label' => 'Company logo', 'required' => false))
            ->add('url')
            ->add('position')
            ->add('location')
            ->add('description')
            ->add('how_to_apply')
            ->add('is_public')
            ->add('email')
            ->add('is_activated')
        ;
    }

    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    {
        $datagridMapper
            ->add('category')
            ->add('company')
            ->add('position')
            ->add('description')
            ->add('is_activated')
            ->add('is_public')
            ->add('email')
            ->add('expires_at')
        ;
    }

    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper
            ->addIdentifier('company')
            ->add('position')
            ->add('location')
            ->add('url')
            ->add('is_activated')
            ->add('email')
            ->add('category')
            ->add('expires_at')
            ->add('_action', 'actions', array(
                'actions' => array(
                    'view' => array(),
                    'edit' => array(),
                    'delete' => array(),
                )
            ))
        ;
    }

    protected function configureShowField(ShowMapper $showMapper)
    {
        $showMapper
            ->add('category')
            ->add('type')
            ->add('company')
            ->add('webPath', 'string', array('template' => 'IbwJobeetBundle:JobAdmin:list_image.html.twig'))
            ->add('url')
            ->add('position')
            ->add('location')
            ->add('description')
            ->add('how_to_apply')
            ->add('is_public')
            ->add('is_activated')
            ->add('token')
            ->add('email')
            ->add('expires_at')
        ;
    }
}

show アクションのために、会社のロゴを表示するようにカスタムテンプレートを使用しました。

src/Ibw/JobeetBundle/Resources/views/JobAdmin/list_image.html.twig

<tr>
    <th>Logo</th>
    <td><img src="{{ asset(object.webPath) }}" /></td>
</tr>
これにより、ジョブとカテゴリの操作の基本的な管理モジュールを作成しました。
次のような多くの機能をもちます。
  • オブジェクトのリストはページ分割されています。
  • リストはソート可能です。
  • リストは、フィルタリングすることができます。
  • オブジェクトは、作成、編集、および削除することができます。
  • 選択されたオブジェクトは、バッチで削除することができます。
  • フォームの検証が有効になっています。
  • フラッシュ·メッセージは、ユーザーへ即時にフィードバックを与えます。

12.6. バッチアクション

バッチアクションは(すべての、または、特定のサブセットの)選択したモデルに対して実行されるアクションです。
リストビューで簡単にいくつかのカスタムバッチアクションを追加することができます。
デフォルトでは、delete アクションを使用すると、一度に複数のエントリを削除することができます。
新しいバッチアクションを追加するには、 Admin クラスから getBatchActions をオーバーライドする必要があります。
では、ここで新しい extend アクションを定義しましょう。

src/Ibw/JobeetBundle/Admin/JobAdmin.php

// ...

public function getBatchActions()
{
    // retrieve the default (currently only the delete action) actions
    $actions = parent::getBatchActions();

    // check user permissions
    if($this->hasRoute('edit') && $this->isGranted('EDIT') && $this->hasRoute('delete') && $this->isGranted('DELETE')) {
        $actions['extend'] = array(
            'label'            => 'Extend',
            'ask_confirmation' => true // If true, a confirmation will be asked before performing the action
        );

    }

    return $actions;
}
JobAdminController の batchActionExtend メソッドは、コアロジックを実現するために実行されます。
クエリーの引数から、選択したモデルを取得します。
選択したモデルは、引数に渡されたクエリーから取得します。
(より低い粒度でモデルを選択するためテンプレートレベルで別の方法を定義したなど)何らかの理由でデフォルトの選択方法なしにバッチアクションを実行することが有用である場合は、渡されるクエリは null になります。

src/Ibw/JobeetBundle/Controller/JobAdminController.php

namespace Ibw\JobeetBundle\Controller;

use Sonata\AdminBundle\Controller\CRUDController as Controller;
use Sonata\DoctrineORMAdminBundle\Datagrid\ProxyQuery as ProxyQueryInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;

class JobAdminController extends Controller
{
    public function batchActionExtend(ProxyQueryInterface $selectedModelQuery)
    {
        if ($this->admin->isGranted('EDIT') === false || $this->admin->isGranted('DELETE') === false) {
            throw new AccessDeniedException();
        }

        $modelManager = $this->admin->getModelManager();

        $selectedModels = $selectedModelQuery->execute();

        try {
            foreach ($selectedModels as $selectedModel) {
                $selectedModel->extend();
                $modelManager->update($selectedModel);
            }
        } catch (\Exception $e) {
            $this->get('session')->getFlashBag()->add('sonata_flash_error', $e->getMessage());

            return new RedirectResponse($this->admin->generateUrl('list',$this->admin->getFilterParameters()));
        }

        $this->get('session')->getFlashBag()->add('sonata_flash_success',  sprintf('The selected jobs validity has been extended until %s.', date('m/d/Y', time() + 86400 * 30)));

        return new RedirectResponse($this->admin->generateUrl('list',$this->admin->getFilterParameters()));
    }
}
それでは 60 日以上投稿者によってアクティブ化されなかったすべてのジョブを削除する新しいバッチアクションを追加してみましょう。
条件が一致するレコードを検索して削除しますので、このアクションのために、リストから任意のジョブを選択する必要はありません。

src/Ibw/JobeetBundle/Admin/JobAdmin.php

// ...

public function getBatchActions()
{
    // retrieve the default (currently only the delete action) actions
    $actions = parent::getBatchActions();

    // check user permissions
    if($this->hasRoute('edit') && $this->isGranted('EDIT') && $this->hasRoute('delete') && $this->isGranted('DELETE')){
        $actions['extend'] = array(
            'label'            => 'Extend',
            'ask_confirmation' => true // If true, a confirmation will be asked before performing the action
        );

        $actions['deleteNeverActivated'] = array(
            'label'            => 'Delete never activated jobs',
            'ask_confirmation' => true // If true, a confirmation will be asked before performing the action
        );
    }

    return $actions;
}
batchActionDeleteNeverActivated アクションを作成するのに加えて、 JobAdminController にさらに新しいメソッド batchActionDeleteNeverActivatedIsRelevant を作成します。
ほかの確認の前に実行され、実際に存在するかどうかを確認するためのものです。
(このケースでは、削除されるべきジョブの選択は JobRepository::cleanup() メソッドによって処理されますので常にtrueを返します。)

src/Ibw/JobeetBundle/Controller/JobAdminController.php

// ...

public function batchActionDeleteNeverActivatedIsRelevant()
{
    return true;
}

public function batchActionDeleteNeverActivated()
{
    if ($this->admin->isGranted('EDIT') === false || $this->admin->isGranted('DELETE') === false) {
        throw new AccessDeniedException();
    }

    $em = $this->getDoctrine()->getManager();
    $nb = $em->getRepository('IbwJobeetBundle:Job')->cleanup(60);

    if ($nb) {
        $this->get('session')->getFlashBag()->add('sonata_flash_success',  sprintf('%d never activated jobs have been deleted successfully.', $nb));
    } else {
        $this->get('session')->getFlashBag()->add('sonata_flash_info',  'No job to delete.');
    }

    return new RedirectResponse($this->admin->generateUrl('list',$this->admin->getFilterParameters()));
}
今日はここまでです。明日は、ユーザー名とパスワードで管理領域を保護する方法を説明します。
これは Symfony2 のセキュリティについて話をする機会になります。

Note

Creative Commons License

このチュートリアルは、クリエイティブ・コモンズ・ライセンス 表示 - 継承 3.0 非移植 (CC BY-SA 3.0) のもとでライセンスされています。 翻訳の元にしたオリジナルはこちらです。 Symfony2 Jobeet http://www.intelligentbee.com/blog/tag/symfony2-jobeet/.