このエントリーをはてなブックマークに追加

枠が空いてたので2日連続で書いちゃいます。
Symfony Advent Calendar 2017 7日目の記事です。

Symfony3.3 以降のSymfonyプロジェクトでは、コントローラクラスを書く時にFrameworkBundleの Controller を継承する必要がなくなりました。

<?php


namespace App\Controller;
use App\Entity\Task;
use Doctrine\ORM\EntityManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Response;

class TaskController
{
  // ...
}

https://github.com/77web/sf3.4-functional-test-sample/blob/master/src/Controller/TaskController.php

Controller クラスを継承しなくて良くなったことを歓迎する声をよく聞きます。

私も、コントローラー内にコンテナを持たず、依存先サービスをinterface名やクラス名ではっきり指定することで、コントローラクラスの見通しが良くなったと思います。まだどこかのコントローラで使っているサービスをうっかり削除という事故も減りますね。

しかし!Controllerを継承しないということは、以前のコントローラに標準装備されていた便利メソッドが使えなくなってしまいます。 generateUrl() とか redirectToRoute() とか createNotFoundException() がないとかなり辛いです…。

何か解決策は無いかとsymfony/symfonyのソースコードをさまよった挙句、昔のControllerにあった便利メソッド群は Symfony3.3 以降では ControllerTrait というクラスにまとめられていることを発見しました。

<?php

namespace Symfony\Bundle\FrameworkBundle\Controller;

// ...

trait ControllerTrait
{
    // ...

    /**
     * Generates a URL from the given parameters.
     *
     * @see UrlGeneratorInterface
     *
     * @final since version 3.4
     */
    protected function generateUrl(string $route, array $parameters = array(), int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string
    {
        return $this->container->get('router')->generate($route, $parameters, $referenceType);
    }

    // ...
}

https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php

この ControllerTrait を使えば、Controllerクラスを継承しなくても便利メソッドが使える! …というのはぬか喜びでした。なんと、ControllerTraitのメソッドは $this->container の存在が前提になっているのです。

かと言って、ControllerTraitを使うために$containerを注入してしまうと、せっかくFrameworkBundleのControllerを継承しないメリットがほとんどなくなってしまいます。

そこで!
私が さいきょうのControllerTrait を考えたので発表したいと思います!

Controllerを継承しないコントローラでも、このtraitを使えば、従来の便利メソッドを利用し続けることができます。 どうです?最強だと思いませんか?

どうやって実現したのか

Symfony3.3から Autowiring という仕組みが取り入れられ、デフォルトでコントローラもautowiringの恩恵を受けるサービスになっています。(もちろん、不都合な場合は除外することもできます) https://symfony.com/doc/current/service_container/3.3-di-changes.html

コントローラに対するAutowiringの発動条件は

