はじめに
概要
Laravelでとあるサービスの開発をしていて外部のAPIに依存した機能があるときに、そのテストを書く際にどのようにテストを書くのがいいのかそしてどのような構成にしたらいいのかを考えてみます。
環境
- php: 8.0
- Laravel: 8.*
仕様
今回はTwilio というSMSや電話での二要素認証ができるAPIを使った会員登録を実装します。
事前準備
↓こちらの記事を参考にTwilioのパッケージのインストールと.envの設定をしてある状態からスタートします。
TwilioとLaravelを使った二要素認証APIの作り方
処理の流れ
以下のようなフローで実装します。
- 会員情報を入力する
- 登録した電話番号にSMSで認証コードを送信する
- 送信した認証コードを検証する(届かなければ再送可)
- 認証が成功すればログイン
この記事では2までの処理について説明致します。
会員登録処理
まずは以下のような登録処理用のコントローラーを作成します。
// 他のuse文は省略
use App\Verify\Service;
class RegisterController extends Controller
{
/**
*
* @var string
*/
protected $redirectTo = '/verify';
/**
*
* @var Service
*/
protected $verify;
/**
*
* @param Service $verify
*/
public function __construct(Service $verify)
{
$this->verify = $verify;
}
/**
* 登録画面表示
*
* @return \Illuminate\View\View
*/
public function create()
{
return view('auth.register');
}
/**
* 登録処理
*
* @param \Illuminate\Http\Request $request
* @return \App\User
*/
protected function store(Request $request)
{
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'tel' => $request->tel,
'password' => Hash::make($request->password),
]);
$this->sendSms($user);
}
/**
* SMS送信処理
*
* @param User $user
* @return \Illuminate\View\View|\Illuminate\Http\Response
*/
protected function sendSms($user)
{
$verification = $this->verify->startVerification($user->tel, $request->post('channel', 'sms'));
if (!$verification->isValid()) {
$user->delete();
$errors = new MessageBag();
foreach($verification->getErrors() as $error) {
$errors->add('verification', $error);
}
return view('auth.register')->withErrors($errors);
}
$messages = new MessageBag();
$messages->add('verification', "{$request->user()->tel}に認証コードを送信しました。");
return redirect('/verify')->with('messages', $messages);
}
}
ここでは会員登録画面の表示、登録処理、登録完了後にSMSを登録した電話番号に送信しています。 その際にsendSms処理内でDIしたTwilioのサービスクラスを用いてSMSを送信しています。
Twilioのサービスクラスについて
先程のコントローラーにDIしているTwilioのサービスクラスについて解説します。
今回は概要にもあるように外部のAPIを用いるため、事前に以下のようなインターフェイスを用意し別のAPIを利用した場合も差し替えができるようにしたいと思います。
app/Verify/Service.php
namespace App\Verify;
interface Service
{
/**
* 外部サービスを利用した電話認証プロセスの開始
*
* @param $tel
* @param $channel
*/
public function startVerification($tel, $channel);
/**
* 外部サービスによる認証コードの確認
*
* @param $tel
* @param $code
*/
public function checkVerification($tel, $code);
}
そして以下のようにこのインターフェイスに沿ったTwilioの具象クラスを実装します。
namespace App\Services\Twilio;
use App\Verify\Service;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Client;
class Verification implements Service
{
/**
* @var Client
*/
private $client;
/**
* @var string
*/
private $verification_sid;
/**
* Verification constructor.
* @param $client
* @param string|null $verification_sid
* @throws \Twilio\Exceptions\ConfigurationException
*/
public function __construct($client = null, string $verification_sid = null)
{
if ($client === null) {
$sid = config('app.twilio.account_sid');
$token = config('app.twilio.auth_token');
$client = new Client($sid, $token);
}
$this->client = $client;
// TwilioのService SID
$this->verification_sid = $verification_sid ?: config('app.twilio.verification_sid');
}
/**
* Twilio Verify V2 APIを使った電話認証プロセスの開始
*
* @param $tel
* @param $channel
* @return string sid|MessageBag
*/
public function startVerification($tel, $channel)
{
try {
// Twilioでの認証開始
$verification = $this->client->verify->v2->services($this->verification_sid)
->verifications
->create($tel, $channel);
// 成功ならsidを返却
return $verification->sid;
} catch (TwilioException $exception) {
// エラーメッセージ発行
$messages = new MessageBag();
$messages->add('verification', "認証プロセスの開始に失敗しました");
}
}
/**
* Twilio Verify V2 APIによる検証コードの確認
*
* @param $tel
* @param $code
* @return string sid|MessageBag
*/
public function checkVerification($tel, $code)
{
try {
$verification_check = $this->client->verify->v2->services($this->verification_sid)
->verificationChecks
// 認証コード発行
->create($code, ['to' => $tel]);
// 認証コードの発行に成功ならsidを返却
if($verification_check->status === 'approved') {
return new $verification_check->sid;
}
} catch (TwilioException $exception) {
// エラーメッセージ発行
$messages = new MessageBag();
$messages->add('verification', "認証に失敗しました");
}
}
}
具象クラスの実装が完了したらDIコンテナに登録をします。
app/Providers/AppServiceProvider.php
public function register()
{
$this->app->bind(
'App\Verify\Service',
'App\Services\Twilio\Verification',
);
}
これで先程のRegisterControllerのコンストラクタでServiceインターフェイス型を持ったTwilioのインスタンスを注入することができます。 そしてそのインスタンスからstartVerificationというメソッドでSMS送信を行うことができます。
app/Http/Controllers/Auth/RegisterController.php
public function __construct(Service $verify)
{
$this->verify = $verify;
}
これで登録が完了したユーザーの電話番号宛にSMSで認証コードが送信されるようになったかと思います。 次回は残りのSMS認証、認証成功後のリダイレクトところまで解説したいと思います。 テストまで行こうと思ったけどちょっと長すぎたので、この次の次の回くらいには解説します!!!!!