- 投稿日:2019-05-27T23:15:35+09:00
laravel-slack-apiを使用して、Laravelからslackへ通知する
はじめに
Laravelでは
slack-notification-channel
というslackへ通知する仕組みが存在しています。
https://laravel.com/docs/5.8/notifications#slack-notificationsしかし、
Incoming Webhook
を使用しているためfile upload等をすることができません。
今回自分はアプリケーションログをfile形式でslackへ通知したかったので、laravel-slack-apiライブラリを使用してみました。
今回は使い方の紹介となります。
※最終コミットが2016年でしたが、問題なく動きました。アプリケーションログをfile形式でslackへ通知した際のイメージです。
設定
laravel-slack-apiのreadme.mdに沿って進めています。
また、slack botの設定はこの記事の最後に手順を書いております。
インストール
composer require vluzrmos/slack-apiproviders、config等の追加
config/app.php'providers' => [ Vluzrmos\SlackApi\SlackApiServiceProvider::class, ] //... 'aliases' => [ 'SlackApi' => Vluzrmos\SlackApi\Facades\SlackApi::class, 'SlackChannel' => Vluzrmos\SlackApi\Facades\SlackChannel::class, 'SlackChat' => Vluzrmos\SlackApi\Facades\SlackChat::class, 'SlackGroup' => Vluzrmos\SlackApi\Facades\SlackGroup::class, 'SlackFile' => Vluzrmos\SlackApi\Facades\SlackFile::class, 'SlackSearch' => Vluzrmos\SlackApi\Facades\SlackSearch::class, 'SlackInstantMessage' => Vluzrmos\SlackApi\Facades\SlackInstantMessage::class, 'SlackUser' => Vluzrmos\SlackApi\Facades\SlackUser::class, 'SlackStar' => Vluzrmos\SlackApi\Facades\SlackStar::class, 'SlackUserAdmin' => Vluzrmos\SlackApi\Facades\SlackUserAdmin::class, 'SlackRealTimeMessage' => Vluzrmos\SlackApi\Facades\SlackRealTimeMessage::class, 'SlackTeam' => Vluzrmos\SlackApi\Facades\SlackTeam::class, ]config/services.php'slack' => [ 'token' => env('SLACK_TOKEN'), ]slackで発行されたトークンを記載する
.envSLACK_TOKEN='xoxb-xxxxx'使い方
$php artisan tinker
から簡単に動作確認できます。シンプルなメッセージを送る
SlackChat::message('#test', 'ピピッカチュウ');テキストをfileとして送る
SlackFile::upload([ 'filename' => 'ピーカ.txt', 'initial_comment' => 'ピカピカーチュ', 'content' => "ピーカ、チューウ!ピーカ、チューウ!ピーカ、チューウ!ピーカ、チューウ!\n", 'channels' => '#test' ]);
slackボットの設定
下記はslackボットの設定方法です。
slack Appの作成
slack apiから設定を行う。
Bot Userの作成
scopeの設定
botから投稿する場合は
chat:write:bot
を付与する。
連携許可
Bot User OAuth Access Token
Bot User OAuth Access Tokenをlaravel側のslack tokenに設定してください。
Botを対象のチャンネルに追加
- 投稿日:2019-05-27T20:20:04+09:00
Policyが紐づいていないModelに対してGate::deniesを実行したらtrueが返ってきた
環境
Laravel: 5.5
PHP: 7.2前提
同じModelでもグローバルスコープをかけたい場合とかけたくない場合で2つModelを作っていた。
グローバルスコープをかけている方はかけていない方を継承したModelになっている。
グローバルスコープをかけている方は、権限やdeleted_atの関係でかけていたのでもちろんPolicyも登録されていた。
かけていない方は、権限を気にせず参照したい場合があるという理由でかけておらずPolicyもつけていなかった。
起こったこと
URLに特定のパラメータ(hoge_id=1のような形)が入っているかでModelの持ってくる方法を変えている三項演算子があった。
$hoge = $request->has('hoge_id') ? Hoge::findOrFail($request->get('hoge_id')) : Auth::user()->fuga->hoge;この三項演算子の:の右側に入った場合に
Auth::user()
で取り出される認証系のModelがグローバルスコープをかけていない方になっており、そのリレーションなので取り出されたfugaもhogeもかけていない方(=Policyがついていない)になっていた。その場合に、Gate::deniesの第2引数に$hogeを渡すと、第1引数で指定したPolicy内で作成した関数にたどり着かずtrueで返ってきた。
Gateクラスの中身を覗いてみるとGate::deniesはGate::allowsの結果を反転させたものが返されていた。
Gate::allowsの中身は以下の通り
public function allows($ability, $arguments = []) { return $this->check($ability, $arguments); }呼び出されているcheckの中身はこんな感じ
public function check($abilities, $arguments = []) { return collect($abilities)->every(function ($ability) use ($arguments) { try { return (bool) $this->raw($ability, $arguments); } catch (AuthorizationException $e) { return false; } }); }tryの中のrawはこんな感じ(コメントアウトは見やすさのために一旦除けました)
protected function raw($ability, $arguments = []) { if (! $user = $this->resolveUser()) { return false; } $arguments = Arr::wrap($arguments); $result = $this->callBeforeCallbacks( $user, $ability, $arguments ); if (is_null($result)) { $result = $this->callAuthCallback($user, $ability, $arguments); } $this->callAfterCallbacks( $user, $ability, $arguments, $result ); return $result; }rawの中で呼ばれているresolveUser()を条件にしたif文の中のreturn false;が怪しいと踏んでresolveUser()を見に行く
こんな感じ
protected function resolveUser() { return call_user_func($this->userResolver); }ここのcall_user_funcで追いかけられなくなったので終了。
以下推測
call_user_funcのphp公式ドキュメントを見に行ったが存在しない関数名を渡した場合はどうなるかはわからず。ただ一番ありそうな気がする部分なので、call_user_funcでfalseが返り、
if (! $user = $this->resolveUser()) { return false; }のif文の中に入ったことでfalseがallowsの結果として返りdeniesがtrueになったのかなと思います。
振り返り
結局のところ型の意識が抜けた状態でプログラムを書いていたのが根本の原因かなと思うのでこういう(何らかの原因で同じModelを二つ作らざるを得ない)場合には自分が今どっちのModelを扱っているのか気にしたり後からそれが明示的にわかるような書き方をする必要があるなと学びました。
- 投稿日:2019-05-27T18:08:41+09:00
マイグレーション(migration)ファイルのupとdownとchangeについて
up
マイグレート(migrate)をした時に、実行される処理です。
down
ロールバック(rollback)した時に、実行される処理です。
ロールバックでまだ存在しないカラムを作ることも実はできる。
change
これは、upとdownの合体版です。
changeの中にテーブル構成を記述します。
migrateすると、記述を元にテーブル、カラムの作成等が行われます。rollbackは基本的にmigrate前の状態に戻してくれます。
取り消しではありません。
change一つでupとdownの両方ができるので便利です。
changeの注意点
既に存在するカラムを削除したい場合ちょっと注意です。
1, カラムを削除するためにマイグレーションファイルを作成
2, change内にテーブル名とカラム名の2つだけを記述して、migrateを実行し、カラムを削除。
3, 消すもの間違えた!ので、rollbackして今消したカラムを復活だ!
rollback!!
とするとエラーが出る可能性があります。理由はrollback時におけるカラム作成のための情報不足です。
どういうことかというと、rollbackは、基本的にmigrateの反転です。
migrateが削除なら、rollbackは作成になります。カラム作成の時にテーブル名とカラム名だけでは不十分ですよね。
なので、型も定義してあげなければいけません。
他にありがちなミス
1, マイグレーションファイルを作成する。
2, 間違えてdown()メソッドの中にカラム作成の記述をする。
3, 知らずにmigrateしテーブルを確認する。
4, テーブルにカラムがないことに気づき、rollbackする。
(この時点でテーブルにカラムが作成されるが大体気づかない。)
5, 記述を直して、再びmigrateする。
6, 記述はあっているはずなのに、既にそのカラムがあります的なエラーを吐かれて詰む。といったこともあります。
初心者の方は特に気をつけてください。
- 投稿日:2019-05-27T14:13:01+09:00
Laravel git cloneした後にすること
laravelをGit管理するときにつまずいたこ
コマンドプロンプトで少し操作しないと動かない
copy .env.sample .env composer install php artisan key:generate以下のように
composer update
コマンドを打つ場合は
composer.jsonファイルを書きかえた時だけ。実際の手順は
作業者A composer.json修正
作業者A composer update
作業者A GITにプッシュ->マージ
他の作業者 GITからプル
他の作業者 composer install
- 投稿日:2019-05-27T08:59:22+09:00
swagger-uiでAPIを叩いて結果をjsonで見る
ローカルで開発中にUI上からAPIを実行して結果をjson形式で見たいと思ってLaravelプロジェクトで実現した件をまとめておきます。
Laravelは5.5を使っています。最終的にはこういう事ができます
必要な作業
必要な対応は下記の通りです。
- swagger-uiの環境を作る
- swagger-phpを使ってopenapi.yamlを自動生成できるようにする
- Cross Originなリクエストをswagger-uiから実行可能にする
- swagger-uiからのリクエストの時はレスポンスをjsonで返す
- swagger-uiからのリクエストの時は認証を無効にする
- swagger-uiからのPOSTリクエスト時のトークンチェックを無効にする
swagger-uiの環境を作る
swager-ui用の階層をプロジェクトのrootに作って下記のファイルを作ります。
ポートは衝突しない何かしらを選んで指定します。docker-compose.ymlswagger-ui: image: swaggerapi/swagger-ui container_name: "swagger-ui" ports: - "8001:8080" volumes: - ./openapi.yaml:/openapi.yaml environment: SWAGGER_JSON: /openapi.yamlSWAGGER_JSON という環境変数で、apiを定義するファイルを指定します。
openapi.yamlは同階層に作成し、マウントしておきます。swagger-phpを使ってopenapi.yamlを自動生成できるようにする
openapi.yamlを手で書くこともできますが、コントローラに記述したPHPDocから自動生成するツールがあるのでそれを使います。どの道何かしらを追記する必要がありますが、編集対象をphpファイルに統一できるのでこれを使います。
下記の手順でパッケージを追加します。
composer require zircote/swagger-phpこれで生成コマンドが使えるようになるので、Makefileに生成コマンドを作っておきます。
実行するとプロジェクト内のPHPDocを見てopenapi.yamlが生成されます。build: ## openapiの定義ファイルを更新する ./vendor/bin/openapi app -o ./swagger-ui/swagger-phpの記法については下記URLを参考にします。
http://zircote.com/swagger-phpGETリクエストの例
/** * @OA\Get( * path="/sample", * tags={"SampleController"}, * @OA\Parameter(name="some_id", * in="query", * description="何かしらのid", * @OA\Schema(type="integer") * ), * @OA\Response( * response="200", * description="レスポンス内容の説明" * ) * ) */POSTリクエストの例
/** * @OA\Post( * path="/sample/register", * tags={"SampleController"}, * description="何かしらの登録処理", * @OA\RequestBody( * required=true, * @OA\MediaType( * mediaType="application/x-www-form-urlencoded", * @OA\Schema( * type="object", * @OA\Property( * property="name", * description="登録する何かの名前", * type="string" * ), * @OA\Property( * property="user_id", * description="ユーザid", * type="integer" * ) * ) * ) * ), * @OA\Response( * response="200", * description="レスポンス内容の説明", * ) * ) */Cross Originなリクエストをswagger-uiから実行可能にする
異なるhostからのリクエストはデフォルトでは禁じられているため、ローカルでかつswagger-uiからのアクセスのみ許可する設定をします。
下記のページにあるように、許可するhostを指定する必要があります。
https://fetch.spec.whatwg.org/#http-cors-protocolMiddlewareとして下記のクラスを追加します。
Cors.php<?php namespace App\Http\Middleware; use Closure; class Cors { // from swagger-ui const ACCESS_ALLOW_ORIGIN = 'http://localhost:8001'; /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { $response = $next($request); if (app()->isLocal()) { $response ->header("Access-Control-Allow-Origin" , self::ACCESS_ALLOW_ORIGIN) ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); } return $response; } }swagger-uiからのリクエストの時はレスポンスをjsonで返す
これでswagger-uiでAPIの一覧を見れるようになりました。
ただし、このままだとレスポンスがhtmlになってしまうので、形式をjsonに変更します。LaravelはデフォルトでBaseControllerを継承していますが、その間に一つ中間クラスを作ってそこに共通処理を書いていきます。
まずはswagger-uiからのリクエスト判定です。
ローカルからのアクセスであることと、headerの中のアクセスoriginを取得して判定します。use Illuminate\Routing\Controller as BaseController; use App\Http\Middleware\Cors; class Controller extends BaseController { protected function isSwagger() { return app()->isLocal() && request()->headers->get('origin') === Cors::ACCESS_ALLOW_ORIGIN; }次にこれを使ってresponseの形式を変換します。
実装に手を入れることになり嫌な気持ちがありますが、responseをviewのパラメータを渡す処理をラップする関数を作り、Controllerの記述を書き換えます。protected function response($view, $response) { if ($this->isSwagger()) { return response()->json($response); } return view($view)->with($response); }これでswagger-uiからのアクセス時にのみresponse形式をjsonにすることができました。
他にうまい方法があればそうしたいところ。swagger-uiからのリクエストの時は認証を無効にする
何かしらの仕組みで認証を使っている場合は無効にしないとswagger-ui上から突破するのは困難です。
これは認証の使い方次第ですが、標準搭載されているMiddlewareを使っている場合は下記のように対応することで無効化できます。public function __construct() { if (!$this->isSwagger()) { $this->middleware('auth')->only(['getUpdate', 'postRegister', 'postUpdate']); } }swagger-uiからのPOSTリクエスト時のトークンチェックを無効にする
ここまででGETリクエスウトについては動くようになりますが、まだPOSTリクエスト時にのCSRFトークンのチェックに引っかかります。
これもswagger-ui上から適切なtokenを送るのは困難であるため無効化します。
VerifyCsrfToken.phpというファイルがあるので、handleをオーバーライドし、判定条件を変えます。
UnitTest時に無効にしてるのと挙動としては近いですね。isSwaggerと同じ条件を書いてしまっているのは良くないですが、雑に作るとこうなります。
VerifyCsrfToken.phpuse Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware; class VerifyCsrfToken extends Middleware { /** * @return bool */ protected function runningSwaggerUi() { return app()->isLocal() && request()->headers->get('origin') === Cors::ACCESS_ALLOW_ORIGIN; } /** * @param \Illuminate\Http\Request $request * @param Closure $next * @return mixed|\Symfony\Component\HttpFoundation\Response * @throws TokenMismatchException */ public function handle($request, Closure $next) { if ( $this->runningSwaggerUi() || $this->isReading($request) || $this->runningUnitTests() || $this->inExceptArray($request) || $this->tokensMatch($request) ) { return $this->addCookieToResponse($request, $next($request)); } throw new TokenMismatchException; }最後に
判定処理が共通化できてないのと、Corsクラスの定数に依存してる辺りはなんとかしたいところです。その辺りは次の課題ということで。いい方法知ってる人がいたら教えてほしいです。
とりあえずswagger-uiからAPIを実行してjsonで結果を見る環境はこれでできました。