  • コンストラクタインジェクションが定義されている
  • setterインジェクションが定義されていて、かつ @required アノテーションがついている

のいずれかを満たす場合です。

さきほどの さいきょうのControllerTrait をよく見ていただくと、 @required つきsetterインジェクションをつけてあるのが見つかると思います。

Autowiringで狙ったオブジェクトを差し込むコツはいくつかあるので、 ドキュメント をよく読んで使ってみてください。

ControllerTraitについて補足

一度はFrameworkBundleにもコンテナに依存しない版ControllerTraitが入ったのですが、現在はコンテナを注入したものに再度変更されています。

最強のControllerTraitを実際に使ってみるとわかる のですが、すべての便利メソッドを使うための必要サービスをautowiringで注入するためには、デフォルトではインストールされないバンドル・コンポーネント( SecurityBundleSerializer )を入れる必要があったり、デフォルトではOFFになっている機能(セッションやCSRFチェック)を有効にする必要があります。

これでは Symfony Flex のいいところ「最低限のコンポーネントでスタートして必要なものだけ入れる」が生きないということで、コンテナ利用版に戻されたのではないかと思います。(autowiringでの注入は対象サービスが存在しないとエラーになってしまいますが、コンテナからの取得なら、事前に $container->has($serviceId) でチェックして、有効になっていないサービスは使わせないということができるからです。

ControllerTraitの便利メソッド群も、よく見るとジャンルが別のもの(HTTPエラー関係、セキュリティ関係、URL関係、DB、フォーム)が一つのtrait内に同居している状態です。 個人的には、今後、この便利メソッド群はジャンル別のtraitに分けられて、開発者は必要な時に必要なtraitを使う形に進化していくのではないかと期待しています。

まとめ

長々と書いてきましたが、何が言いたいかというと「Autowiring楽しいよ!」という一言です :smile: symfony1時代やSymfony2時代に「Symfonyは設定ファイルが多すぎて…」と敬遠してしまった開発者にも、ぜひ一度試してみてほしいです。

明日は smd8122 さんです!


このエントリーをはてなブックマークに追加

Symfony Advent Calendar 2017 6日目 の記事です。 昨日は弊社の澤井による Symfony4をインストールして”Hello World”を表示させるまでの手順 でした。

先月末、ついにSymfony3.4とSymfony4がリリースされましたね! インストール方法やディレクトリ配置もガラッと変わり、なんだか新鮮な感じがします。

さて、最新のSymfony環境で開発するときにも、テストは欠かせません。 テストと言えば機能テストを書く時のもはや定番の便利バンドル、LiipFunctionalTestBundle があります。 Symfony2.xでLiipFunctionalTestBundleを使う場合の記事は既にあるのですが、

Symfony3.4以上にはそのまま適用できず、少し設定方法が変わっていました。

そこで、 Symfony3.4 の環境でLiipFunctionalTestBundleを使って機能テストを書く方法をご紹介します。(本当はSymfony4でやりたかったのですが、 LiipFunctionalTestBundleはまだSymfony4未対応のため、泣く泣く3.4にします。)

Symfony3.4環境を作る

symfony/skeletonを使ってSymfony3.4環境を作りましょう。

$ composer create-project symfony/skeleton:"~3.4"

必要な依存ライブラリをインストール

まず、 symfony/skeleton にはphpunit/phpunitを始めとするテスト用のライブラリ群が入っていないので、機能テストを実行するのに必要なライブラリを追加します。

$ composer require simple-phpunit symfony/browser-kit:"~3.4"

次に、 LiipFunctionalTestBundleを使うのに必要なライブラリを追加します。 nelmio/alice のバージョン3.0以降とLiipFunctionalTestBundleは互換性がありませんので、 nelmio/alice のバージョン指定 ~2.0 が必要です。

$ composer require liip/functional-test-bundle nelmio/alice:"~2.0" doctrine/doctrine-fixtures-bundle hautelook/alice-bundle

prodとtestでデータベースを分けるための設定

config/packages/test/doctrine.yaml を下記の内容で作成します。

doctrine:
  dbal:
    driver: "pdo_sqlite"
    url: "sqlite:///%kernel.project_dir%/var/testdb.sqlite"

また、 hautelook/alice-bundle の設定ファイルは、デフォルトではdev用しか生成されないので、testにもコピーします。

$ cp config/packages/dev/hautelook_alice.yml config/packages/test/hautelook_alice.yml

テスト対象のエンティティとコントローラを用意

エンティティ

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Class Task
 *
 * @ORM\Entity
 * @package App\Entity
 */
class Task
{
    /**
     * @var integer
     * @ORM\Id()
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Column(type="integer")
     */
    public $id;

    /**
     * @var string
     * @ORM\Column(type="string", length=255)
     */
    public $title;
}

コントローラ

<?php

namespace App\Controller;

use App\Entity\Task;
use Doctrine\ORM\EntityManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Response;

class TaskController
{
    /**
     * @var EntityManagerInterface
     */
    private $em;

    /**
     * @param EntityManagerInterface $em
     */
    public function __construct(EntityManagerInterface $em)
    {
        $this->em = $em;
    }


    /**
     * @Route("/task/{id}")
     * @param int $id
     * @return Response
     */
    public function showAction($id)
    {
        $task = $this->em->find(Task::class, $id);

        return new Response('task: '.$task->title);
    }
}

これで準備は完了です!

機能テストを書く

早速fixtureと機能テストを書いてみましょう。

fixture

# tests/fixtures/task.yml
App\Entity\Task:
  task1:
    title: "test1"

機能テスト

<?php

// tests/Controller/TaskControllerTest.php

namespace App\Controller;

use Liip\FunctionalTestBundle\Test\WebTestCase;

class TaskControllerTest extends WebTestCase
{
    public function setUp()
    {
        parent::setUp();

        $this->loadFixtureFiles([
            __DIR__.'/../fixtures/task.yml',
        ]);
    }

    public function test()
    {
        $client = static::createClient();
        $client->request('GET', '/task/1');

        $this->assertEquals('task: test1', $client->getResponse()->getContent());
    }
}

実行結果

$ bin/phpunit
#!/usr/bin/env php
PHPUnit 6.5.2 by Sebastian Bergmann and contributors.

Testing Project Test Suite
.                                                                   1 / 1 (100%)

Time: 653 ms, Memory: 16.00MB

OK (1 test, 1 assertion)

Remaining deprecation notices (4)

The Symfony\Bundle\FrameworkBundle\Test\KernelTestCase::getPhpUnitXmlDir() method is deprecated since 3.4 and will be removed in 4.0: 1x
    1x in TaskControllerTest::setUp from App\Controller

The Symfony\Bundle\FrameworkBundle\Test\KernelTestCase::getPhpUnitCliConfigArgument() method is deprecated since 3.4 and will be removed in 4.0: 1x
    1x in TaskControllerTest::setUp from App\Controller

The "hautelook_alice.fixtures.loader" service is private, checking for its existence is deprecated since Symfony 3.2 and will fail in 4.0: 1x
    1x in TaskControllerTest::setUp from App\Controller

The "hautelook_alice.fixtures.loader" service is private, getting it from the container is deprecated since Symfony 3.2 and will fail in 4.0. You should either make the service public, or stop using the container directly and use dependency injection instead: 1x
    1x in TaskControllerTest::setUp from App\Controller

テスト通りました! LiipFunctionalTestBundle内で非推奨の警告が出てますが、fixtureを使った機能テスト自体はうまく実行できています。

まとめ

本記事で使用したサンプルコードは下記レポジトリで公開しています。
https://github.com/77web/sf3.4-functional-test-sample

ちなみに、LiipFunctionalTestBundleでは現在、 Symfony4対応のPR が作業中のようですので、いずれSymfony4でも使えるようになると思われます。 楽しみですね!


このエントリーをはてなブックマークに追加

Symfony Advent Calendar 2017 の5日目の記事です。

開発部の澤井です。新人研修でSymfony4をインストールし、Hello Worldを表示する作業を行いました。今回は、その手順を紹介します。

目次

Symfony Flex/Recipes

本記事では、各種パッケージをSymfony FlexのRecipes経由でインストールします。

Symfony Flex は、Symfonyアプリケーションを構成する際のパッケージのインストールや削除を便利にしてくれるComposerプラグインです。

Symfony Flexは、Symfony Flexサーバー に登録されているレシピ(Recipes)を使ってパッケージのインストールや削除を実行します。レシピを使うと、機能を実現するのに必要な複数の関連パッケージをまとめて管理できるので、非常に便利です。

Symfony Flexは、後述する symfony/skeleton でインストールされるパッケージに含まれているので、symfony/skeleton でセットアップしたプロジェクトでは特に意識せずに使い始めることができます。

Symfony4 セットアップ

symfony/skeletonでプロジェクトを作成

$ composer create-project symfony/skeleton test

serverレシピをインストール

$ composer req server

serverレシビは、WebServerBundleをインストールします。WebServerBundleは、Symfonyアプリケーションをビルトインサーバーで手軽に動かすための、server:runといったコマンドを提供します。

下記コマンドでサーバーを起動します。

# サーバーを起動
$ bin/console server:run

下記コマンドで、使用可能なコマンド一覧を表示できます。

bin/console list server
Symfony 4.0.0 (kernel: src, env: dev, debug: true)
#...
Available commands for the "server" namespace:
  server:log     Starts a log server that displays logs in real time
  server:run     Runs a local web server
  server:start   Starts a local web server in the background
  server:status  Outputs the status of the local web server for the given address
  server:stop    Stops the local web server that was started with the server:start command

makerレシピをインストール

$ composer req maker

makerレシピは、SymfonyMakerBundleをインストールします。SymfonyMakerBundleは、後述するコントローラーの作成など、様々な機能を提供しています。下記コマンドで、使用可能なコマンド一覧を表示できます。

$ bin/console list make
Symfony 4.0.0 (kernel: src, env: dev, debug: true)
#...
Available commands for the "make" namespace:
  make:command          Creates a new console command class
  make:controller       Creates a new controller class
  make:entity           Creates a new Doctrine entity class
  make:form             Creates a new form class
  make:functional-test  Creates a new functional test class
  make:subscriber       Creates a new event subscriber class
  make:twig-extension   Creates a new Twig extension class
  make:unit-test        Creates a new unit test class
  make:validator        Creates a new validator and constraint class
  make:voter            Creates a new security voter class

annotレシピをインストール

$ composer req annot

annotレシピは、SensioFrameworkExtraBundleをインストールします。SymfonyMakerBundleは、コントローラーを作成するとき、ルーティング定義にアノテーションを使います。アノテーションによるルーティングの制御は、SensioFrameworkExtraBundleが提供する機能です。

annotはannotationの省略形です。レシピは複数のエイリアスを持つ場合があります。アノテーションレシピのエイリアスもannotの他にannotationannotationsannotsがあり、全て同じ機能を提供します。

デバッグパックをインストール

Symfonyは、高機能なデバッグ機能をバンドルで提供しています。

本記事では、デバッグ機能を提供するバンドル群を、Symfony Recipes経由でインストールします。

$ composer req debug-pack

主に下記バンドルがインストールされます。

セットアップ参考リンク

Hello World表示

コントローラーを作成

SymfonyMakerBundleのコマンドを使いコントローラーを作成します。

$ bin/console make:controller DefaultController

上記コマンドで、src/Controller/DefaultController.phpが作成されます。今回は、Twigテンプレートを使いHello Worldを表示するので、下記のように変更します。

<?php
// src/Controller/DefaultController.php 
namespace App\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;

class DefaultController extends Controller
{
    /**
     * @Route("/default", name="default")
     */
    public function index()
    {
-       // replace this line with your own code!
-       return $this->render('@Maker/demoPage.html.twig', [ 'path' => str_replace($this->getParameter('kernel.project_dir').'/', '', __FILE__) ]);
+       return $this->render('default.html.twig',
+           ['message' => 'Hello World']
+       );
    }
}

ビューを作成

本記事では、ビューのエンジンとしてTwigを使います。

twigレシピをインストール

$ composer req twig 

twigレシピはTwigBundleをインストールします。

TwigBundleはdebug-packに含まれるため、すでにdebug-packをインストールしている場合はこの手順は不要です。

TwigBundleがインストールされていれば、下記ディレクトリとファイルが作成されているはずです。

  • templatesディレクトリ
  • templates/base.html.twigファイル

ビュー作成

今回は、templates/base.html.twigを継承したtemplates/default.html.twigというファイルを作成し、このファイルでHello Worldを表示させます。

{# templates/default.html.twig #}
{% extends "base.html.twig" %}
{% block body %}
    {{ message }}
{% endblock %}

base.html.twigは下記のようになっています。

{# templates/base.html.twig #}
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}Welcome!{% endblock %}</title>
        {% block stylesheets %}{% endblock %}
    </head>
    <body>
        {% block body %}{% endblock %}
        {% block javascripts %}{% endblock %}
    </body>
</html>

表示結果を確認

bin/console server:runコマンドでサーバーを起動します。

デフォルトの起動結果は下記のようになります。

$ bin/console server:run

 [OK] Server listening on http://127.0.0.1:8000  

ブラウザでhttp://127.0.0.1:8000/defaultへアクセスすると、Hello Worldが表示されることを確認できます。

image

最後に

今回は、Symfony4のインストールからHello Worldを表示するまでの手順を書きました。まだ、Symfony4をインストールする情報は多くありませんが、Symfony Flex/Recipesを使うと、初めに考えていたよりもシンプルにインストールを進めることができました。

この記事が、Symfony4を試してみたいと考えているみなさんのお役に立てれば嬉しいです。