- 投稿日: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-27T22:05:36+09:00
スネーク?キャメル?なんちゃらケースのまとめ
株式会社オズビジョンのユッコ (@terra_yucco) です。
外部の API なども使いながらプログラミングをしていると、同じ単語でも色々な表現方法に行き当たります。例えば私の所属している会社オズビジョンの提供サービスの一つ、ハピタスコミック。
サービスとしての正式表記はきちんとありますが、以下のような様々な表記が可能です。
- HapitasComic
- hapitasComic
- HAPITAS_COMIC
- hapitas_comic
- hapitas-comic
このどの表記も見かけるケースがあるので、それぞれなんと呼ぶのかを調べてみました。
ケース一覧
前提
プログラミング言語では空白を命令の区切りとして扱うものが多いため、英語では
hapitas comic
などと空白で表現できる言葉を、空白に変わる何かで接続して表現する必要がある。
区切り方法は大きく分けて以下の 3 パターン。
- 大文字小文字の違い
- アンダースコア
_
- ハイフン
-
ここに大文字小文字のバリエーションなどでいくつかのケースが生まれます。
アッパーキャメルケース / Upper camel case
HapitasComic
- PHP
- クラス名は Upper camel case (PSR-1)
- 別名
- パスカルケース / Pascal case (Pascal で使われていた)
ロウワーキャメルケース / Lower camel case
hapitasComic
- PHP
- メソッド名は Lower camel case (PSR-1)
- 別名
- 単に camel case の場合 Lower camel case であることが多い
アッパースネークケース / Upper snake case
HAPITAS_COMIC
- PHP
- クラス定数は Upper snake case (PSR-1)
- 別名
- コンスタントケース / Constant case (定数に使うことが多いからか?)
ロウワースネークケース / Lower snake case
hapitas_comic
- PHP
- 組み込み関数はこれ
preg_match
str_replace
etc.- PSR にはこれを使わなければいけない規定はないが、一部 FW (FuelPHP など) では規約にしているケースあり
- 別名
- 単に snake case の場合 Lower snake case であることが多い
ケバブケース / Kebab case
hapitas-comic
- PHP
-
は減算を表現する演算子のため使用できない- CSS 界隈でよく見かける
- 別名
- チェインケース / Chain case (鎖のように見える)
アッパーケバブケースってないの?
ここまでは普段よく見かける組み合わせでしたが、ケバブケースの大文字版ってないんだろうかとふと気になって調べてみたところ。
Kotlin 用の extension ですがUPPER-DASH-CASE
のような文字列は以下の呼び方をするようです。
- Upper dash case
- Upper hyphen case
- Upper kebab case
また jagaapple を見ると、以下のような呼び名もあります。
- Train case
なので、厳密にいえば Kebab case
hapitas-comic
は Lower kebab case なのかもしれません。気になる点
jagaapple では Upper snake case が
HAPITAS_COMIC
ではなくHapitas_Comic
と定義されています。
Upper camel case に業務で出会ったら、どちらを意図しているのかきちんと確認したほうが幸せになれそう。Conclusion
業務中によく見かけて気になっていたケースについてまとめました。
区切り文字 大文字 ケース名 大文字小文字 単語区切り Upper camel
(Pascal Case)大文字小文字 単語区切り
ただし最初は小文字Lower camel
(Camel case)アンダースコア 無し Snake case アンダースコア 有り Upper snake case
(Constant case)ハイフン 無し Kebab case
(Dash case)ハイフン 有り Upper kebab case PHP では使用しない (できない) ものもありますが、適切に使い分けてより可読性高く規約に沿ったコードを書けるようになればと思います!
- 投稿日:2019-05-27T20:00:11+09:00
【PHP7.4】__toString()が例外を吐けるようになる
プロパティの設定を必須にしたかったとしましょう。
class HOGE{ public $var; public function __toString(){ if(!$this->var){ throw new \Exception('$var must set.'); } return sprintf('$var is %1$s', $this->var); } } try{ echo new HOGE(); }catch(\Exception $e){ var_dump($e); }何の変哲も無いように見えるコードですが、これ動きません。
Fatal error: Method HOGE::__toString() must not throw an exception, caught Exception
というFatalエラーを吐いて死にます。実は
__toString()
メソッド内では例外を出すことができないのです。
他のあらゆるマジックメソッドにはこんな制限はないのに、__toString()
だけそういうことになっています。
よくわかりませんね。しかしまあ、そんなよくわからない制限なら要らないよね、ということでAllow throwing exceptions from __toStringのRFCが提出されました。
PHP RFC: Allow throwing exceptions from __toString()
Introduction
__toString()
からの例外スローは禁止されており、致命的エラーになります。
これは__toString()
から任意のコードを呼び出すことを困難にし、一般的なAPIとして使用することが難しくなります。
このRFCは、この制限を取り除くことを目的としています。現在のような動作になっている理由は、PHPエンジンと標準ライブラリのあらゆるところで文字列変換が行われており、そして全ての箇所が例外を正しく処理できるようになっているわけではないからです。
技術的観点からすると、この制限は無駄でしかありません。
なぜならば、文字列変換中の例外は、回復可能なエラーを例外に変換するエラーハンドラによって引き起こされる可能性があるからです。set_error_handler(function() { throw new Exception(); }); try { (string) new stdClass; } catch (Exception $e) { echo "(string) threw an exception...\n"; }実際、Symfonyは現在の制限を回避するためにこの抜け穴を使用しています。
残念ながら、これはPHP8で削除予定の$errcontextパラメータに依存しています。Proposal
__toString()
からの例外スローを許可します。
もう致命的なエラーは発生しません。さらにPHP7のエラーポリシーに基づいて、
could not be converted to string
および__toString() must return a string value
というrecoverable fatal errorを、適切なError exceptionに変更します。Extension Guidelines
エクステンション開発者は、文字列変換からの例外を適切に処理するため、以下のガイドラインを考慮に入れてください。
・
zval_get_string()
、convert_to_string()
および類似の関数は、例外を生成した場合でも文字列を生成します。この文字列を含めてください。必ずしも従う必要はありませんが、そうすることができます。
・文字列変換がエラーハンドラによって例外になった場合の結果は、オブジェクトから文字列への変換の場合は空文字列、配列の場合は"Array"という文字列になります。これは以前と同じ動作です。
・通常は(EG(exception))
で例外がスローされたかどうかを確認すれば十分です。zend_string *str = zval_get_string(val); if (EG(exception)) { // リソース解放など return; }・失敗する可能性のある操作を行うヘルパーAPIが多数追加されます。
// zval_get_string()に似ているが、変換に失敗したらNULLを返す zend_string *str = zval_try_get_string(val); if (!str) { // リソース解放とか return; } // Main code. zend_string_release(str); // zval_get_tmp_string()に似ているが、変換に失敗したらNULLを返す zend_string *tmp, *str = zval_try_get_tmp_string(val, &tmp); if (!str) { // リソース解放とか return; } // Main code. zend_tmp_string_release(tmp); // convert_to_string()に似ているが、変換に成功したか否かのbooleanを返す if (!try_convert_to_string(val)) { // リソース解放とか return; } // Main code.・
try_convert_to_string()
は、変換失敗時には元の値を返しません。そのため、これを使った方が、convert_to_string()
と例外チェックを使うより安全です。
・あらゆる文字列変換操作にチェックを行うことができますが、チェックを省略しても通常は余剰な警告が発生するだけです。気をつけて実装しなければならないのは、主にデータベースのような、永続的な構造を変更する操作です。Backward Incompatible Changes
recoverable fatal error
からError exception
への変更は、破壊的変更です。Vote
投票開始は2019/05/22、終了が2019/06/05、投票数の2/3+1の賛成票で受理されます。
2019/05/27現在、賛成32反対0で、ほぼ確実に導入決定です。
感想
これによって、あらゆるメソッドから例外を出すことができるようになりました。
さて、なんでこんな簡単そうな変更が今まで残っていたの?
かというと、変更が簡単ではないからです。最初に報告があったのはなんと2011年1月であり、2012年にはあのNikitaが実装が大変なんじゃよーと言っています。
しかし結局全てのcommitがNikita自身の手によって行われました。
最終的な修正は111ファイル1000行以上に及びます。
どんだけ働いてんだこの人。PHPフォーラムは基本的に保守的な人が多く、破壊的な変更はあまり受理されない傾向にあるのですが、Nikitaについては既に、Nikitaが言うなら仕方ないというレベルに達しているようです。
三項演算子のDeprecateとか、他の人が提案してたら絶対通らなかったよね。しかしこれ、てっきりuninitializedを
__toString()
したときのために作ったものなのかと思ったのですが、特に関係なかったみたいですね。そして修正規模のわりに、あまり重要な使いどころが思いつかないというか、そもそも個人的には
__toString()
自体まず使うことがないですね。
デバッグにはvar_dump()
という超絶便利関数がありますし、文字列化したいなら適当にメソッド生やします。
- 投稿日:2019-05-27T15:05:59+09:00
Emacs PHP Modeユーザーマニュアル (Draft)
Emacs PHP Mode(
php-mode
)はEmacsでのPHPスクリプトの編集のためのメジャーモードです。あと、この記事は書きかけです。
インストール方法
php-mode
は開発の歴史的経緯から、Emacs本体に標準添付されていません。しかし、標準のパッケージ機能を使ってMELPAから簡単にインストールできます。package / use-package を使ったインストール
~/.emacs.d/init.el
に以下のような記述(どちらか、または両方)があることを確認してください。init.el(add-to-list 'package-archives '("melpa-stable" . "https://stable.melpa.org/packages/") t) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)その上で、use-packageが導入済みであればインストールは簡単です。
init.el(use-package php-mode :ensure t)Emacsで
init.el
を編集しているなら、(use-package php-mode :ensure t)
の直後にカーソルを移動してキーボードでC-x C-e
(control+e control+xを順に押す)と、その場でPHP Modeのインストールが始まります。packageまたはuse-packageでインストールしたパッケージは、通常
~/.emacs.d/elpa/
に配置されます。もし.emacs.d
ディレクトリをGitで管理しているなら、このファイルは.gitignore
で管理の対象外にすることを推奨します。Lispパッケージは継続的な更新によって改善されているので、よりよいユーザー体験を求めるにはインストールよりも定期的にアップデートすることが重要です。Emacs標準のパッケージ機能またはuse-packageでインストールした場合、以下の手順でパッケージを更新できます。
M-x list-packages
でパッケージ一覧を表示する- このリスト上でキーボードの
U
(Shift + u)を押して、更新されたパッケージをマークする- 続いてキーボードの
x
を押して更新を実行する- Emacsを再起動する
MELPA版のPHP Modeには実験的な実装が含まれていることがあります。より安定性を求める場合は
:pin melpa-stable
を設定することで、正式なバージョンリリース版がインストールされるようになります。init.el(use-package php-mode :ensure t :pin melpa-stable)手動インストール
emacs-php/php-modeを
git clone
するか、Releases · emacs-php/php-modeからzipまたはtarをダウンロードして展開してください。展開したあとmake
コマンドを実行すると、Lispファイルのバイトコンパイルとphp-mode-autoloads.el
が生成されます。init.el(when (file-directory-p "~/path/to/php-mode") (load "~/path/to/php-mode/php-mode-autoloads.el")) ;; 通常、以下のコードはどれも書く必要がありません ;; (require 'php-mode) ;; (add-to-list 'load-path (expand-file-name "~/path/to/php-mode")) ;; (add-to-list 'auto-mode-alist '("\\.php\\'" . php-mode))OSのパッケージシステムからのインストール
Debian 10(Buster)またはUbuntu 19.04(Disco Dingo)以降では、
apt install elpa-php-mode
でインストールすることができます。しかしこれらのパッケージは少々古いため注意が必要です。Emacsの個人設定を持ち込めないサーバー運用作業での一時的な利用などには利用できますが、それ以外の環境ではEmacsのパッケージ機能などで最新バージョンの利用を強く推奨します。以前のDebianおよびUbuntuにはPHP Modeは
php-elisp
というパッケージ名で収録されていました。しかしこのパッケージは2014年から更新されていなかったため問題が多く、強く非推奨です。関連パッケージの導入
- editorconfig-emacs - プロジェクト単位でのインデント設定はEditorConfigでの設定を推奨
- exec-path-from-shell - シェルから環境変数を引き継ぐ
- Flycheck - 編集中のファイルの構文エラーなどをオンザフライでチェックする
- flycheck-phpstan - PHPStanを使った静的解析
- composer.el - EmacsからComposerを操作するインターフェイス
PHPStanを適切に設定することで、PhpStormなどの商用製品を上回る精度のコーディング時エラーチェックが可能になります。PHPで開発が捗るリアルタイムエラーチェックに書きました。
カスタマイズ
設定例
init.el(defun my-php-mode-setup () "My PHP-mode hook." (subword-mode 1) (setq show-trailing-whitespace t) (setq-local page-delimiter "\\_<\\(class\\|function\\|namespace\\)\\_>.+$") (require 'flycheck-phpstan) (flycheck-mode t) (add-to-list 'flycheck-disabled-checkers 'php-phpmd) (add-to-list 'flycheck-disabled-checkers 'php-phpcs)) (use-package php-mode :hook ((php-mode . my-php-mode-setup)) :custom (php-manual-url 'ja) (php-mode-coding-style 'psr2) (php-mode-template-compatibility nil) :config (bind-key "[" (smartchr "[]" "array()" "[[]]") php-mode-map) (bind-key "]" (smartchr "array " "]" "]]") php-mode-map) ;; (bind-key "C-}" 'cedit-barf php-mode-map) ;; (bind-key "C-)" 'cedit-slurp php-mode-map) (bind-key "C-c C-c" 'psysh-eval-region php-mode-map) (bind-key "<f6>" 'phpunit-current-project php-mode-map) (bind-key "C-c C--" 'php-current-class php-mode-map) (bind-key "C-c C-=" 'php-current-namespace php-mode-map))
- 投稿日: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で結果を見る環境はこれでできました。
- 投稿日:2019-05-27T00:07:15+09:00
WordPressプラグイン作成の勉強をしてみた
wordpress初心者です。サンプルは調べながら作成しました、間違いなどありましたら指摘お願いします
この記事の編集履歴
参考
- 0からわかるWordPressプラグイン開発(スッテプ・バイ・ステップ) - Qiita
- WordPress Plugin Development - YouTube
- WordPress で Ajaxを利用する | バシャログ。
- jsファイルでWordPressのテンプレートパスを使えるようにする | TECH | 孤独村
- plugins - why does the add_action('the_content) overwrite my page - WordPress Development Stack Exchange
サンプル
Server-Sent Events(SSE)を使用した簡易チャットプラグインを作成してみました
確認バージョン
- PHP 7.2
- wordpress 5.2.1
- Google Chrome 74.0.3729.169
この記事の概要
wordpressの基本的な機能を勉強しながら、簡易チャットプラグインを作成していきます
最終的なコードを見たい方はこちらを見て、本記事は読まなくて良いです。
私が調べながら作っていった手順を一歩ずつ書いていく内容となっています。
間違いやより良いやり方がある場合は教えてください。対象読者
wordpressのplugin作成に壁を感じてる方
- 初心者の私でもやればできたというのが伝わればと、
wordpress初心者 (wordpressを画面から操作したことがあり、PHPの基礎的な知識がある)
- plugin作成はそこまで難しくなかったので、初心者がいきなりplugin作成してみる、もありだと思いました
1 プラグインをwordpressに認識させる
1-1 phpファイル作成
{root}/wp-content/plugins/simple-sse-chat/simple-sse-chat.php
plugins
ディレクトリに作成したいプラグイン用のフォルダと、それと同じ名前のphpファイルを作成します1-2 プラグイン名と説明を設定
/wp-content/plugins/simple-sse-chat/simple-sse-chat.php<?php /* Plugin Name: Simple SSE Chat Description: Server Snet Eventsを使用した簡易チャット */ // ↓このファイルに直接アクセスされた場合のために必ず処理の先頭につけましょう if ( ! defined( 'ABSPATH' ) ) { exit; }この状態で管理画面のプラグインのページをみると、プラグインを確認することができます
1-3 有効化する
有効化
を押すと/wp-content/plugins/simple-sse-chat/simple-sse-chat.php
で書いた内容が実行されることになります。
現時点ではまだ何もおきません。2 管理画面のメニューに追加
2-1
add_action('admin_menu', $callback)
add_action
または、add_filter
を使って、フックを設定します。
ここでは、管理メニューが読み込まれる前に実行されるadmin_menu
を指定します。フックについて参考:
【WordPress入門】アクションフックとフィルターフックを使いこなそう | 滋賀/京都/大阪でホームページ制作ならYUKiYURi WEB/wp-content/plugins/simple-sse-chat/simple-sse-chat.php<?php // ... add_action('admin_menu', function () { add_menu_page( 'Simple Chat Settings', // <title>タグの内容を設定 'Simple Chat', // 左メニューに表示される名前を設定 'manage_options', // 権限 'simple-sse-chat', // スラッグ 'admin_menu_simple_sse_chat', // メニューを開いたときに実行される関数名 'dashicons-admin-comments', // アイコン 200 // メニューの表示順、200と大きい数字にしたので、メニューの一番下に表示される ); }); function admin_menu_simple_sse_chat() { ?> <div class="wrap"> <h2>Simple Chat Settings</h2> </div> <?php }メニューに追加され、ページを表示することができました。
2-2
get_option
、update_option
で入力値を保存する/wp-content/plugins/simple-sse-chat/simple-sse-chat.php<?php // ... add_action('admin_menu', function () { add_menu_page( 'Simple Chat Settings', // <title>タグの内容を設定 'Simple Chat', // 左メニューに表示される名前を設定 'manage_options', // 権限 'simple-sse-chat', // スラッグ 'admin_menu_simple_sse_chat', // メニューを開いたときに実行される関数名 'dashicons-admin-comments', // アイコン 200 // メニューの表示順、200と大きい数字にしたので、メニューの一番下に表示 ); }); function admin_menu_simple_sse_chat() { if ($_SERVER['REQUEST_METHOD'] === 'POST') { check_admin_referer('hoge-fuga-piyo'); // CSRF対策 update_option('simple_sse_chat_header', $_POST['chat_heder']); ?> <div id="setting-error-settings-update" class="update-settings-error notice is-dismissible"><strong>Settings have been saved.</strong></div> <?php } $chat_heder = get_option('simple_sse_chat_header', 'Simple Chat'); // 第2引数は指定のoption_nameがない場合の初期値 ?> <div class="wrap"> <h2>Simple Chat Settings</h2> <form method="POST" action=""> <?php wp_nonce_field( 'hoge-fuga-piyo'); ?> <label for="chat_heder">チャットタイトル</label> <textarea name="chat_heder" class="large-text"><?= esc_textarea($chat_heder) ?></textarea> <input type="submit" name="submit_scripts_update" class="button button-primary" value="UPDATE"> </form> </div> <?php }
wp_options
テーブルに入力値が保存されていることが確認できました。3 ショートコードでチャットのHTMLを表示する
3-1 まず、簡単なHTMLだけを表示
/wp-content/plugins/simple-sse-chat/simple-sse-chat.php<?php // ... add_shortcode('simple_sse_chat', function () { return '<span style="color:blue;">ショートコードのテスト</span>'; });※wordpress 5からの投稿エディタのGutenberg(グーテンベルグ)でのショートコード設定方法↓
ショートコードがページに表示できたのを確認できました。
3-2 チャット用のHTMLを表示
HTMLを見やすくするためにバッファリングしてHTMLを表示するようにしてみました。
get_option('simple_sse_chat_header', 'Simple Chat');
として管理画面から登録した値を出力します。/wp-content/plugins/simple-sse-chat/simple-sse-chat.php<?php // ... add_shortcode('simple_sse_chat', function () { $header = get_option('simple_sse_chat_header', 'Simple Chat'); ob_start(); // バッファリング開始 ?> <form id="js-simple-sse-chat-form"> <h2><?= esc_html($header) ?></h2> <div class="simple-sse-chat-container"> <table id="js-simple-sse-chat-body"> <tbody></tbody> </table> </div> <input type="text" name="chat-content" id="js-simple-sse-chat-input"> <input type="submit" value="送信"> </form> <style> .simple-sse-chat-container { height: 200px; overflow: scroll; } </style> <?php return ob_get_clean(); // バッファからデータを取得してクリア });4 チャットデータ登録用のテーブルをDBに作成する
4-1
register_activation_hook()
でプラグインがアクティブ化されたときに実行される関数を定義/wp-content/plugins/simple-sse-chat/simple-sse-chat.php<?php // ... register_activation_hook(__FILE__, function () { // プラグインがアクティブ化されたときに実行される });4-2
$wpdb
を使用してDBを操作する/wp-content/plugins/simple-sse-chat/simple-sse-chat.php<?php // ... register_activation_hook(__FILE__, function () { global $wpdb; $sql = sprintf('CREATE TABLE %ssimple_sse_chat ( `id` INT NOT NULL AUTO_INCREMENT, `user_id` INT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY (`id`) ) %s;', $wpdb->prefix, $wpdb->get_charset_collate()); require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); });一度停止して、最有効化しても実行してくれます
プラグイン有効化時にテーブルを作成してくれました。
5 チャットの入力値を登録する
5-1 javascriptを読み込む
jsファイルを作成します。
/wp-content/plugins/simple-sse-chat/script.jsconsole.log(simple_sse_chat_data) // <- PHPから渡される変数今回はショートコードのHTMLをjavascriptで操作したいので、
add_action('the_content', $callback)
で、投稿や固定ページの内容が表示されたときに、has_shortcode($content, 'simple_sse_chat')
で指定のショートコードが使われていた場合のみ、jsファイルを読み込みます。/wp-content/plugins/simple-sse-chat/simple-sse-chat.php<?php // ... add_action('the_content', function ($content) { // ショートコードが使われているページのみjsを読み込む if (has_shortcode($content, 'simple_sse_chat')) { // script.jsにsimple_sse_chat_dataという名前のオブジュエクトを定義し、home_urlというプロパティを定義 wp_enqueue_script('simple_sse_chat', plugin_dir_url(__FILE__) . 'script.js'); wp_localize_script('simple_sse_chat', 'simple_sse_chat_data', [ 'home_url' => home_url(), 'nonce' => wp_create_nonce('hoge-fuga-piyo'), // CSRF対策 ]); } return $content; });指定のjavascriptが読み込まれ、PHPからjavascriptに
simple_sse_chat_data
という変数名で値を渡せていることが確認できました。5-2
wp_ajax_{action_name}
でチャットの内容を登録する用のurlを設定/wp-content/plugins/simple-sse-chat/simple-sse-chat.php<?php // ... add_action('wp_ajax_chat_post', function () { check_ajax_referer('hoge-fuga-piyo', 'security'); // CSRF対策 global $wpdb; $wpdb->insert($wpdb->prefix.'simple_sse_chat', [ 'user_id' => get_current_user_id(), 'content' => $_POST['chat-content'], ]); });↑のような書き方ですと
/wp-admin/admin-ajax.php?action=chat_post
でリクエストすると、コールバック関数を実行してくれます。5-3 ajaxで登録処理をする
/wp-content/plugins/simple-sse-chat/script.jsconst { home_url, nonce } = simple_sse_chat_data; // 登録 document.getElementById("js-simple-sse-chat-form").addEventListener("submit", async e => { e.preventDefault(); formData = new FormData(e.target) // 入力値が空の場合は何もしない if (formData.get("chat-content") === "") return; formData.append('security', nonce); // 登録のリクエスト const res = await fetch(`${home_url}/wp-admin/admin-ajax.php?action=chat_post`, { method: "POST", body: formData, }) // 入力欄を空にしからにしておく document.getElementById("js-simple-sse-chat-input").value = "" });チャットからの入力値をDBに保存することができました。
6 Server-Sent Eventsでチャットの内容を表示する
6-1 Server-Sent Eventsを実行するurlを定義する
/wp-content/plugins/simple-sse-chat/simple-sse-chat.php<?php // ... // ajaxのhookだが、SSEも問題なかったのでwp_ajax_{action_name}を使用しました add_action('wp_ajax_event_streame', function () { if (!wp_verify_nonce($_GET['_wpnonce'], 'hoge-fuga-piyo')) { exit; // CSRF対策 } global $wpdb; header('Content-Type: text/event-stream'); header('Cache-Control: no-store'); while(true) { printf("data: %s\n\n", json_encode([ 'chat_data' => $wpdb->get_results( "SELECT s.id, s.content, u.user_login FROM {$wpdb->prefix}simple_sse_chat s LEFT JOIN {$wpdb->prefix}users u ON s.user_id = u.id ORDER BY id DESC LIMIT 10" ), ])); ob_end_flush(); flush(); sleep(1); } });6-2 javascriptで表示処理をする
/wp-content/plugins/simple-sse-chat/script.jsconst { home_url, nonce } = simple_sse_chat_data; // ... const es = new EventSource(`${home_url}/wp-admin/admin-ajax.php?action=event_streame&_wpnonce=${nonce}`); // 表示 let lastId = 0; es.addEventListener("message", e => { const { chat_data } = JSON.parse(e.data); // 更新がなければ何もしない if (lastId === (chat_data[0] ? chat_data[0].id : 0)) return; // jsonを受け取ってHTMLにして表示 targetElement = document.getElementById("js-simple-sse-chat-body").querySelector("tbody"); targetElement.innerHTML = "" chat_data.forEach(data => { const { user_login, content } = data targetElement.insertAdjacentHTML("afterbegin", ` <tr> <td> <small>${user_login}</small> <br> <strong>${content}</strong> </td> </tr> `) }) // 新着があったら下にスクロール const scrollElement = document.querySelector(".simple-sse-chat-container") scrollElement.scrollTop = scrollElement.scrollHeight lastId = (chat_data[0] ? chat_data[0].id : 0); });Server-Sent Eventsでチャットの内容を表示することができました。
7 プラグイン削除時に不要なデータを削除する
7-1
register_uninstall_hook()
でプラグインが削除された場合に実行される関数を定義今回の場合だと、
wp_simple_sse_chat
テーブルの削除、wp_options
テーブルのoption_name
:simple_sse_chat_header
の削除が必要です。/wp-content/plugins/simple-sse-chat/simple-sse-chat.php<?php // ... // プラグインが削除されたときに実行される function simple_sse_chat_uninstall () { // TODO マルチサイトの考慮をするとより良いものとなる // see also https://github.com/okumurakengo/simple-sse-chat/pull/7 global $wpdb; $wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}simple_sse_chat"); // テーブル削除 delete_option('simple_sse_chat_header'); // wp_optionsテーブルの値を削除 } register_uninstall_hook(__FILE__, 'simple_sse_chat_uninstall');管理画面から削除ボタンを押すと、こちらの関数が実行してくれます
最後まで読んでいただいてありがとうございました。m(_ _)m