SymfonyでServiceにFactory Methodで生成したオブジェクトを注入する

「ServiceにFactory Methodで生成したオブジェクト刺せたらいいなー」と調べたらできたのでメモ。
例として、[Guzzle](https://github.com/guzzle/guzzle)を利用し、Qiitaの[プロフィール取得API](https://qiita.com/api/v2/docs#get-apiv2usersuser_id)にアクセスして取得したユーザプロフィールを表示するものを作成しました。

## まずはFactoryを作る。
GuzzleのClientを生成するFactoryを作ります。

```php:src/Factory/HttpClientFactory
<?php

namespace App\Factory;

use GuzzleHttp\Client;

class HttpClientFactory
{
    public static function create($baseUri)
    {
        $httpClient = new Client(['base_uri' => $baseUri, 'timeout' => 2]);
        return $httpClient;
    }
}
```

``` HttpClientFactory::create('http://localhost:8080'); ``` のような形でbase_uriを指定したClientオブジェクトを返します。

## Serviceを作る
Qiitaから指定したユーザIDのプロフィールを取得するServiceを作ります。

```php:src/Service/QiitaService.php
<?php

namespace App\Service;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;

class QiitaService
{
    private $client;
    public function __construct(Client $client)
    {
        $this->client = $client;
    }

    public function getUser($userId)
    {
        try {
            $response = $this->client->get('api/v2/users/' . $userId);
            return json_decode((string) $response->getBody(), true);
        } catch (ClientException $e) {
            return null;
        }
    }
}
```

コンストラクタでClientのオブジェクトを引数としています。このオブジェクトを利用してQiitaのAPIにアクセスしますが、get()で呼び出しているURIはClientオブジェクトにbase_uriが指定されていないと正しく動作しません。では、このServiceにFactoryで生成したClientオブジェクトを注入してみましょう。

## Service Containerの設定をする
Service Containerを設定してServiceにClientオブジェクトを注入します。

```diff:config/services.yaml
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.

# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.

    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
        resource: '../src/*'
        exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'

    # controllers are imported separately to make sure services can be injected
    # as action arguments even if you don't extend any base controller class
    App\Controller\:
        resource: '../src/Controller'
        tags: ['controller.service_arguments']

    # add more service definitions when explicit configuration is needed
    # please note that last definitions always *replace* previous ones
+
+    qiitaClient:
+        class: GuzzleHttp\Client
+        factory: ['App\Factory\HttpClientFactory', 'create']
+        arguments:
+            $baseUri: 'https://qiita.com'
+
+    App\Service\QiitaService:
+        arguments:
+            $client: '@qiitaClient'
```

まず、qiitaClientでQiita用のClientを生成する設定を行います。 ```factory: ['App\Factory\HttpClientFactory', 'create']```を指定することで、HttpClientFactoryのcreate()メソッドを呼び出してくれます。argumentsにはcreate()メソッドの引数を指定します。ここで```https://qiita.com```を指定することで、Qiita用のClientオブジェクトを生成できます。

続いてQiitaServiceのargumentsに@qiitaClientを指定し、Qiita用のClientオブジェクトを注入します。これで完成です。さいごにControllerでこのServiceを使って、プロフィールを表示させます。

## ControllerでServiceを使う
ControllerへはautowiringでServiceが注入されるので、こんな感じでOK。

```php:src/Controller/UserController.php
<?php

namespace App\Controller;

use App\Service\QiitaService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class UserController extends AbstractController
{
    /** @var QiitaService  */
    private $qiitaService;
    
    public function __construct(QiitaService $qiitaService)
    {
        $this->qiitaService = $qiitaService;
    }

    /**
     * @param $userId
     * @return Response
     * @Route("/user/{userId}", name="user")
     */
    public function index($userId)
    {
        $user = $this->qiitaService->getUser($userId);
        return $this->render('user/index.html.twig', ['user' => $user]);
    }
}
```

で、実行した結果がこちら。

http://localhost:8000/user/ippey_s
![result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9882/8ec7b5fe-3370-b530-a97c-d5fd7973f66c.png)

バッチリQiitaからプロフィール情報を取得できました！

今回のサンプルはこちらに公開しています。
https://github.com/Ippey/factory-injection-test