[{"data":1,"prerenderedAt":309},["ShallowReactive",2],{"$ft6jtkd6xhw3c":3,"$f1sp24gtqpifc5":263},{"featured":4,"latest":129,"categories":227},[5,14,27,37,48,56,66,76,86,96,106,114,122],{"slug":6,"title":7,"date":8,"excerpt":9,"image":10,"categories":11},"custom-database-tables","Custom DataBase Tables","2015-11-24T02:29:34","You can create the new own table freely in the your WordPress database via this plugin. Then, you can make the management of table or (as view or entry or edit) data in a simple user interface.\\nAlso,","\u002Fassets\u002Fuploads\u002F2016\u002F02\u002Fcdbt-overview.png",[12],{"slug":13,"name":7},"cdbt",{"slug":15,"title":16,"date":17,"excerpt":18,"image":19,"categories":20},"10_php_tips_for_developers","PHP開発者が心得ておくべき10の鉄則","2016-01-11T18:38:09","PHPだけに限った話ではないが、プログラミングを行うに当たって、心得て、準備しておくと失敗が劇的に減る事柄をまとめてある良い記事を見つけたので、私なりの解釈で意訳しつつ、経験則を踏まえてあらためてまとめてみた。 オリジナ…","\u002Fassets\u002Fuploads\u002F2016\u002F01\u002Feyecatch-php-10-tips.jpg",[21,24],{"slug":22,"name":23},"column","コラム",{"slug":25,"name":26},"development","開発関連",{"slug":28,"title":29,"date":30,"excerpt":31,"image":32,"categories":33},"jquery-timeline-v1","2種類の横型タイムラインを簡単に作成できる「jQuery.Timeline」をリリース","2017-05-29T14:55:46","現在開発しているサービスで利用するべく、横型タイムラインを生成できるjQueryプラグインを自作したので、そこだけ単独でリリースしてみた。 ソースコード等はGitHubの方にリリースしてあるので、興味のある方は、そち…","\u002Fassets\u002Fuploads\u002F2017\u002F05\u002Fjquery_timeline_image.png",[34],{"slug":35,"name":36},"jqtl","jQuery.Timeline",{"slug":38,"title":39,"date":40,"excerpt":41,"image":42,"categories":43},"optimize-browser-cache-of-static-resources","更新されたJS、CSS、画像のみブラウザキャッシュを破棄して読み込ませる","2018-01-31T15:47:47","WEBサービスの保守や開発をやっていると、JavaScriptやスタイルシート、画像などの静的リソースを更新した際、しばしば「確認したけど、更新されてないよ」とか云われて、「ブラウザのキャッシュを消してからもう一度見て…",null,[44,47],{"slug":45,"name":46},"codes","コード集",{"slug":25,"name":26},{"slug":49,"title":50,"date":51,"excerpt":52,"image":53,"categories":54},"jquery-timeline","jQuery.Timeline version2 – full documentation","2019-03-04T20:40:48","Plugin Overview The jQuery.Timeline plugin provides you two types of the horizontal timeline. You can create a…","\u002Fassets\u002Fuploads\u002F2019\u002F03\u002Fjquery.timeline_v2.jpg",[55],{"slug":35,"name":36},{"slug":57,"title":58,"date":59,"excerpt":60,"image":61,"categories":62},"released-the-sloth","Released the Sloth of mobile-first CSS micro framework!","2019-04-16T18:04:25","I released the “Sloth” that is mobile-first CSS micro framework. I have long used frontend framewo…","\u002Fassets\u002Fuploads\u002F2019\u002F04\u002Fsloth_doc_head.png",[63],{"slug":64,"name":65},"sloth","Sloth",{"slug":67,"title":68,"date":69,"excerpt":70,"image":71,"categories":72},"challenge-to-conceal-the-wordpress-site","WordPressサイトを徹底的に秘匿化する","2020-02-02T23:59:58","私は自分の個人サイトをはじめ、仕事でもWordPressサイトを運用している。WordPressは気軽にサイトを立ち上げられてカスタマイズも簡単な、素晴らしいオープンソースなのだが、世界中で使われまくっているその知名度…","\u002Fassets\u002Fuploads\u002F2020\u002F02\u002Fshutterstock_1878005929.jpg",[73],{"slug":74,"name":75},"wordpress","WordPress関連",{"slug":77,"title":78,"date":79,"excerpt":80,"image":81,"categories":82},"what-is-forkor","Made a shortened URL app called Forkor","2020-07-07T22:27:01","What’s Forkor? Forkor is a service that provides a shortened URL feature that allows you to redirect any…","\u002Fassets\u002Fuploads\u002F2020\u002F07\u002Fforkor.png",[83],{"slug":84,"name":85},"forkor","Forkor",{"slug":87,"title":88,"date":89,"excerpt":90,"image":91,"categories":92},"manual-about-svg-japan","インタラクティブな日本地図を生成するJSプラグイン「SVG Japan」","2020-11-16T21:19:58","各都道府県をそれぞれ選択・クリック可能なインタラクティブな日本地図を生成するネイティブJavaScript建てのプラグイン「SVG Japan」を作ったので、その紹介と使い方をまとめておく。ちなみにソースコードはgit…","\u002Fassets\u002Fuploads\u002F2020\u002F11\u002Fsvg-japan.png",[93],{"slug":94,"name":95},"svg-japan","SVG Japan",{"slug":97,"title":98,"date":99,"excerpt":100,"image":101,"categories":102},"wp-ignitor-plugin-to-conceal-wordpress-site","WordPressサイトを秘匿化するプラグイン「WP Ignitor」","2021-01-19T15:28:21","以前「WordPressサイトを徹底的に秘匿化する」という記事で、WordPress建てのサイトを秘匿化する方法を取り上げたのだが、あれをサイト構築時に毎度実施するとなるととても労力が必要であった。そこで、もっとお手軽…","\u002Fassets\u002Fuploads\u002F2021\u002F01\u002Fbanner-1554x500-1.png",[103],{"slug":104,"name":105},"wp-ignitor","WPイグナイタ",{"slug":107,"title":108,"date":109,"excerpt":110,"image":111,"categories":112},"tastewp","無料でWordPressサイトを複数作成できるWEBサービス「TasteWP」","2021-03-25T20:21:59","私が日本語翻訳に協力しているWEBサービス「TasteWP」について紹介しようと思う。 https:\u002F\u002Ftastewp.com このサービス、サインアップすることなく無料で自分用のWordPressサイトを作れるとい…","\u002Fassets\u002Fuploads\u002F2021\u002F03\u002FTasteWP_screenshot.png",[113],{"slug":74,"name":75},{"slug":115,"title":116,"date":117,"excerpt":118,"image":119,"categories":120},"monster-hunter-rise-skill-simulator","モンハンライズのスキルシミュレータを作ってみた！","2021-06-28T20:24:52","コラムで連載していたNintendo Switch版『モンスターハンターライズ』のスキルシミュレータ開発がやっと一段落して一通り完了した。いやぁ、記事書きながら同時並行で開発すると思っていた以上に時間がかかるもんだなぁ…","\u002Fassets\u002Fuploads\u002F2021\u002F06\u002Fmhrss_v100.png",[121],{"slug":25,"name":26},{"slug":123,"title":124,"date":125,"excerpt":126,"image":42,"categories":127},"comparison-of-javascript-date-libraries","JavaScriptの日時ライブラリを比較してみた","2021-07-28T22:39:19","ネイティブJavaScriptで日時を取り扱う場合、ビルトインされているDateオブジェクトを使うことになるのだが、このDateオブジェクトは取り扱いにクセが多く、直感的に日時を操作できない点でも有名である。そこで、専…",[128],{"slug":25,"name":26},[130,141,153,165,175,184,192,195,198,206,213,220],{"slug":131,"title":132,"date":133,"excerpt":134,"image":135,"categories":136},"pulllog_launched","ガチャ履歴を記録・分析できるWebサービス「PullLog」","2025-08-22T13:05:30","このたび、新しい個人開発サービスとして 「PullLog（プルログ）」 をリリースした。 URLはこちら https:\u002F\u002Fpulllog.net PullLogとは？ PullLogは、ソーシャルゲームのガチャ履歴を記録…","\u002Fassets\u002Fuploads\u002F2025\u002F08\u002Fpulllog_stats_jp.png",[137,140],{"slug":138,"name":139},"industries","プロダクト",{"slug":25,"name":26},{"slug":142,"title":143,"date":144,"excerpt":145,"image":146,"categories":147},"mockapi-php-openapi-schema-generation","モックAPIのレスポンスから OpenAPI 3.0 スキーマを自動生成するPHPツール","2025-04-17T09:31:52","私自身がそうなのですが、OpenAPI仕様書を毎回手作業で書いていて「面倒だな」と思ったことはありませんか？ そんな（私と）あなたにぴったりのツールがあります。 レスポンスの例さえ用意すれば、OpenAPI 3.0 スキ…","\u002Fassets\u002Fuploads\u002F2025\u002F04\u002Fswaggerui-preview.png",[148,151,152],{"slug":149,"name":150},"mockapi-php","MockAPI-PHP",{"slug":138,"name":139},{"slug":25,"name":26},{"slug":154,"title":155,"date":156,"excerpt":157,"image":158,"categories":159},"ambient_playlist_customize","Ambientでメメントモリのラメントプレイヤーを作る","2023-11-30T17:32:39","「Ambient」の最新バージョン 1.2.0で実装した「疑似フェーダー」機能を使って、ソーシャルゲーム「メメントモリ」のYouTube公式チャンネルで公開されているMVを集約したラメント専用プレイヤーを作ってみた。 ラ…","\u002Fassets\u002Fuploads\u002F2023\u002F11\u002Fambient_mememori_1.png",[160,163,164],{"slug":161,"name":162},"ambient","Ambient",{"slug":138,"name":139},{"slug":25,"name":26},{"slug":166,"title":167,"date":168,"excerpt":169,"image":170,"categories":171},"released_ambient_demo_version","Ambientメディアプレイヤーのクラウド版デモを公開","2023-11-22T13:47:36","つい先日「Ambient」の最新バージョン 1.1.0をリリースして、それに合わせてクラウド版デモサイトも公開してみた。 なお、クラウド版デモでローカルのホストコンピュータのメディアを再生するには、そのメディアファイルも…","\u002Fassets\u002Fuploads\u002F2023\u002F10\u002FAmbientMediaPlayer-typologo.png",[172,173,174],{"slug":161,"name":162},{"slug":138,"name":139},{"slug":25,"name":26},{"slug":176,"title":177,"date":178,"excerpt":179,"image":170,"categories":180},"ambient-media-player","YouTubeと自分のPC内メディアを同時に再生できるメディアプレイヤーを作ってみた","2023-10-20T00:00:25","私はよくPCで何か作業をするときには音楽を垂れ流していることが多い。 そういう時にかける曲は、手持ちのアルバムCD等からPCに取り込んだ音源だったり、さらにはYouTubeの動画だったりとメディアや再生環境がバラバラだ。…",[181,182,183],{"slug":161,"name":162},{"slug":138,"name":139},{"slug":25,"name":26},{"slug":185,"title":186,"date":187,"excerpt":188,"image":189,"categories":190},"what-is-the-sunorhc","Sunorhc is a date-time library for JavaScript","2021-08-25T22:55:55","Sunorhc is a lightweight library for handling dates and times for JavaScript. The library wraps a native JavaS…","\u002Fassets\u002Fuploads\u002F2021\u002F08\u002Fshutterstock_1091212919_resized.jpg",[191],{"slug":25,"name":26},{"slug":123,"title":124,"date":125,"excerpt":126,"image":42,"categories":193},[194],{"slug":25,"name":26},{"slug":115,"title":116,"date":117,"excerpt":118,"image":119,"categories":196},[197],{"slug":25,"name":26},{"slug":199,"title":200,"date":201,"excerpt":202,"image":203,"categories":204},"mhrise-skill-simulator-creation-chapter-12","モンスターハンターライズのスキルシミュレータを作ってみる！ 第12回","2021-06-21T13:11:46","今回は「マイセット登録」と護石データの「エクスポート／インポート」機能の追加をしていく。折り良く（悪く？）「FF7リメイク・インターグレード」や「FORTNITEシーズン7」が出たこともあって、そっちをプレイしてたらこ…","\u002Fassets\u002Fuploads\u002F2021\u002F04\u002Fb6ca09e9a7f6faf27641f62dabe64c6ff97b4f326aa478379e3c112aa28eb9ab.jpg",[205],{"slug":22,"name":23},{"slug":207,"title":208,"date":209,"excerpt":210,"image":203,"categories":211},"mhrise-skill-simulator-creation-chapter-11","モンスターハンターライズのスキルシミュレータを作ってみる！ 第11回","2021-06-07T14:52:31","さて、今回はユーザー個別データの管理部分を開発していく予定だが、まずその前にやっておくことがある。 新スキル「風雷合一」に対応する Ver.3.0の大型無料アップデートで追加された新スキルに「風雷合一」というのがある…",[212],{"slug":22,"name":23},{"slug":214,"title":215,"date":216,"excerpt":217,"image":203,"categories":218},"mhrise-skill-simulator-creation-chapter-10","モンスターハンターライズのスキルシミュレータを作ってみる！ 第10回","2021-06-02T00:29:10","今回は、護石管理画面の作成だ。この護石管理では、ユーザーごとに護石の登録を行う機能と、登録した護石を削除する機能を持つ仕様になる。この画面から登録した護石データはデータベースに格納するので、フロントエンド側からのリクエス…",[219],{"slug":22,"name":23},{"slug":221,"title":222,"date":223,"excerpt":224,"image":203,"categories":225},"mhrise-skill-simulator-creation-chapter-9","モンスターハンターライズのスキルシミュレータを作ってみる！ 第9回","2021-05-22T23:56:27","さて、今回はVuexによるデータ・キャッシュの実装だ。一度データベースから読み込んだデータをフロントエンド側で再利用可能なストアに格納することで、データベースへの接続回数を減らしてシステム負荷を下げるとともに、UX的な…",[226],{"slug":22,"name":23},[228,230,232,236,238,239,240,241,242,244,247,248,250,252,254,258,261],{"slug":161,"name":162,"count":229},3,{"slug":13,"name":7,"count":231},7,{"slug":233,"name":234,"count":235},"csnp","Custom Sticky Notes",2,{"slug":84,"name":85,"count":237},1,{"slug":35,"name":36,"count":229},{"slug":149,"name":150,"count":237},{"slug":64,"name":65,"count":237},{"slug":94,"name":95,"count":237},{"slug":74,"name":75,"count":243},37,{"slug":245,"name":246,"count":237},"wpqt","WP Qiita",{"slug":104,"name":105,"count":237},{"slug":22,"name":23,"count":249},29,{"slug":45,"name":46,"count":251},16,{"slug":138,"name":139,"count":253},5,{"slug":255,"name":256,"count":257},"lifelog","ライフログ",64,{"slug":259,"name":260,"count":235},"uncategorized","未分類",{"slug":25,"name":26,"count":262},45,{"type":264,"post":265},"post",{"slug":214,"title":215,"date":216,"excerpt":217,"image":203,"categories":266,"modified":268,"html":269,"tags":270,"related":286,"prev":303,"next":306},[267],{"slug":22,"name":23},"2022-07-21T18:30:39","\n\u003Cp>今回は、護石管理画面の作成だ。この護石管理では、ユーザーごとに護石の登録を行う機能と、登録した護石を削除する機能を持つ仕様になる。この画面から登録した護石データはデータベースに格納するので、フロントエンド側からのリクエストを処理してINSERTやUPDATE、DELETE等のクエリを発行することになる。\u003C\u002Fp>\n\n\n\n\u003Cp>──と、当初は考えていたのだが、護石データに関しては後々統計を取るなど再利用することもできそうだな……と思ったので、一度データベースに格納したデータを \u003Cstrong>DELETEクエリで物理削除\u003C\u002Fstrong> するのは止めることにした。つまり、フロントエンドで削除要求が行われた護石データについては、削除フラグをONにするという \u003Cstrong>UPDATEクエリによる論理削除\u003C\u002Fstrong> 方式を採用しておくのだ。\u003C\u002Fp>\n\n\n\n\u003Cp>　これを行うためには、護石マスターテーブルに論理削除用の削除フラグを格納するカラムが必要になる。このカラムは真偽値のみを格納することになるので \u003Ccode>bit(1)\u003C\u002Fcode> 型として追加する。なぜバイナリのビット値型のカラムにするのかは、以前私が書いた下記の記事を読んでみて欲しい。\u003C\u002Fp>\n\n\n\n\u003Cblockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\u003Cp>\u003Ca href=\"\u002Ftype-of-bit-is-optimal-as-field-handling-boolean-in-mysql\">MySQLに真偽値を格納する場合はbit(1)型のフィールドが最適\u003C\u002Fa>\u003C\u002Fp>\u003C\u002Fblockquote>\n\n\n\n\u003Cp>　そんなわけで、早速テーブルの修正を行う。まだ護石マスターテーブルにはデータが入っていないので、一度\u003Ccode>DROP TABLE\u003C\u002Fcode>してから改めて作り直す対応で問題ない。\u003C\u002Fp>\n\n\n\n\u003Ch2 class=\"wp-block-heading\">護石管理画面の追加\u003C\u002Fh2>\n\n\n\n\u003Cp>次に、新たに護石管理画面用のルーティングをVue.jsに追加する。ルートの追加は\u003Ccode>src\u002Frouter\u002Findex.js\u003C\u002Fcode>に下記のように追加するだけだ。\u003C\u002Fp>\n\n\n\n\u003Cpre class=\"wp-block-code\">\u003Ccode>import Talismans from '..\u002Fviews\u002FTalismans.vue'\n...(中略)\nconst routes = &#91;\n    ...(中略)\n    {\n        path: '\u002Ftalismans',\n        name: 'Talismans',\n        component: Talismans,\n    },\n...(以下略)\u003C\u002Fcode>\u003C\u002Fpre>\n\n\n\n\u003Cp>　そして、追加した設定に沿って各種コンポーネントファイルを作成・配置し、画面構成をマークアップする。Vuetifyのコンポーネントについては特に解説はしないが、最終的に出来上がった護石管理画面は下記のようになっている。\u003C\u002Fp>\n\n\n\n\u003Cfigure class=\"wp-block-image\">\u003Cimg data-recalc-dims=\"1\" decoding=\"async\" src=\"\u002Fassets\u002Fuploads\u002F2021\u002F05\u002FMHRSS_talismans.png\" alt=\"護石管理画面\"\u002F>\u003C\u002Ffigure>\n\n\n\n\u003Cp>　おまけ機能的に、プレビュー欄で登録しようとしている護石の評価を行うようにしてみた。評価用の公式は独自仕様なのではあるが、護石の空きスロットのサイズや数をベースにしたスロット評価と、付いているスキルを装飾品に換算して、その装飾品を作成するための素材価値をベースにしたスキル評価を合算して、100点を★5とした評価指標になっている。なお、いわゆる「\u003Cstrong>神護石\u003C\u002Fstrong>」と呼ばれる護石は★6の評価になるように調整してみたが、まぁ、評価方程式が素材取得難易度による定量的なものになっているので、ゲームのプレイ感を加味した定性的な観点から見ると評価結果には違和感を感じる。何気にゲーム中に重宝していて、コレって自分的に「神護石」じゃね？って護石もこの機能の評価だと★4だったりして、「結構、評価辛いなぁ……」と不満な結果になることも多いんだよね……w（ま、あくまでもおまけ機能なので、一つの指標にはなるかな……と）\u003C\u002Fp>\n\n\n\n\u003Ch2 class=\"wp-block-heading\">バックエンドのDB更新用拡張\u003C\u002Fh2>\n\n\n\n\u003Cp>　フロントエンド側ができたので、次はバックエンド側のDB更新処理を開発していく。既に汎用的なINSERT処理はヘルパーにあるので、作るのは汎用的なUPDATE処理になる。今回の護石管理ではデータの物理削除を行わないので、DELETE処理は不要だが、一応これも作っておくことにする。これで、基本的なCRUD処理はすべて行えるようになる。\u003C\u002Fp>\n\n\n\n\u003Cpre class=\"wp-block-code\">\u003Ccode>protected function update_data( string $table_name, array $data, array $conditions, string $operator = 'and', bool $use_named_parameters = true ): bool {\n    $operator = preg_match( '\u002F^(and|or)$\u002Fi', $operator ) ? strtoupper( $operator ) : 'AND';\n    $set_clauses = &#91;];\n    $parameters = &#91;];\n    foreach ( $data as $column => $value ) {\n        if ( $use_named_parameters ) {\n            $set_clauses&#91;\":$column\"] = \"`$column` = :$column\";\n            $parameters&#91;\":$column\"] = $value;\n        } else {\n            $set_clauses&#91;$column] = \"`$column` = ?\";\n            $parameters&#91;$column] = $value;\n        }\n    }\n    $where_clauses = &#91;];\n    if ( ! empty( $conditions ) ) {\n        foreach ( $conditions as $_cond ) {\n            if ( $use_named_parameters ) {\n                if ( preg_match( '\u002F^in$\u002Fi', $_cond&#91;1] ) &amp;&amp; is_array( $_cond&#91;2] ) ) {\n                    $where_clauses&#91;\":cond_{$_cond&#91;0]}\"] = \"find_in_set(cast(`{$_cond&#91;0]}` as char), :cond_{$_cond&#91;0]})\";\n                    $parameters&#91;\":cond_{$_cond&#91;0]}\"] = implode(',', $_cond&#91;2]);\n                } else {\n                    $where_clauses&#91;\":cond_{$_cond&#91;0]}\"] = \"`{$_cond&#91;0]}` {$_cond&#91;1]} :cond_{$_cond&#91;0]}\";\n                    $parameters&#91;\":cond_{$_cond&#91;0]}\"] = $_cond&#91;2];\n                }\n            } else {\n                if ( preg_match( '\u002F^in$\u002Fi', $_cond&#91;1] ) &amp;&amp; is_array( $_cond&#91;2] ) ) {\n                    $where_clauses&#91;\"cond_{$_cond&#91;0]}\"] = \"find_in_set(cast(`{$_cond&#91;0]}` as char), ?)\";\n                    $parameters&#91;\"cond_{$_cond&#91;0]}\"] = implode(',', $_cond&#91;2]);\n                } else {\n                    $where_clauses&#91;\"cond_{$_cond&#91;0]}\"] = \"`{$_cond&#91;0]}` {$_cond&#91;1]} ?\";\n                    $parameters&#91;\"cond_{$_cond&#91;0]}\"] = $_cond&#91;2];\n                }\n            }\n        }\n    }\n    $base_sql = \"UPDATE $table_name SET %s\";\n    $base_sql = sprintf( $base_sql, implode( ', ', $set_clauses ) );\n    if ( ! empty( $where_clauses ) ) {\n        $base_sql .= \" WHERE %s\";\n        $base_sql = sprintf( $base_sql, implode( \" $operator \", $where_clauses ) );\n    }\n    \u002F\u002F Ready for binding values\n    $bind_values = $this->get_binding_values( $parameters, $use_named_parameters );\n    \u002F\u002F Execute data insertion\n    try {\n        $sth = $this->dbh->prepare( $base_sql );\n        if ( ! empty( $bind_values ) ) {\n            foreach ( $bind_values as $_key => $_value ) {\n                $sth->bindValue( $_key, $_value&#91;0], $_value&#91;1] );\n            }\n        }\n        $result = $sth->execute();\n        return $result &amp;&amp; $sth->rowCount() != 0;\n    } catch ( \\PDOException $e ) {\n        throw $e;\n        return false;\n    }\n}\n\nprivate function get_binding_values( array $data, bool $use_named_parameters = true ): array {\n    if ( ! empty( $data ) ) {\n        return &#91;];\n    }\n    $bind_values = &#91;];\n    $bind_index = 0;\n    foreach ( $data as $_key => $_val ) {\n        switch ( gettype( $_val ) ) {\n            case 'integer':\n                $value = intval( $_val );\n                $data_type = \\PDO::PARAM_INT;\n                break;\n            case 'array':\n            case 'object':\n                $value = json_encode( $_val );\n                $data_type = \\PDO::PARAM_STR;\n                break;\n            case 'boolean':\n                $value = boolval( $_val );\n                $data_type = \\PDO::PARAM_BOOL;\n                break;\n            case 'NULL':\n                $value = null;\n                $data_type = \\PDO::PARAM_NULL;\n                break;\n            case 'double':\n            case 'string':\n            default:\n                $value = strval( $_val );\n                $data_type = \\PDO::PARAM_STR;\n                break;\n        }\n        $bind_index++;\n        $bind_key = $use_named_parameters ? $_key : $bind_index;\n        $bind_values&#91;$bind_key] = &#91; $value, $data_type ];\n    }\n    return $bind_values;\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\n\n\u003Cp>　追加した\u003Ccode>update_data\u003C\u002Fcode>（上記ソースでは省略してあるが\u003Ccode>delete_data\u003C\u002Fcode>も同様）は既に作ってあるINSERTとSELECT用の処理の焼き直しのようなアルゴリズムになるので、苦労することなく作れた。さらに、CRUD処理全てで使うプリペアドステートメントに値をバインドするための事前処理（値のバリデート＆キャスト処理）は、プライベート・メソッド\u003Ccode>get_binding_values\u003C\u002Fcode>として切り出して共通化した。\u003C\u002Fp>\n\n\n\n\u003Cp>　ちなみに、特殊な処理をしているところがあるので、それだけ説明しておこう。\u003Cbr>PDOのプリペアードステートメントの値としてPHPの配列をバインドするケースに対応するための方法だ。例えば、下記のようなUPDATEクエリを想定して欲しい。\u003C\u002Fp>\n\n\n\n\u003Cpre class=\"wp-block-code\">\u003Ccode>UPDATE talismans SET disabled = true WHERE id IN (1,2,4,6,7,9,10);\u003C\u002Fcode>\u003C\u002Fpre>\n\n\n\n\u003Cp>　SQLのIN句を使って複数のIDにマッチするレコードを更新したいケースになる。PDOのプリペアードステートメントの値では配列は取り扱えないので、複数IDを結合した文字列として渡すのだが、下記のような渡し方をするとエラーになってしまう。\u003C\u002Fp>\n\n\n\n\u003Cpre class=\"wp-block-code\">\u003Ccode>$sth = $dbh->prepare(\"UPDATE talismans SET disabled = :disabled WHERE id IN (:ids)\");\n$sth->execute(&#91;':disabled' => true, ':ids' => implode(',', &#91;1,2,4,6,7,9,10])]);\u003C\u002Fcode>\u003C\u002Fpre>\n\n\n\n\u003Cp>　PDOでは文字列のバインド値を文字列としてそのまま展開するため、実際に発行されるSQLが、\u003C\u002Fp>\n\n\n\n\u003Cpre class=\"wp-block-code\">\u003Ccode>UPDATE talismans SET disabled = true WHERE id IN (\"1,2,4,6,7,9,10\");\n\u003C\u002Fcode>\u003C\u002Fpre>\n\n\n\n\u003Cp>──となってしまうからだ。これを回避するために、IN句の場合のみ下記のような関数を経由させるようなプリペアードステートメントを生成するようにしてある。\u003C\u002Fp>\n\n\n\n\u003Cpre class=\"wp-block-code\">\u003Ccode>$sth = $dbh->prepare(\"UPDATE talismans SET disabled = :disabled WHERE find_in_set(cast(id as char), :ids)\");\n$sth->execute(&#91;':disabled' => true, ':ids' => implode(',', &#91;1,2,4,6,7,9,10])]);\u003C\u002Fcode>\u003C\u002Fpre>\n\n\n\n\u003Cp>　これで、バインドされたカンマ区切りの文字列をSQL上でパースして、IN句と同等の条件マッチができるようになる。ただ、SQLによる文字列キャスト処理はオーバーヘッドが高いので、配列に含まれる要素数が多くなると、クエリのパフォーマンスが低下する懸念が残る。後々、取り扱う配列サイズにキャップを設ける等の施策が必要になるかもしれない……。\u003C\u002Fp>\n\n\n\n\u003Ch2 class=\"wp-block-heading\">エンドポイントのアクセス・スキームの拡張\u003C\u002Fh2>\n\n\n\n\u003Cp>　次に、エンドポイントの処理として、フロントエンド側から受け取ったリクエストをハンドリングできるようにする。具体的には、コアクラスの\u003Ccode>CustomerCore::handle_request()\u003C\u002Fcode>メソッドを拡張するのだ。\u003C\u002Fp>\n\n\n\n\u003Cp>　今まではフロントエンドからの要求はSELECTクエリによる参照リクエストのみだったので、エンドポイントで捌くメソッドはGETだけだったが、これからはINSERTやUPDATE、DELETEのリクエストが発生することになる。それらのリクエスト・メソッド用に処理を準備していくうえで重要なのがRESTfulなメソッド定義である。つまり、処理内容がデータの登録（INSERT）であるならばPOSTメソッド、データ更新（UPDATE）ならばPUTメソッド、データ削除（DELETE）ならばDELETEメソッドといった風にHTTPのリクエストメソッドで処理を分岐させることになるのだ。\u003C\u002Fp>\n\n\n\n\u003Cpre class=\"wp-block-code\">\u003Ccode>protected function handle_request() {\n    $use_named_parameters = true;\n    $response = null;\n    switch ( $this->request_method ) {\n        case 'POST':\n            \u002F\u002F データ登録処理\n            break;\n        case 'GET':\n            \u002F\u002F データ参照処理\n            break;\n        case 'PUT':\n            \u002F\u002F データ更新処理\n            break;\n        case 'DELETE':\n            \u002F\u002F データ削除処理\n            break;\n        case 'COOKIE':\n        default:\n            break;\n    }\n    if ( $response ) {\n        $response = $this->check_to_cast( $response );\n        $this->return_response( $response );\n    } else {\n        $this->return_response( &#91;] );\n    }\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\n\n\u003Cp>　上記はコアクラス側での実装例だが、取得したリクエスト・メソッドに応じて、処理を分岐させている。\u003Cbr>注意が必要なのが、PUTやDELETEといったメソッドで送信されたデータのPHP側での受け取り方だ。PHPにはINPUT_PUTやINPUT_DELETEといったフィルター関数用の定数が用意されていないのだ。かといってINPUT_POSTでも取得できないので、下記のように\u003Ccode>file_get_conents('php:\u002F\u002Finput')\u003C\u002Fcode>で取得する必要がある。\u003C\u002Fp>\n\n\n\n\u003Cpre class=\"wp-block-code\">\u003Ccode>protected function catch_request(): void {\n    $this->request_method = $_SERVER&#91;'REQUEST_METHOD'];\n    $options = &#91;\n        \u002F\u002F filter options (省略)\n    ];\n    $this->the_request = match( $this->request_method ) {\n        'POST' => filter_input_array( INPUT_POST, $options ),\n        'GET' => filter_input_array( INPUT_GET, $options ),\n        'COOKIE' => filter_input_array( INPUT_COOKIE, $options ),\n        default => filter_input_array( INPUT_POST, $options ),\n    };\n    if ( ! $this->the_request ) {\n        $this->the_request = json_decode( file_get_contents( 'php:\u002F\u002Finput' ), true );\n    }\n    if ( ! $this->the_request ) {\n        $this->app_die( 'Request not found.' );\n    }\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\n\n\u003Cp>　さらに、フロントエンド側へレスポンスを返す時のヘッダ出力も調整しておく必要がある。特に異なるオリジン間ではCORSに引っかかってバックエンドとの通信がエラーになることが多い。特にGETやPOST以外の拡張メソッドを使用する場合はプリフライトリクエストでOPTIONSメソッドが発行されたりして、CORSによるブロックが発生しやすいのだ。そのため、PHPから出力するレスポンスのヘッダ\u003Ccode>Access-Control-Allow-Origin:\u003C\u002Fcode>にしっかりオリジンとなるURIを許可しておく等の対策も必要になってくる。この辺は解説しだすと、それだけで一記事になるので詳細は省くが、CORSを回避するためのバックエンド側の実装は下記のようになっている。\u003C\u002Fp>\n\n\n\n\u003Cpre class=\"wp-block-code\">\u003Ccode>protected function return_response( array $data ): void {\n    if ( isset( $_SERVER&#91;'HTTP_ORIGIN'] ) ) {\n        $allow_origin_regex = '@^https?:\u002F\u002F(localhost:8080|192\\.168\\.0\\.19:8080|(.*)?ka2.org)$@';\n        $origin = preg_match( $allow_origin_regex, $_SERVER&#91;'HTTP_ORIGIN'] ) ? $_SERVER&#91;'HTTP_ORIGIN'] : '*';\n        $allow_methods = $origin === '*' ? 'GET,OPTIONS' : 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS';\n        $allow_credentials = $origin === '*' ? 'false' : 'true';\n    } else {\n        $origin = '*';\n        $allow_methods = 'GET,OPTIONS';\n        $allow_credentials = 'false';\n    }\n    header( 'Access-Control-Allow-Origin: '. $origin );\n    header( 'Access-Control-Allow-Methods: '. $allow_methods );\n    header( 'Access-Control-Allow-Credentials: '. $allow_credentials );\n    header( 'Access-Control-Allow-Headers: X-Requested-With,Origin,X-Csrftoken,Content-Type,Accept' );\n    header( 'Access-Control-Expose-Headers: X-Custom-header', false );\n    header( 'Content-Type: application\u002Fjson; charset=utf-8' );\n    die( json_encode( $data ) );\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\n\n\u003Cp>　リクエスト元のオリジンをスーパーグローバル変数\u003Ccode>$_SERVER\u003C\u002Fcode>で取得して、正規表現で許容するオリジンかどうかを判定する。許可オリジンに対してのみプリフライトリクエストOPTIONSの次に来る本来のリクエストPUTやDELETE等を許可するという流れだ。なお、許可していないオリジンにはGETとOPTIONSメソッドのみ許可している。\u003C\u002Fp>\n\n\n\n\u003Cp>　そんなこんなで、あとはコアクラス側に具体的な処理を追加して行けば良い。ここでは省略するが、詳しいソース内容に興味がある人は\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fka215\u002Fmhr-skill-simulator\u002Ftree\u002Fv0.1.5\">GitHubのソース\u003C\u002Fa>を見て欲しい。\u003C\u002Fp>\n\n\n\n\u003Ch2 class=\"wp-block-heading\">Axiosで更新処理を行う\u003C\u002Fh2>\n\n\n\n\u003Cp>　最後に、バックエンドに追加した更新処理をAxiosからリクエストする。まずはPOST、PUT、DELETEのDB書き込み系のメソッドを一手にリクエストできる共通メソッド \u003Ccode>saveData\u003C\u002Fcode> を \u003Ccode>src\u002Fplugins\u002Ffunctions.js\u003C\u002Fcode> のミックスインに準備する。\u003C\u002Fp>\n\n\n\n\u003Cpre class=\"wp-block-code\">\u003Ccode>saveData: async function(method, params, callback=null, always=null) {\n    const instance = this.createAxios()\n    method = &#91;'post', 'put', 'delete', 'patch'].includes(method) ? method: 'post'\n    await instance&#91;method]('index.php', {params: params})\n    .then(response => {\n        if (callback &amp;&amp; typeof callback === 'function') {\n            callback(response)\n        }\n    })\n    .catch(error => {\n        console.error('Failure to insert data.', error)\n    })\n    .finally(() => {\n        if (always &amp;&amp; typeof always === 'function') {\n            always()\n        }\n    })\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\n\n\u003Cp>　後は、各コンポーネント側でデータを登録したいところで、\u003C\u002Fp>\n\n\n\n\u003Cpre class=\"wp-block-code\">\u003Ccode>this.saveData('post', {table: 'talismans', data: newTalismanData})\u003C\u002Fcode>\u003C\u002Fpre>\n\n\n\n\u003Cp>──と呼べば良い。さらに、データを更新する時は、\u003C\u002Fp>\n\n\n\n\u003Cpre class=\"wp-block-code\">\u003Ccode>this.saveData('put', {table: 'talismans', data: {disabled: true}, conditions: &#91; &#91;'id', '=', 1], ], operator: 'and'})\u003C\u002Fcode>\u003C\u002Fpre>\n\n\n\n\u003Cp>──という感じだ。\u003Cbr>　第3引数にバックエンドからの応答結果を受け取ってコールバック関数を受け渡せるようにしているので、そこに処理結果を通知する等の後処理を追加する形で使える。例えば、実際の護石管理コンポーネントでの護石登録用のメソッドは下記のようになっている。\u003C\u002Fp>\n\n\n\n\u003Cpre class=\"wp-block-code\">\u003Ccode>registerData: function() {\n    let newTalisman = {\n        name: this.t_name,\n        rarity: this.t_rarity,\n        slot1: this.t_slot1,\n        slot2: this.t_slot2,\n        slot3: this.t_slot3,\n        skills: {},\n        worth: this.t_worth,\n    }\n    if (this.t_skill1 != null &amp;&amp; this.t_skill1_level != null) {\n        newTalisman.skills&#91;this.t_skill1] = this.t_skill1_level\n    }\n    if (this.t_skill2 != null &amp;&amp; this.t_skill2_level != null) {\n        newTalisman.skills&#91;this.t_skill2] = this.t_skill2_level\n    }\n    this.saveData('post', {table: 'talismans', data: newTalisman}, (response) => {\n        let notices = {\n            title: null,\n            messages: &#91;],\n        }\n        if (response.data.state == 201) {\n            newTalisman.id = response.data.id\n            this.$store.dispatch('addData', {property: 'talismans', data: newTalisman})\n            notices.title = '通知'\n            notices.messages = &#91; '護石を登録しました。', '登録された護石は護石管理から確認・削除できます。' ]\n        } else {\n            notices.title = 'エラー'\n            notices.messages = &#91; '護石の登録に失敗しました。', 'もう一度お試しください。' ]\n        }\n        this.$root.$emit('open:notification', notices)\n    })\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\n\n\u003Cp>　注意点としては、護石の登録・削除後はVuexのストア側も更新しないといけないことだ。これを忘れるとDB側とアプリ側のデータ同期に齟齬が生じてしまうので、DB更新後のタイミングでストアを更新するアクションも準備しておかなければならない。基本的にはその都度DBから最新データを取得してストアをリロードしておけば問題ないだろう。\u003C\u002Fp>\n\n\n\n\u003Ch2 class=\"wp-block-heading\">護石管理画面が「一応」完成\u003C\u002Fh2>\n\n\n\n\u003Cp>　これで、護石管理画面から護石データを登録できるようになった。\u003Cbr>　しかーし、このままでは登録した護石がユーザーに紐づいていない。つまり、せっかく登録してもユーザー個別の護石データになっていないのだ。そんなわけで護石登録が完了した時点で、リクエスト元のユーザー側にその辺の紐づけ処理を行う必要がある。\u003Cbr>　今回のアプリではユーザー登録等の面倒な処理は実装する予定がないんだが、では、ユーザー側のどこにそれらのデータを紐づけるべきだろうか……。色々と手段はあるものの、無難かつ簡単なところではユーザーのブラウザに保存すれば良いだろう。\u003Cbr>　そんなわけで、次回はユーザー側へのデータ保存処理を実装して、護石管理機能を完全化する。\u003C\u002Fp>\n\n\n\n\n\n\n\n\u003Cp>　さて、護石データは登録できるようになったが、まだユーザーに紐づけられないのが歯がゆいところだ。まぁ、護石評価を試してみる用途には使えるだろう。なお、それ以外のUIについても一通りのデバッグを行って、ある程度実装が完了している状態だ。\u003Cbr>\u003Cbr>　あとは武器と防具のデータを投入するためにExcelのスプレッドシートを書き上げなきゃなぁ……（何気に、これが一番シンドイんだわ……w）。\u003C\u002Fp>\n\n\n\n\u003Cp>　ようやく、あと数回のコラムで完成しそうな勢いになって来た。本当はVer.3のアップデート前に完成させたかったんだが……まぁ、のんびりやるさ。趣味だしねw\u003C\u002Fp>\n\n\n\n\u003Cp>　ちゅーか、Ver.3で追加された「\u003Cstrong>風雷合一\u003C\u002Fstrong>」スキルが、かなりシミュレータ泣かせの鬼仕様なんだが……。これも、\u003Ca href=\"\u002Fmhrise-skill-simulator-creation-chapter-11\">次回\u003C\u002Fa>に対応しようかね……w\u003C\u002Fp>\n",[271,274,277,280,283],{"slug":272,"name":273},"mhrise","MHRise",{"slug":275,"name":276},"%e3%82%b7%e3%83%9f%e3%83%a5%e3%83%ac%e3%83%bc%e3%82%bf%e3%83%bc","シミュレーター",{"slug":278,"name":279},"%e3%82%b9%e3%82%ad%e3%83%ab","スキル",{"slug":281,"name":282},"%e3%83%a2%e3%83%b3%e3%82%b9%e3%82%bf%e3%83%bc%e3%83%8f%e3%83%b3%e3%82%bf%e3%83%bc%e3%83%a9%e3%82%a4%e3%82%ba","モンスターハンターライズ",{"slug":284,"name":285},"%e3%83%a2%e3%83%b3%e3%83%8f%e3%83%b3%e3%83%a9%e3%82%a4%e3%82%ba","モンハンライズ",[287,290,293,296],{"slug":199,"title":200,"date":201,"excerpt":202,"image":203,"categories":288},[289],{"slug":22,"name":23},{"slug":207,"title":208,"date":209,"excerpt":210,"image":203,"categories":291},[292],{"slug":22,"name":23},{"slug":221,"title":222,"date":223,"excerpt":224,"image":203,"categories":294},[295],{"slug":22,"name":23},{"slug":297,"title":298,"date":299,"excerpt":300,"image":203,"categories":301},"mhrise-skill-simulator-creation-chapter-8","モンスターハンターライズのスキルシミュレータを作ってみる！ 第8回","2021-05-15T23:40:48","今回はフロントエンドのVue.jsにAxios導入して、バックエンドのPHPを経由してデータベースからデータを取得する部分を作って行くのだが、その前に、現在のデータベースのテーブル定義に欠陥があったので、そちらを修正す…",[302],{"slug":22,"name":23},{"slug":221,"title":222,"date":223,"excerpt":224,"image":203,"categories":304},[305],{"slug":22,"name":23},{"slug":207,"title":208,"date":209,"excerpt":210,"image":203,"categories":307},[308],{"slug":22,"name":23},1793391394161]