こんにちは。
先日、酔っ払って帰宅中にメガネを紛失した武田です。

最近、Symfony2というフレームワークを利用するようになりました。
一般的な管理画面の機能であるCRUDをけっこう楽に作れるバンドルがあったので紹介してみたいと思います。
※Symfony2のドキュメントって日本語の物が少ないのでちょっとでも増やせればなんて思ってます。。。

今回はsonataAdminチュートリアルをベースに環境構築、シンブルなブログ用CRUD作成してみようと思います

実験環境

以下のバージョンで進めていきます。

  • Cent OS 6.8 64bit
  • PHP 7.0.13
  • symfony 2.8
  • MySQL 5.7.16

まずは環境構築からやっていきます。

symfonyプロジェクト作成+起動テスト

# 2.8で実験用プロジェクト作成
symfony new adminSample  2.8
cd adminSample/

# ビルドインサーバの実行
php app/console server:run 192.168.0.184:8000
※IPは環境で読み替えてください

ブラウザでアクセスしてみると。。。

無事、アクセスOK
最近のフレームワークは自前でウェブサーバまで使えて便利になりましたね

Composer インストール

今回のバンドルを入れるためにComposerを導入します。

# Composerインストール
[adjust@localhost adminSample]$   curl -sS https://getcomposer.org/installer | php
All settings correct for using Composer
Downloading...

Composer (version 1.4.1) successfully installed to: /home/adjust/sandbox/adminSample/composer.phar
Use it: php composer.phar

利用するバンドルのインストール

# sonata-project/admin-bundleインストール
php composer.phar require sonata-project/admin-bundle

Using version ^3.13 for sonata-project/admin-bundle
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
...[略]

# sonata-project/doctrine-orm-admin-bundleインストール
php composer.phar require sonata-project/doctrine-orm-admin-bundle
Using version ^3.1 for sonata-project/doctrine-orm-admin-bundle
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
...[略]

バンドルを有効化

以下に追記
vi app/AppKernel.php

        $bundles = array(
            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
            new Symfony\Bundle\SecurityBundle\SecurityBundle(),
            new Symfony\Bundle\TwigBundle\TwigBundle(),
            new Symfony\Bundle\MonologBundle\MonologBundle(),
            new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
            new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
            new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
            new AppBundle\AppBundle(),
            // These are the other bundles the SonataAdminBundle relies on
[追記]      new Sonata\CoreBundle\SonataCoreBundle(),
[追記]      new Sonata\BlockBundle\SonataBlockBundle(),
[追記]      new Knp\Bundle\MenuBundle\KnpMenuBundle(),
            // And finally, the storage and SonataAdminBundle
[追記]      new Sonata\DoctrineORMAdminBundle\SonataDoctrineORMAdminBundle(),
[追記]      new Sonata\AdminBundle\SonataAdminBundle(),
        );

configの設定

以下を追記

vi app/config/config.yml

sonata_block:
    default_contexts: [cms]
    blocks:
        # enable the SonataAdminBundle block
        sonata.admin.block.admin_list:
            contexts: [admin]

以下を修正

vi app/config/config.yml

# 言語を日本語に設定
framework:
    translator: { fallbacks: [jp] }

ルーティングの追加

以下を追記

vi app/config/routing.yml

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

/admin以下を管理画面と設定

キャッシュのクリアとアセットのインストール

# キャッシュのクリア
php app/console cache:clear

 // Clearing the cache for the dev environment with debug true
...[略]

# アセットのインストール
php app/console assets:install

 Installing assets as hard copies.

 --- -------------------------- ----------------
      Bundle                     Method / Error
 --- -------------------------- ----------------
  ?   FrameworkBundle            copy
  ?   SensioDistributionBundle   copy
 --- -------------------------- ----------------

...[略]

ひとまずこれでsonataadminバンドル準備ではできたはず。
/admin以下にアクセスしてみましょう

とりあえずは管理画面っぽいレイアウトがでてきましたね。
まだ、肝心の管理するデータなど未設定のためメニューなどはからっぽです。

エンティティの作成

とりあえずDB周りを作らないといけませんね。
まずはエンティティを作りたいと思います。

