Posted at

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

「ServiceにFactory Methodで生成したオブジェクト刺せたらいいなー」と調べたらできたのでメモ。

例として、Guzzleを利用し、Qiitaのプロフィール取得APIにアクセスして取得したユーザプロフィールを表示するものを作成しました。


まずはFactoryを作る。

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


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を作ります。


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オブジェクトを注入します。


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。


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

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

今回のサンプルはこちらに公開しています。

https://github.com/Ippey/factory-injection-test