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

こんにちは。下田です。

Symfonyで開発していると必ず触ることになる、services.ymlについて。
最近ですが、このservices.ymlに記述するだけで実現できてしまう機能が意外とたくさんあることを知りました。
今回はそれらの機能について、どんなものがあるのか調べてみました。

(以下、Symfony4.2での記述です)

services.ymlでできること

インジェクションいろいろ

まずは基本、 あるオブジェクトを他のオブジェクトに注入したい時に書く方法です。

コンストラクタインジェクション

基本形の argumentsです。
コンストラクタ引数の変数名をkey、実際に渡すオブジェクトをvalueに書きます。

services:
    App\Service\MessageGenerator:
        arguments:
            $logger: '@monolog.logger.request'

セッターインジェクション

コンストラクタではなく、setterメソッドを使ってオブジェクトを注入する時のyamlの書き方です。
callsを使うと任意のsetterを呼ぶことができます。
(※実際、setterではなくとも任意の関数をcallできるようです。用途がぱっと思いつきませんが:thinking:

services:
    App\Service\MessageGenerator:
        calls:
            - method: setLogger
              arguments:
                  - '@logger'

省略形で以下のようにも書くことができます。

services:
    App\Service\MessageGenerator:
        calls:
            - [setLogger, ['@logger']]

参考:https://symfony.com/doc/current/service_container/calls.html

また引数に@?を使うと、 注入したいオブジェクトがcontainerにない場合は無視する という書き方もできます。

services:
    App\Service\MessageGenerator:
        calls:
            - [setLogger, ['@?logger']]

上記の例では、 @loggerのインスタンスが存在しない場合はそもそも setLogger()がcallされません。

参考:https://symfony.com/doc/current/service_container/optional_dependencies.html#ignoring-missing-dependencies

@?でオプショナルにできる機能はついさっき先輩に教えてもらいました。多謝:pray:

タグによる自動注入

symfonyの開発を進めていると tags: ['twig.extension'] とか tags: [{name: form.type }] などなど、tags:を書くことがあるかと思います。

あれは特定タグのつけられたオブジェクトたちを、まとめて別のオブジェクトに注入する働きがあります。

(例えば、tags:['form.type']を付けられたものは、ここらへんでまとめて注入処理をしているようです)
参考:https://symfony.com/doc/current/service_container/tags.html

また、symfonyに最初から用意されているタグ以外にも、オリジナルのタグを作って好きなオブジェクトにまとめて注入することもできます!

services:
  app_sample.foo:
    class: App\Sample\SampleFoo

  app_sample.bar:
    class: App\Sample\SampleBar

  # ...

  App\SampleResolver:
    class: App\SampleResolver
    calls:
      - ["addSample", ["@app_sample.foo"]]
      - ["addSample", ["@app_sample.bar"]]
      - ["addSample", ["@app_sample.baz"]]
      - ["addSample", ["@app_sample.foo2"]]
      - ["addSample", ["@app_sample.foo3"]]
      # ...
      - ["addSample", ["@app_sample.foo100"]]

これが

# config/services.yaml
services:
  app_sample.foo:
    class: App\Sample\SampleFoo
    tags:
      - "myapp.sample"

  app_sample.bar:
    class: App\Sample\SampleBar
    tags:
      - "myapp.sample"

  # ...

  App\SampleResolver:
    class: App\SampleResolver
    # 大量のcallsが減ってスッキリ!

こう書けます。

具体的な方法は弊社記事に詳しい説明があります。
前述のcalls:をたくさん書かなければいけない場面に遭遇したときなどにオススメです :smiley:

遅延読み込み

実際にはめったに使われないのに、他のインスタンスの初期化に必要なため毎回インスタンス化しなければならず、しかもインスタンス化すると重くて困るようなオブジェクトやサービス(例えば、処理失敗の時だけ必要なloggerや、外部APIクライアント等)に遭遇したことはありませんか?

そういったオブジェクトには lazy: trueを付けることで、そのオブジェクトが実際に必要になるまでインスタンス化を遅らせることができます。

依存関係が複雑になってくるとインスタンスの生成順序なども問題になってきますが、そんな時でもlazy:オプションで実際のインスタンス生成を後回しにすることで解決できるかも知れません。
参考:https://symfony.com/doc/current/service_container/lazy_services.html

Factoryパターン

あるクラスを直接インスタンス化せず、ファクトリを利用してオブジェクトを生成したいときがあります。(同種のインスタンスがたくさん必要なときや、インスタンス生成手順に複雑な処理が必要なときなど)

そんなときはファクトリクラスを作ると思うのですが、services.ymlでもファクトリクラス名とメソッド名を記述することで、ファクトリを使ったインスタンス生成を利用できます。 factory: を使います。

例えばこんな感じでファクトリクラスを作った場合、

class NewsletterManagerStaticFactory
{
    public static function createNewsletterManager()
    {
        $newsletterManager = new NewsletterManager();
        // ...
        return $newsletterManager;
    }
}

services.ymlの記述はこんな形になります。 keyに生成したいクラス名を書き、生成に使うファクトリクラス名とメソッド名を factory:に記述します。

services:
    App\Email\NewsletterManager:
        factory: 'App\Email\NewsletterManagerFactory:createNewsletterManager'
        # 配列でも書けるが、古い書き方
        # factory: ['App\Email\NewsletterManagerStaticFactory', createNewsletterManager]

上記はファクトリメソッドがstaticの場合ですが、そうでない場合でもファクトリクラス自体を先にインスタンス化しておけば普通に使えます。

services:
    # 先にファクトリをインスタンス化して、サービス登録しておく
    App\Email\NewsletterManagerFactory: ~

    App\Email\NewsletterManager:
        factory: 'App\Email\NewsletterManagerFactory:createNewsletterManager'

参考:https://symfony.com/doc/current/service_container/factories.html

まとめ

以上、個人的に便利でスゴい!と思ったものや、実際に使っているものを羅列してみました。
services.ymlの記述を工夫するだけで実現できてしまうことが思いの外多くて驚きでした。

また今回、それらの機能について調べることで、背後にある有用な設計パターンについてもたくさん知ることができました。
設計の引き出しをたくさん増やしておけば、「何だかうまくいかないな…」と思った時でも、ひらめきが生まれるかもしれません :bulb:

今回紹介した機能は公式のサービスコンテナのドキュメントにまとまっていますので、皆様も是非services.ymlについて調べてみて下さい!
https://symfony.com/doc/current/service_container.html#learn-more


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

2019/03/29〜3日間に渡って行われている PHPerKaigi 2019 にて、「設計力を上げる!バリエーションの見極め術」を発表しました。

https://speakerdeck.com/77web/she-ji-li-woshang-geru-bariesiyonfalsejian-ji-meshu

日々コードレビューや同僚との設計相談をしている中で、私が着目しているif〜else〜で良いときとダメなときの見極め方をうまく説明したいなぁ、と思っていたことをまとめたものです。
発表中に「なぜこれが良い例なのか、詳細は去年の後藤さんのトークのスライドを見てください」とご紹介したスライドは PHPerKaigi 2018 ベストトーク賞「SOLIDの原則ってどんなふうに使うの?」スライド公開・配布 にあります。 TrackAが満席になり、立って聞いてくださった方もいて、本当にありがとうございました!

イベントはあと1日あるので、いろいろなトークを聞いて、普段めったに会えない関東や他地方のエンジニアとも話して、目一杯楽しんで帰りたいと思います。
会場で見かけた際はお気軽に声をかけてください。


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

こんにちは、CTOの @ttskch です。

株式会社カルテットコミュニケーションズは、本日から開催されるPHPerKaigi 2019に、シルバースポンサーとして協賛いたします。

https://phperkaigi.jp/2019/
日時:2019年3月29日(金)〜31日(日)
会場:練馬区立区民・産業プラザ Coconeriホール

カルテットが開発・運営している Lisket(リスケット) はバックエンドの大部分がPHPで作られており、弊社開発部にはPHPのユーザーコミュニティでの出会いがきっかけで入社してくれたスタッフもたくさんいます。

カルテットはPHPerの皆さんを応援しています。一緒に盛り上がっていきましょう!:raised_hands:

お知らせ1:弊社スタッフが登壇いたします

3/30(土) 14:15〜14:30、Track A にて弊社開発部の @77web「設計力を上げるバリエーションの見極め術」 という題目で登壇させていただきます。いい話なのでぜひご聴講ください!(笑)

お知らせ2:会場で「条件分岐禁止ギプス」を配布します

今回カルテットは、来場者の方へ以下のような紙製バンド、通称 「条件分岐禁止ギプス」 を配布いたします:+1:

image

4色のうちランダムに1色が封入されています

さらに、同封のチラシに記載されている手順にしたがって、会場にいる @77web または @ttskch を捕まえていただければ、漏れなくシリコン製の条件分岐禁止ギプスを無料で差し上げます!:raised_hands:

image

ぜひゲットして周りに自慢しちゃってください!:joy:

「PHPerチャレンジ」企画

ところで、今回のPHPerKaigiでは期間中の3日間 「PHPerチャレンジ」 という企画が開催されるようです:sparkles:

PHPerチャレンジとは…

PHPerチャレンジは会場内外に隠された「PHPerトークン」を探しだし、イベントサイトに入力して得られたスコアを競う企画です。 PHPerトークンは「記号の# + 任意の文字列」の形をしています。

カルテットでもPHPerトークンをご用意しました。コチラになります!

#条件分岐禁止

それでは、皆さんと会場でお会いできるのを楽しみにしています!