# カテゴリテーブル用エンティティの作成
php app/console doctrine:generate:entity --entity="AppBundle:Category" --fields="name:string(255)" --no-interaction
  Entity generation


  created ./src/AppBundle/Entity/
  created ./src/AppBundle/Entity/Category.php
> Generating entity class src/AppBundle/Entity/Category.php: OK!
> Generating repository class src/AppBundle/Repository/CategoryRepository.php: OK!
  Everything is OK! Now get to work :).

# ブログ記事用エンティティの作成
php app/console doctrine:generate:entity --entity="AppBundle:BlogPost" --fields="title:string(255) body:text draft:boolean" --no-interaction

  Entity generation
  created ./src/AppBundle/Entity/BlogPost.php
> Generating entity class src/AppBundle/Entity/BlogPost.php: OK!
> Generating repository class src/AppBundle/Repository/BlogPostRepository.php: OK!

  Everything is OK! Now get to work :).

無事エンティティが作成できました。

今回はblog_postテーブルとCategorテーブル連携させたいのでエンティティに設定を追加します。
これでblog_postとCategor間のリレーションを貼ることが出来ます。

以下を追記
vi src/AppBundle/Entity/BlogPost.php

    // [略]...

    /**
     * @ORM\ManyToOne(targetEntity="Category", inversedBy="blogPosts")
     */
    private $category;

    public function setCategory(Category $category)
    {
        $this->category = $category;
    }

    public function getCategory()
    {
        return $this->category;
    }

    // [略]...

以下を修正
vi src/AppBundle/Entity/BlogPost.php

    /**
     * @var bool
     *
     * @ORM\Column(name="draft", type="boolean")
     */
[修正] private $draft = false;

vi src/AppBundle/Entity/Category.php
以下を追記

    // [略]...

    /**
    * @ORM\OneToMany(targetEntity="BlogPost", mappedBy="category")
    */
    private $blogPosts;

    public function __construct()
    {
        $this->blogPosts = new ArrayCollection();
    }

    public function getBlogPosts()
    {
        return $this->blogPosts;
    }


    // [略]...

DB設定

今回はmysqlを利用します。

vi app/config/parameters.yml
以下を修正
※前もってDBは作成しておいてください
※環境にあわせて読み替えください

parameters:
    database_host: 127.0.0.1
    database_port: 3306
    database_name: admin_sample
    database_user: root
    database_password: password

テーブルの作成

エンティティに定義した項目でDBにテーブルを作りましょう

# エンティティを元にDBにテーブル作成
php app/console doctrine:schema:create

eating database schema...
Database schema created successfully!

無事動いたようです。
mysqlから出来ているか確認しましょう

mysql> show tables;
+------------------------+
| Tables_in_admin_sample |
+------------------------+
| blog_post              |
| category               |
+------------------------+
2 rows in set (0.04 sec)

mysql> desc category;
+-------+--------------+------+-----+---------+----------------+
| Field | Type         | Null | Key | Default | Extra          |
+-------+--------------+------+-----+---------+----------------+
| id    | int(11)      | NO   | PRI | NULL    | auto_increment |
| name  | varchar(255) | NO   |     | NULL    |                |
+-------+--------------+------+-----+---------+----------------+
2 rows in set (0.01 sec)

mysql> desc blog_post;
+-------------+--------------+------+-----+---------+----------------+
| Field       | Type         | Null | Key | Default | Extra          |
+-------------+--------------+------+-----+---------+----------------+
| id          | int(11)      | NO   | PRI | NULL    | auto_increment |
| title       | varchar(255) | NO   |     | NULL    |                |
| body        | longtext     | NO   |     | NULL    |                |
| draft       | tinyint(1)   | NO   |     | NULL    |                |
| category_id | int(11)      | YES  | MUL | NULL    |                |
+-------------+--------------+------+-----+---------+----------------+
5 rows in set (0.01 sec)

mysqlにも反映されてますね!

ADMIN CLASSの作成

基本的にsonata admin bundleの各CRUDはこのadminクラスに定義する形で作ります。

まずは項目が少ないcategoryテーブルの管理画面を作りましょう。

以下を新規作成
vi src/AppBundle/Admin/CategoryAdmin.php

namespace AppBundle\Admin;

use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Symfony\Component\Form\Extension\Core\Type\TextType;

class CategoryAdmin extends AbstractAdmin
{
    // 登録,更新で利用する入力フォームの定義
    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper->add('name', TextType::class); 
        // 第二引数は 'text'でも可能ですがsymfony3.0からは廃止されるようなので新しい書き方とします。
    }

    //  一覧画面での検索に利用する項目の定義
    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    {
        $datagridMapper->add('name');
    }

    // 一覧画面で表示する項目の定義
    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper->addIdentifier('name');
    }
}

サービスへの登録

作成したADMIN classをサービスとして登録します。

vi config/services.yml

以下を追記

services:
    # ...
    admin.category:
        class: AppBundle\Admin\CategoryAdmin
        arguments: [~, AppBundle\Entity\Category, ~]
        tags:
            - { name: sonata.admin, manager_type: orm, label: Category }

ルーティングの調整

vi app/config/routing.yml

以下を追記

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

カテゴリ管理画面確認

これでひとまずは揃ったはず動きを見てましょう

  • ダッシュボード

    左メニュー及びダッシュボード中央にCategoryが追加されてることがわかります。

  • カテゴリ登録
    データがないので新規登録をしてみましょう。
    定義した通りカテゴリ名の項目がありますので入力してみましょう。

    登録できたようです。

  • カテゴリ一覧

先に登録したカテゴリが表示されてますね。

この時点でも一般的な機能を既に備えていることが、見てもらうとわかるかなと思います。
かなり盛りだくさんで制作の際は逆に機能を削ることなるレベル

  • 項目でのsort
  • 検索
  • 一覧で複数チェックしての削除
  • json,xml,csv,xls形式での書き出し
  • ページネーション
  • 1ページ上での表示件数変更

ブログ記事 ADMIN CLASS

Categoryと同様にブログ記事管理画面を作成しましょう

以下を新規作成
vi src/AppBundle/Admin/BlogPostAdmin.php

namespace AppBundle\Admin;

use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;

class BlogPostAdmin extends AbstractAdmin
{
    // 登録,更新で利用する入力フォームの定義
    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper
            ->add('title', TextType::class)
            ->add('body',  TextareaType::class)
            // カテゴリに関してはリレーションをはったcategoryテーブルから参照させます。
            ->add('category', EntityType::class, array(
                'class' => 'AppBundle\Entity\Category',
                'property' => 'name',
            ))
        ;
    }

    //  一覧画面での検索に利用する項目の定義
    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    {
        $datagridMapper
            ->add('title')
            // こちらもカテゴリに関しては~
            ->add('category', null, array(), EntityType::class, array(
                'class'    => 'AppBundle\Entity\Category',
                'choice_label' => 'name',
            ))        
            ->add('draft')
        ;
    }

    // 一覧画面で表示する項目の定義
    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper
        ->addIdentifier('title')
        ->add('category.name') // こちらもカテゴリに関しては~
        ->add('draft')
        ;
    }
}
  • カテゴリテーブルとのリレーションがあるので若干書き方が違います。

サービスへの追加

vi app/config/services.yml

以下を追記

services:
    # ...
    admin.blog_post:
        class: AppBundle\Admin\BlogPostAdmin
        arguments: [~, AppBundle\Entity\BlogPost, ~]
        tags:
            - { name: sonata.admin, manager_type: orm, label: Blog post }

ブログ記事管理画面確認

設定が終わったので実際触ってみましょう

  • 登録画面
    設定通り、項目が表示なされました。
    記事も無事登録できるようです。

  • 一覧画面

まとめ

  • いかがでしょうか。シンプルな管理画面ならこの程度の行程で実装可能です。
    どうです使ってみたくなってきたんじゃあないでしょうか?
    次回はこれを改修してよりよいものにしていく予定です。
    以下の機能を追加を考えてます。
    • 日本語化
    • バリデーション
    • 画像の登録
    • etc

参考