20210606のPHPに関する記事は10件です。

optionの値によって表示する画像を変える方法

やりたかったこと ・selectタグのoptionの値によって表示させる画像を変えたい。 //bladeファイル <select name="grade" id="grade"> <option value="チャンピオン">チャンピオン</option> <option value="ダイヤ">ダイヤ</option> <option value="プラチナ">プラチナ</option> <option value="ゴールド">ゴールド</option> <option value="シルバー">シルバー</option> <option value="ブロンズ">ブロンズ</option> <option value="コッパー">コッパー</option> </select> //ダイヤを選んだらダイヤの画像を表示させたい controller public function index(){ $posts = Post::get(); return view('posts.index', compact('posts')); } public function add(Request $request){      //selectで選んだデータをgradeに入れる $param =[ 'grade' => $request->grade, ];      //DBにgradeのデータを格納 DB::table('posts')->insert($param); return redirect('/posts/index'); } 表示させたいbladeファイル //posts/index.blade.php //画像の名前をoptionの名前と同じにする <img src="/images/{{$post->grade}}.png" alt="" class="post-list-img"> そしたら表示できた。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHPUnit】複数あるassertのうちどれかが失敗しても全て実行させる方法

ここで書くこと この記事では、 複数のテストパターン(テストで使う値)を用いてテストしたいが、一つのアサーションが落ちた時に他のテストパターンの結果が見れず困っている テストコードとテストパターンを分離する方法が知りたい データプロバイダを使用してテストメソッドにテストパターンを渡したいと考えているがその方法が分からない という方向けに、テストメソッド内に複数あるアサーションのうちのどれかが失敗しても全て実行させる方法の一つとして、「データプロバイダを使ったテストのやり方」について記述しています。 執筆環境 執筆時点の環境は OS: macOS Catalina PHP: PHP 7.3.11 PHPUnit: PHPUnit 9.5.4 です(2021.06.06時点)。 PHPUnit及びアサーションメソッドについて データプロバイダの説明に入る前に、関連用語としてPHPUnitとアサーションメソッドについて少し触れたいと思います。 PHPUnitとはPHPにおける単体テストツールで、テストを行うための様々な機能やアサーションメソッドが用意されています。アサーションメソッドの代表的なものに assertSame($actual, $expected) があります。このアサーションメソッドは2つの引数が値・型ともに同一であるかを比較し、異なる場合にエラーを報告します。 アサーションメソッドには目的に応じて様々な種類があります: PHPUnitの主なAssertメソッド一覧 アサーションメソッドは以下のtestCalcSquare()のように、テストメソッドにおいてテスト対象のメソッドにテストパターンを渡して目的の結果が返ってくるかを判定する際に用いられます。 Square.php <?php class Square { // 引数を二乗した値を返す function calcSquare($a) { return $a * $a; } } SquareTest.php <?php use PHPUnit\Framework\TestCase; include('Square.php'); class SquareTest extends TestCase { // calcSquare()をテストするメソッド public function testCalcSquare() { $square = new Square(); $this::assertSame($square::calcSquare(5), 25); } } テストパターンが複数あるなどテストメソッド内でアサーションメソッドを複数記述した時に、途中で一つでも結果がfailureとなるとそこでテストが終了し、以降のアサーションメソッドは実行されません。 またテストパターンが増えるとその数だけアサーションメソッドを書く必要があり、テストコードが長くなって可読性が悪くなってしまうことも考えられます。 テストパターンが複数あり、(いずれかのパターンでテストが落ちても)一度のテストで全てのテストパターンの結果が分かるようにしたい場合、またテストパターンが増えた時にテストコードが冗長になってしまうことを防ぎたい場合は、今回ご紹介する「データプロバイダ」を用いる方法が有効です。 データプロバイダについて データプロバイダはPHPUnit上で用意されている機能の一つで、これを使うことでテストパターンとテストコードを分離することができます。 大まかなイメージとしては、テストパターンのみを持つメソッドからテストパターンを1つ渡してテストメソッドを実行し、それを各テストパターンごとに繰り返し行う、というような流れで処理が行われます。 テストパターンのみを持つメソッドを新たに作成し、テストメソッドにてデータプロバイダ用のアノテーション(@dataProvider)を用いてそのメソッドを指定することでデータプロバイダ機能を使うことができます。 公式ドキュメント: 2.PHPUnit 用のテストの書き方 具体的なやり方についてはこの後説明していきたいと思います。 データプロバイダを使ったテストのやり方 先ほどの例を使って、データプロバイダ機能の使い方について書いていきます。 まず、以下のようにテストコードからテストパターン部分を取り出して、テストパターンを返すメソッドを作成します(ここでは例として3パターンを用意しています)。アサーションメソッドに渡していた引数を要素に持つ配列が1つのテストパターンとなります。 SquareTest.php // テストパターンを返すメソッドを新たに作る public function providerForTestCalcSquare() { return [ [5, 25], [10, 100], [15, 225], ]; } 次に、テストパターンの値に対応するようにテストメソッドの引数を設定します。それに伴って、テストコード内のテストパターンの値も引数を使うように変更します。 SquareTest.php // テストパターン(今回の例では[5, 25]など)に対応するように引数を設定 public function testCalcSquare(int $num, int $expected) { $square = new Square(); // アサーションメソッドの引数もテストメソッドの引数を使うように変更 $this::assertSame($square::calcSquare($num), $expected); そして、データプロバイダ機能を使いたいテストメソッドにてPHPDocにデータプロバイダアノテーション(@dataProvider)を書き、テストパターンを返すメソッドを指定します。 SquareTest.php /** * @dataProvider providerForTestCalcSquare */ public function testCalcSquare(int $num, int $expected) { ... 最終的に、テストコードは以下のようになります。 SquareTest.php <?php use PHPUnit\Framework\TestCase; include('Square.php'); class SquareTest extends TestCase { public function providerForTestCalcSquare() { return [ [5, 25], [10, 100], [15, 225], ]; } /** * @dataProvider providerForTestCalcSquare */ public function testCalcSquare(int $num, int $expected) { $square = new Square(); $this::assertSame($square::calcSquare($num), $expected); } } これでデータプロバイダ機能を使ってテストコードが実行されます。なお、テストを実行する手順自体には変更はないので、ターミナル上で $ phpunit SquareTest.php のようにすればテストを実行できます。 データプロバイダを使う時の注意点として、 データプロバイダメソッドはpublicでないといけない データプロバイダメソッドに特に命名規則は無いが、PHPUnitにおいてはtestで始まる名前のpublicなメソッドはテストメソッドとして認識されるので、testで始まる名前は避ける が挙げられます。 また、テストパターンが少ない場合はPHPUnitのtestWith機能を使うやり方もあります。 testWith機能の使い方は、以下のようにテストメソッドにおいてPHPDocにtestWithアノテーション(@testWith)を書き、そこに渡したいテストパターンを直接書きます。 SquareTest.php /** * @testWith [5, 25] * [10, 100] */ public function testCalcSquare(int $num, int $expected) { $square = new Square(); $this::assertSame($square::calcSquare($num), $expected); } このやり方でもデータプロバイダと同様に、いずれかのテストパターンが落ちても全てのテストパターンが実行されます。 参考にさせていただいたサイト PHPUnitとデータプロバイダとテストケース生成 PHPUnitのdataProviderを使ったらrisky testになった話 データプロバイダーを使用してテストコードをDRYに PHPUnitのプラクティス
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP × Xdebug × Docker × VSCodeで快適な開発環境をつくってみた

はじめに Rails でポートフォリオを作成してエンジニア転職をしたすずです。 現在は Laravel を主に使用していますがデバッグが捗らない...。 PHP でも Rails の pry-rails みたいに簡単にデバッグをしたかったので Xdebug を導入しました。 目次 はじめに 目次 環境 前提条件 ディレクトリ構成 準備 プロジェクト作成 必要ファイル作成 拡張機能 Docker環境構築 docker-compose.yml Dockerfile コンテナ接続 設定ファイル php.ini xdebug.ini devcontainer.json launch.json 使い方 おわりに 参考文献 環境 macOS Catalina 10.15.7 windows10 pro docker Xdebug 3系 前提条件 Docker 環境 docker-compose.yml Dockerfile ディレクトリ構成 このような構成を想定 . ├── .devcontainer │ └── devcontainer.json ├── app │      ├── .vscode │      │ └── launch.json // 後述しますが、VSCodeの左のバーにある虫マークをクリックすると生成されます。 │      └── addNum.php // デバッグテスト用のファイル ├── docker │ └── php │ ├── Dockerfile │ ├── xdebug.ini │ └── php.ini └── docker-compose.yml 準備 プロジェクト作成 プロジェクトを作成していきます。プロジェクト名はお好きな名前で大丈夫です。ここでは 「php_xdebug_app」 という名前で進めていきます。 ターミナル mkdir php_xdebug_app cd php_xdebug_app 必要ファイル作成 全体の見通しを良くするためファイルを始めに作成していきます。 作成ははじめに載せたディレクトリ構成に沿った形で作成しています。 ターミナル touch docker-compose.yml mkdir docker/php touch docker/php/Dockerfile touch docker/php/php.ini touch docker/php/xdebug.ini touch .devcontainer/devcontainer.json # プロジェクトパス(デバッグしていきたいファイルなどを格納します。) mkdir app 拡張機能 以下の VSCode の拡張機能が必要になりますのでインストールをしてください。 PHPでデバッグをするのに必要になります。 コンテナ内に入るのに必要になります。 Docker環境構築 docker-compose.yml docker-compose.yml version: "3.8" services: php: build: context: . dockerfile: docker/php/Dockerfile volumes: - ./app:/var/www/html ports: - "8000:80" Dockerfile docker/php/Dockerfile FROM php:7.4-fpm WORKDIR /var/www/html RUN apt-get update \ && apt-get install -y libonig-dev libzip-dev unzip \ # xdebug install && pecl install xdebug \ && docker-php-ext-enable xdebug # composer (今回は使用しませんが。。) COPY --from=composer:2 /usr/bin/composer /usr/bin/composer # docker-compose.ymlで指定した volumes と同じように書いてください。 # COPY [コピーする(ローカル)] [コピー先(コンテナ内)] (詳しくは参考文献をご覧ください。) COPY ./app /var/www/html COPY ./docker/php/php.ini /usr/local/etc/php/php.ini COPY ./docker/php/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini 設定ファイル php.ini PHP の設定ファイル。 docker/php/php.ini display_errors = On display_startup_errors = On max_execution_time = 30 session.gc_divisor = 1000 session.sid_bits_per_character = 5 output_buffering = 4096 memory_limit = 128M upload_max_filesize = 64M post_max_size = 64M max_input_vars = 1000 log_errors = On error_log = /dev/stderr error_reporting = E_ALL expose_php = On zend.exception_ignore_args = Off xdebug.ini xdebug の設定ファイル。 docker/php/xdebug.ini xdebug.client_host = host.docker.internal xdebug.start_with_request = yes xdebug.mode = debug # port 番号はデフォルトで9003番ですがわかりやすいように明示的に書いています。9003ではなくても空いているポートなら大丈夫です。 # 後述する .devcontainer/devcontainer.json に書く port と同じにしてください。 xdebug.client_port = 9003 xdebug.log = /var/log/xdebug.log devcontainer.json VSCodeがDockerコンテナに接続するための設定ファイル。 .devcontainer/devcontainer.json { "name": "php_xdebug_app", // お好きな名前 "dockerComposeFile": [ // docker-compose.ymlのパスを記載 "../docker-compose.yml", ], "service": "php", // docker-compose.yml に記載しているサービス名で起動したいコンテナ "workspaceFolder": "/var/www/html", // 作業するパス "forwardPorts": [ // xdebug.iniに書いたport番号と同じにする 9003 ], "extensions": [ // コンテナ内で使用したい拡張機能、お好みに合わせて設定してください。 "felixfbecker.php-debug", "coenraads.bracket-pair-colorizer-2", "oderwat.indent-rainbow", "streetsidesoftware.code-spell-checker", "mosapride.zenkaku" ] } コンテナ接続 Docker環境を構築し、設定ファイルも作成したのでいよいよデバッグ機能を使用する環境(PHPコンテナ内)に入ります。 その前にコンテナをビルドして立ち上げておきましょう。以下のコマンドを叩いてください。 ターミナル # build docker-compose build # コンテナ立ち上げ docker-compose up 以下のような手順でコンテナ接続していきます。文字だけですと伝わらない部分あるかと思いますので Gif を作成しました。ぜひご覧ください。 (※ お使いの環境によって青い表示といっている部分が青くなかったらすみません。適宜対応していただけると助かります。) 1. VSCode画面左下にある青いマークをクリック 2. Reopen in Container をクリック  3. しばらく立ったら画面遷移して左下の青い表示が devcontainer.json で設定した name と同じになっていたらコンテナと接続できています。 launch.json デバッグするのにも設定ファイルが必要になります。 launch.json作成していきます。 VSCode の左側のパネルに虫マーク (実行とデバッグ) があります。これをクリックすると launch.json の作成を求められるかと思うので作成を押してください。わかりにくい場合はディレクトリ構成に合わせてファイルを作成していただいても大丈夫だと思います。 app/.vscode/launch.json { "version": "0.2.0", "configurations": [ { "name": "XDebug on docker", // お好きな名前 "type": "php", // 今回はPHPなので "request": "launch", "port": 9003, // portはxdebug.iniで設定したportと一緒にする "stopOnEntry": true, // デバッグが有効なときにプログラムを中断する } ] } 以上でデバッグができるようになります。 使い方 言葉だけでは伝わらない部分があると思いますのでこちらも Gif を載せておきますので参考にしていただけたら幸いです。 変数など値を確認したいところにブレークポイント(赤ポチ)を置く 左のバーにある虫マークをクリック 左上に再生ボタンが現れるのでクリック(下のバーがオレンジ色に切り替わる) テストなどで動作を確認してみる。 簡単なプログラムを作成したので動作確認などでお使いください。 数字を足して出力するだけのプログラムです。このファイルを実行していきます。 addNum.php <?php function addNum(int $num1, int $num2): int { return $num1 + $num2; } $num1 = 1; $num2 = 2; $result = addNum($num1, $num2); print_r($result); 横のバーに変数名と値など表示されますが、デバッグコンソールに変数名など入力することでも値をみることができます。 おわりに var_dump() などで値を見るよりも簡単に確認することができるようになりました。 まだ使いこなせていませんが、開発をしていって慣れていきたいと思います。 参考までに今回のソースコードをこちらにまとめさせていただきました。 お役に立つことができましたら幸いです。 参考文献 Docker VSCode PHP デバッグ 設定 xdebug vscodeでxdebug3のdocker環境を作る WSL2+Docker+VSCode+PHPでXdebugを使用する際のホストにhost.docker.internalは使えない vscode↔Docker内PHPでリモートデバッグしたときにハマったこと PHPの開発効率化!VSCode+DockerでXdebugを使ってみよう Visual Studio CodeのRemote DevelopmentとDockerで快適な開発環境をゲット 今回作成したプロジェクトのソースコード
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPとVue.jsを使って「本のレンタルアプリ」を作成した話

はじめに PHPとVue.jsを使った「本のレンタルアプリケーション」を作成した時の話や、構成をまとめてみました。 Web系のプログラミングを大方学んでみて、実際に何か作ってみようと考えている人や、複数のプログラミングや技術を合わせて使ったことのない人に参考にしていただければと思います。 PHPとVue.jsでなくても、RubyやjQueryなどでも代用はできますし、どちらかといえば環境構築やデータベースの構成のほうをメインに書いています。 図書レンタルアプリを作った理由 今回この図書レンタルアプリを作ろうと思ったのは、一から一人だけで、サービスを作る経験を積んでおきたかったからです。 業務では基本的に分業が多く、フロントエンドとバックエンドで別れてサービスを構築するのが基本で、新しくサービスを一から作る機会自体少ないです。 基本は現状あるサービスの改修や、修正がメインのため、一からサービスをコンスタントに作って、場数を踏みたいと思ったのがきっかけです。 そして何を作ろうかと考えたときに、会社の本棚の本の管理ができていなかったので、誰が何をいつまで借りているのかや、本のレンタル履歴を管理できるアプリケーションを作ってみようと思ったのがきっかけです。 使用した技術 使用した技術 PHP Vue.js MySQL Docker アプリを作り始めた時の自分のスキルレベル javascript javascriptは実務でもメインで使っているため、問題なくかけるレベルです。 ライブラリーやフレームワークにつては、jQueryとVue.jsが使えました。 jQueryは結構知識はありましたが、Vue.jsの経験は少なめでした。 Vue.js Vue-CLI、vue-router、VueXの基礎知識をudemyの動画を通して学んだくらいです。 仕事ではVueファイル形式で書いたことはなく、今回Vueファイル形式で、ビルドする書き方ははじめてでした。 PHP PHPも実務での経験は少なく、PHPで一からサービスを作ったことはありませんでした。 実務で、今あるサービスの改修や修正、機能追加などでPHPを触るくらいの経験しか有りませんでした。 Docker Docker関しては、完全に初めてで、素人でした。 それまではVirtualBoxでlinuxの環境を作って、開発を行っていました。 なのでlinuxコマンドなどの知識はある前提です。 スキルレベルのまとめ 一応現役のエンジニアではありますが、基本的に一からのサービス構築の経験は少なく、あってもメインで携わって来たのはフロント側のみで、バックエンドやインフラはすでに構築されているものを少し触るくらいの経験しか積んできませんでした。 キャリア的にももっと経験を積まないとまずいと思い、ここらで一気に経験を積みたいと思い、とりあえず「PHP」「Vue.js」「MySQL」「Docker」この辺の技術を使って、一人で一から簡単なサービスを作るところから始めようと思いました。 図書レンタルアプリの機能 バックエンドとユーザー登録、ログインのページはPHP それ以外のフロントエンドはVue.jsで作成しました。 実装する機能のリスト ・ユーザー登録機能 ・ログイン機能 ・ログアウト機能 ・レンタル機能 ・レンタル状況確認機能 ・返却機能 ・返却期限編集機能 ・ユーザー情報編集機能 ・ランキング機能 ・本の登録機能 アプリのデザインについて 普段の業務で自分であまり使わないのですが、デザインを作成するのにXDを使い、簡単にデザインを作成しました。 今回はXDのスキルではなく、Web開発のエンジニアスキルを高めたかったので、デザインはなるべくシンプルにしました。 もともとミニマムなデザインが好きなので、色もモノトーンベースにしています。 PHPで作成した「ユーザー登録ページ」と「ログインページ」     Vue.jsで作成したシングルページアプリケーション(SPA)      データベースについて データベースはMySQLを使用しました。 MySQLにlibraryというデータベースを作成し、その中に「members」「book」というテーブルを作成しました。 データベースの構成と役割 データベース:library   テーブル   └book   └members book bookには本の情報を保存します。 book_id:本を識別するID title:本のタイトル status:本がレンタル中or在庫あるか rental_id:レンタルされている場合はmemberのid、レンタルされていない場合はNULL date_rimit:レンタルされている場合はレンタル期限、レンタルされていない場合はNULL category:本のカテゴリー tag:本の検索に使うtag count:レンタルされた回数 created_at:本が登録された日付 members membersにはアプリを使用する人のログイン情報や、レンタルしている本のidのリストを保存します。 id:ユーザーを識別するID name:ユーザーの名前 mail:ユーザーのメールアドレス password:ログインするためのパスワード created:ユーザー登録した日付 rental_list:現在レンタルしている本のbook_idのリスト 各ファイルの役割 PHPについて PHPでCRUD機能を実装しました。 ユーザーの登録機能(Create) ユーザのログイン機能(Read) ユーザー、本の管理機能(Read) ユーザー、本の情報書き換え(Update) ※Dleteに当たる機能は今回有りませんでした。 Vue.jsで作成したSPAから、本を検索したり、レンタルしたりするときにPHPを動かす必要がありますが、axiosを使い通信を行いました。 Vue.jsについて ※この話はVue.jsを学んでいる人ならわかると思いますが、Vue.jsに興味がない場合は飛ばしてください Vuejsで作成したSPAのページはタブ構成になっており、タブの切替はVue-routerを使って、urlを切り替えてタブ機能を実装しています。 各タブの中のページの切り替え(例えば、レンタルタブの中で、本を検索したり、本をレンタルする際にページの切り替え)は、動的コンポーネントで表示を切り替えています。 レンタルタブ      状況タブ(レンタル状況と返却) Dockerでの環境構築 今回は開発環境としてはDockerでLAMP環境を構築することにしました。 Docker-composeを使って、複数のコンテナをたててLAMPを構築しました。 参考までにymlファイルと、dockerファイルを公開しておきます。 yml version: "3.7" services: mysql: image: mysql:5.7 volumes: - db_data:/var/lib/mysql restart: always environment: MYSQL_ROOT_PASSWORD: "password" phpmyadmin: depends_on: - mysql image: phpmyadmin/phpmyadmin environment: PMA_HOST: mysql restart: always ports: - "8080:80" php-apache: build: ./php volumes: - ./htdocs:/var/www/html restart: always ports: - "80:80" depends_on: - mysql volumes: db_data: {} ./phpの直下に書きを保存 dockerfile FROM php:7.3-apache COPY ./php.ini /usr/local/etc/php/ COPY ./apache2.conf /etc/apache2/ COPY ./sites/*.conf /etc/apache2/sites-available/ RUN apt-get update \ && apt-get install -y libfreetype6-dev libjpeg62-turbo-dev libpng-dev \ && docker-php-ext-install pdo_mysql mysqli mbstring gd iconv 上記のファイルで下記のコマンドを実行すれば、LAMP環境が立ち上がります。 terminal docker-compose up -d Dockerでの環境構築で困ったこと 今回一番困ったのがDockerで作ったapacheの設定です。 私も初めて知ったのですが、公式で公開されている「php:7.3-apache」などのApacheは、あくまでphpが動くだけのapacheの環境しか用意していないため、普通のapacheの環境でデフォルトで設定されているようなものは、デフォルトで入っていないことがあります。 今回ハマったのが、Rewriteのモジュールです。 Vue-routerを使った時に、historyモードを使用していると、リロードした時に404エラーが出てしまいます。これはVueの公式にも記載されています。 それを回避するために、apacheの設定ファイルか、.htaccessでRewriteの設定をする必要があるのですが、そのためのモジュールがapacheにはありませんでした。 それに気づかずに、設定ファイルにRewriteの記述を書いても問題が解決せずにいました。 今回の場合は、それをphp:7.3-apacheに入り、インストールして解決しましたが、本来であれば、Dockerfileにそれを記載しておく必要があります。 そうしないと次回コンテナを新しく立ち上げた時に、また、同じことをしないといけないからです。 かなり勉強になりました。 アプリケーションを作ってみた感想 今回一番新しい試みだったのが、Vue.js(Vuex、Vue-router)とDockerでの開発でしたが、初めてだったので、ディレクトリ構造などが少し納得できるくらい整理ができず、本当は記事にして、詳細まで説明しようと思っていたのですが、できませんでした。 ただ、一度作ってみることで、ディレクトリ構造以外にも、設計やデータベースの構成など、ワンストップで開発しないと経験できないことを多く学ぶことができました。 やはり部分的に開発しているだけでは、見えていないことや、自分のスキルとして足りていないことが多くあることを発見でき、やってみてよかったと思いました。 AWSの使用について(余談) ちょっと今回の話に関係ないののですが、少し話をさせてください。 このアプリを作る際に、本番のアップロード先について最初はAWSで、VPCとEC2を使ってネットワークとサーバーを立てて、そこで公開しようと思っていました。 ですが、この手のアプリケーションを作る時に、流行っているからというだけでAWSを使うのはちょっと違うと考えました。 特に初学者の人は手を出しやすい場所だと思うのですが、AWSは基本的に従量課金のため、サーバーが動いている分だけ料金が課金されていきます。 lamdaなどを使って、アプリケーションが動くときだけ稼働させるなどの工夫をすればいいのですが、割と初学者にとってはハードルが高いため、最初はおすすめしません。 もちろんAWSの技術をみにつける目的があるのであれば、試すのもありですが、今回はインフラ周りというよりも動くものを作るのが目的だったので、さくらサーバーなどのレンタルサーバーを使用しました。 というのも会社で使うアプリケーションのため、サーバーやAWSは会社のものを使うので、コストは最小限に抑える必要があったからです。 プログラミングを学ぶときにはこういったコストのことも考えながら作ると、より実践向きのエンジニアに成長できると思うので、今回関係ないですが、この話をはさみました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker×nginx×php-fpmでwordpress複数台を負荷分散させつつ動かしてみました。

特別Wordpressでブログを出しているような身ではないのですが、少し変わった環境を構築してみたいかもという興味本位でやってみました。 Wordpressサイト運用でプラグイン入れたりバージョン上げたりしたら動かないなんてことも聞くので、こうやって仮想環境を導入してローカル検証の手段を確保したり、将来的にそのままECSなどクラウドコンテナ管理サービスにデプロイして運用したりするといいのかもと思ってる最近です。 いくつかヒットした日本語サイトの情報が古いもので参考にできなかったので、公式ドキュメントのみ参照して2021年最新のもののみで作成しました。将来のバージョンアップも考慮して使用バージョンも残してまとめています。 同じようなことをしたい方に参考にしていただければ幸いです。 環境 Windows 10 (Docker Desktopインストール済) ディレクトリ構成 project/ ├ .docker/ │ ├ env_files/ │ │ ├ mysql.env │ │ └ wordpress.env │ └ nginx/ │ └ default.conf └ docker-compose.yml 使用したdockerイメージ パッチバージョンまで指定すると後で使えなくなった時に困るので、マイナーバージョンまでの指定でイメージを使用するのをお勧めします。バージョン違いで動かないことも稀にあるので指定は必ず行いましょう。 以下に使用したイメージと特記事項を残しています。 nginx:1.21-alpine 特記事項なし wordpress:5.7-php7.4-fpm-alpine 7.4-fpmを土台にしたphpイメージのため、9000番ポートがデフォルトでexportされている(参考ページ) mysql:8.0 デフォルトで3306番ポート、33060番ポートがexportされている MySQL8.0以上ではデフォルトの認証方式が5.7以前と異なるため注意 具体的な実装方法 コード project/docker-compose.yml version: "3.9" services: nginx: image: nginx:1.21-alpine ports: - "3000:80" volumes: - ./.docker/nginx/default.conf:/etc/nginx/conf.d/default.conf - ./html:/var/www/html depends_on: - wp_first - wp_second - wp_third wp_first: image: wordpress:5.7-php7.4-fpm-alpine volumes: - ./html:/var/www/html env_file: - ./.docker/env_files/wordpress.env depends_on: - mysql wp_second: image: wordpress:5.7-php7.4-fpm-alpine volumes: - ./html:/var/www/html env_file: - ./.docker/env_files/wordpress.env depends_on: - mysql wp_third: image: wordpress:5.7-php7.4-fpm-alpine volumes: - ./html:/var/www/html env_file: - ./.docker/env_files/wordpress.env depends_on: - mysql mysql: image: mysql:8.0 command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --default-authentication-plugin=mysql_native_password volumes: - ./.docker/mysql:/var/lib/mysql env_file: - ./.docker/env_files/mysql.env project/.docker/nginx/default.conf upstream wp_fastcgi_passes { least_conn; server wp_first:9000; server wp_second:9000; server wp_third:9000; } server { listen 80; root /var/www/html/; index index.php; location / { try_files $uri $uri/ /index.php$is_args$args; } location ~ \.php$ { fastcgi_pass wp_fastcgi_passes; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name; } } project/.docker/env_files/mysql.env MYSQL_ROOT_HOST=% MYSQL_ROOT_USER=root MYSQL_ROOT_PASSWORD=root MYSQL_DATABASE=wordpress project/.docker/env_files/wordpress.env WORDPRESS_DB_HOST=mysql WORDPRESS_DB_USER=root WORDPRESS_DB_PASSWORD=root WORDPRESS_DB_NAME=wordpress docker関連の説明 Wordpressファイルの自動生成場所をマウントしておく 基本的にwordpressコンテナを立ち上げると1分くらいで/var/www/htmlに生成されるので、wordpressコンテナのvolumeに./html:/var/www/htmlを指定しておきます。こうすると、今は無い「html」というディレクトリがいつの間にか出来上がります。 静的ファイルを配信する準備をしておく nginxコンテナのvolumeにも./html:/var/www/htmlを指定しておきます。wordpressを動かしているfpmはphpのみをさばく役割のみ担っているため、cssやjavascript、画像などの静的ファイルはnginxから配信します。 mysql8.0の起動方法に気を付ける 上述した通りで認証方法が変わっているので、mysqlのcommandに--default-authentication-plugin=mysql_native_passwordを指定しておきましょう。 nginx関連の説明 rootとindexのphpファイルを指定する nginxにドキュメントがどこにあるのか、「/」のリクエスト(=index)が飛んできたときに見に行くファイルがindex.htmlでなくindex.phpのリクエストであると指定しておきましょう。何も指定していないとphpファイルを探してくれません。 phpファイルが来た時の流し方を指定する location ~ \.php$で、phpファイルへのリクエストの時にはfastcgiに処理を流すように指定してください。こうすることでwordpressコンテナにphpの処理を任せられるので、nginxは静的ファイルの配信とphpリクエストの誘導のみ行うようになります。 複数台構成のfastcgiへの負荷分散を指定する upstreamで(コンテナ名):(exposeしているポート番号)を複数指定すれば、あとはnginxの方で勝手に負荷分散(ロードバランシング)してくれます。これをfastcgi_passに使うことでnginx1台でfpm複数台へ処理を流す設定完了です。 実際にやってみる mysqlの初期化 最初はデータベースの初期化(1~2分)が必要なので、進捗確認目的もかねてバックグラウンド実行はせずに立ち上げてください。終わったらCtrl+Cを2回くらい押すと停止します。 $ docker-compose up mysql Wordpressファイルの生成 いきなりWordpress複数台立ち上げるとそれぞれのコンテナでWordpressファイルを生成し始めちゃうので、最初は必ず1つだけ立ち上げてください。こちらは進捗がコマンドに出ないので、wp-admin, wp-content, wp-includesが出そろってしばらくファイル生成がなければ停止してください。 $ docker-compose up wp_first 一気に全部立ち上げましょう 負荷分散を実感するため、今回はdocker-compose up -dを使用していません。wp_firstやwp_second、wp_thirdそれぞれにリクエストの処理が割り振られているのを確認できると思います。 $ docker-compose up ブラウザで確認 localhost:3000へアクセス。以上! 参考 wordpress - Docker Hub wordpress/Dockerfile at 1d90641dc2075168fe59df2f02502df068cc5531 · docker-library/wordpress · GitHub php/Dockerfile at 368453e2918b0a4a5ce488cc987c59c30e4ae4be · docker-library/php · GitHub Nginx upstream module - Nginx.org
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Hotpepperのapiを使ってお店の情報を表示してみた

はじめに 転職活動の面接で「WebApi使ったことある?」と聞かれ、全く経験のなかった僕はドラクエのさまようたましいのようにまごまごしてしまったため、この機会に何かのAPIを使ってみようと思いました。 今回はリクルートが提供しているhotpepperのAPIを利用して、我が本拠地横浜のレストラン情報を取得してみました。 そもそもWebAPIとは? APIとは、「Application Programming Interface」の頭文字をとったもので、インターフェースと言う時からも何となくわかるように、プログラムがサービスを利用する窓口のことを言います。 Web APIの場合、プログラムはWeb上に公開され、外部から呼び出して利用します。その多くは無料で私たちも利用することができます。 実際にHotpepperのAPIでレストランの情報を取得して表示してみた 開発環境 PHP:7.3.11 Laravel:7.30.4 Guzzle: 7.3 リクルートWEBサービスへ新規登録 こちらよりメールアドレス のみで無料登録し、APIキーを取得します。登録したメールアドレス宛にすぐ届きます。 laravelのプロジェクトを作成 ターミナルから新たにプロジェクトを作成します。 すでに練習用のプロジェクトがある場合は必要ありません。 $ composer create-project --prefer-dist laravel/laravel hotpepperApi "6.*" Guzzleのインストール Guzzle は、HTTP リクエストの送信を容易にし、Web サービスと簡単に統合できる PHP HTTP クライアントです。 $ composer require guzzlehttp/guzzle APIのの設定をenv.ファイルに追記します。 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" HOTPEPPER_API_KEY=1111a1a1aaaa1a11 // ここにAPI登録時に取得したAPIキーをペースト 本番環境で env.ファイルは読み込まれないので、config配下にhotpepper.phpを作成。 ModelやContorllerから、configメソッドで呼び出せるようにします。 <?php return [ 'api_key' => env('HOTPEPPER_API_KEY') ]; これでconfig('hotpepper.api_key')でどこからでも呼び出せるようになりました。 コントローラーに店舗情報を取得できるよう実装していく GETメソッドでAPIを利用してみます。 HotpepperのAPIを叩いて、横浜のタがついた店舗情報10件取得してみよう 任意のコントーラーを作成して記述してみる。 ひとまずHotpepperControllerを作成。 <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use GuzzleHttp\Client; // ここを追記 class HotpepperController extends Controller { // HTTPリクエストを送るURL private const REQUEST_URL = 'http://webservice.recruit.co.jp/hotpepper/gourmet/v1/'; // APIキー private $api_key; public function index() { // インスタンス生成 $client = new Client(); // HTTPリクエストメソッド $method = 'GET'; // APIキーを取得 $this->api_key = config('hotpepper.api_key'); // APIキーや検索ワードなどの設定を記述 $options = [ 'query' => [ 'key' => config('hotpepper.api_key'), 'keyword' => '浦安', 'count' => 10, 'format' => 'json', ], ]; // HTTPリクエストを送信 $response = $client->request($method, self::REQUEST_URL, $options); // 'format' => 'json'としたのでJSON形式でデータが返ってくるので、連想配列型のオブジェクトに変換 $restaurants = json_decode($response->getBody(), true)['results']; // dd($restaurants); // index.blade.phpを表示する return view('index', compact('restaurants')); } } 以下のように記述することでAPIに対してGETメソッドでHTTP通信を行うことができる $client = new Client(); $response = $client->request([メソッド], [アクセスしたいURL]); ルーティングの設定も忘れずに。 URL/homeだったらHotpperControllerのindexアクションへ web.php Route::get('/home', 'HotpepperController@index'); 店舗情報を取得してみた 結果を表示するviewは、index.blede.phpを新たに作成します。 取得した値は配列に変換しているため、$restrants['name']のような記述で値を表示することが可能です。 <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>ホットペッパーAPI</title> </head> <body> <table border="1"> <tr> <th>店舗名</th> <th>営業時間</th> </tr> @for ($i = 0; $i < $restaurants['results_returned']; $i++) <tr> <td><a href="{{{ $restaurants['shop'][$i]['urls']['pc'] }}}">{{{ $restaurants['shop'][$i]['name'] }}}</a></td> <td>{{{ $restaurants['shop'][$i]['open'] }}}</td> </tr> @endfor </table> </body> </html> こんな感じで取得できました! おわりに はじめてWebAPIを使ってみて、こんな簡単に情報を取得できることに感動しました。 世の中には無料で提供されているAPIがごまんとあるようなので他のものも使ってみたいですね! 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP & SQL for WordPress TIPS

クラスファイルを読み込んだ後にインスタンス化して利用 hoge.php //クラスを読み込む include(plugin_dir_path( __FILE__ )."contents.php"); //クラスをインスタンス化 $C_contents = new contents(); //実行 $C_contents->main_contents(); //-------------------------------------------------------------------- //phpファイルを読み込む include(plugin_dir_path( __FILE__ )."class_info.php"); //登録情報クラスをインスタンス化 $regis_info = new C_info(); //登録テーブル $h_option=$regis_info->h_option; //参照テーブル情報取得 $get_row =$regis_info->get_row ; //テーブル名取得 $table_name =$regis_info->table_name; //キー項目名 $key_name=$regis_info->key_name; class_info.php <?php //接続するテーブルの登録情報を"h_option"から取得するクラス class C_info { //登録テーブル public $h_option; //参照テーブル情報取得 public $get_row; //テーブル名取得 public $table_name; //キー項目名 public $key_name; //テーブル項目取得 public $query; public $colInfo; //テキストエリア項目の取得 public $area; public $txt_area; //コンストラクタ public function __construct() { global $wpdb; //登録テーブル $this->h_option=$wpdb->prefix."h_option"; //参照テーブル情報取得 $this->get_row = $wpdb->get_row("SELECT * FROM $this->h_option WHERE option_id = 1"); //テーブル名取得 $this->table_name = $wpdb->prefix.$this->get_row->table_name; //キー項目名 $this->key_name=$this->get_row->key_name; //テーブル項目取得 $this->query = "DESC $this->table_name"; $this->colInfo = $wpdb->get_col($this->query); //テキストエリア項目の取得 $this->area = $this->get_row->txt_area; $this->txt_area = explode(',', $this->area); } //テーブルのカラムを取得 function read_col($tb_name){ global $wpdb; return $col_list = $wpdb->get_col("DESC $tb_name"); } } $wpdbに含まれるテーブル名を全部取得したい場合 hoge.php function main_contents(){ global$wpdb; foreach($wpdb->tables as $key=>$value){ echo'<p>['.$key.'] => '.$value.'</p>'; } } hoge.php //データベースのテーブル名を取得して配列に入れる処理 $cols = array(); foreach($wpdb->tables as $key=>$value){ array_push($cols,$value); } テーブルのフィールド(カラム)を一覧取得する 以下の例では[shops]テーブルを指定しています hoge.php $query = "DESC $wpdb->shops"; $colInfo = $wpdb->get_col($query); foreach ($colInfo as $col){ echo "<p>$col</p>"; } //-------テーブル名を変数にするときはprefixをつける-------- $query = "DESC $wpdb->prefix$senTbNam"; $check_list = $wpdb->get_col($query); $c_dataform->form_check($check_list); レコード数を取得する xxx.php global $wpdb; $record_count = $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->reviews WHERE field_name ='$atai' form関係 ボタン処理 hoge.php //選択ボタンを押したかどうか $senInfo=filter_input(INPUT_POST,"sentaku"); if($senInfo=="選択") { $senFlg=1; $senTbNam=isset($_POST['テーブルを選択してください']) ? htmlspecialchars($_POST['テーブルを選択してください']) : null; echo $senTbNam; } hoge.html <tbody> <form method="POST" action=""></form> <tr> <!--プルダウンとテキストボックス--> <td style="border-style: none;">テーブル選択:</td> <td style="border-style: none;"><select name="テーブルを選択してください"> <option value=""></option> <option value="posts">posts</option> <option value="comments">comments</option> <option value="links">links</option> <option value="options">options</option> <option value="postmeta">postmeta</option> <option value="terms">terms</option> <option value="term_taxonomy">term_taxonomy</option> <option value="term_relationships">term_relationships</option> <option value="termmeta">termmeta</option> <option value="commentmeta">commentmeta</option> <option value="shops">shops</option> </select> </td> <td style="border-style: none;"><input type="submit" name="sentaku" value="選択"> </td> </tr> </tbody
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP for WordPress TIPS

クラスファイルを読み込んだ後にインスタンス化して利用 hoge.php //クラスを読み込む include(plugin_dir_path( __FILE__ )."contents.php"); //クラスをインスタンス化 $C_contents = new contents(); //実行 $C_contents->main_contents(); //-------------------------------------------------------------------- //phpファイルを読み込む include(plugin_dir_path( __FILE__ )."class_info.php"); //登録情報クラスをインスタンス化 $regis_info = new C_info(); //登録テーブル $h_option=$regis_info->h_option; //参照テーブル情報取得 $get_row =$regis_info->get_row ; //テーブル名取得 $table_name =$regis_info->table_name; //キー項目名 $key_name=$regis_info->key_name; class_info.php <?php //接続するテーブルの登録情報を"h_option"から取得するクラス class C_info { //登録テーブル public $h_option; //参照テーブル情報取得 public $get_row; //テーブル名取得 public $table_name; //キー項目名 public $key_name; //テーブル項目取得 public $query; public $colInfo; //テキストエリア項目の取得 public $area; public $txt_area; //コンストラクタ public function __construct() { global $wpdb; //登録テーブル $this->h_option=$wpdb->prefix."h_option"; //参照テーブル情報取得 $this->get_row = $wpdb->get_row("SELECT * FROM $this->h_option WHERE option_id = 1"); //テーブル名取得 $this->table_name = $wpdb->prefix.$this->get_row->table_name; //キー項目名 $this->key_name=$this->get_row->key_name; //テーブル項目取得 $this->query = "DESC $this->table_name"; $this->colInfo = $wpdb->get_col($this->query); //テキストエリア項目の取得 $this->area = $this->get_row->txt_area; $this->txt_area = explode(',', $this->area); } //テーブルのカラムを取得 function read_col($tb_name){ global $wpdb; return $col_list = $wpdb->get_col("DESC $tb_name"); } } $wpdbに含まれるテーブル名を全部取得したい場合 hoge.php function main_contents(){ global$wpdb; foreach($wpdb->tables as $key=>$value){ echo'<p>['.$key.'] => '.$value.'</p>'; } } hoge.php //データベースのテーブル名を取得して配列に入れる処理 $cols = array(); foreach($wpdb->tables as $key=>$value){ array_push($cols,$value); } テーブルのフィールド(カラム)を一覧取得する 以下の例では[shops]テーブルを指定しています hoge.php $query = "DESC $wpdb->shops"; $colInfo = $wpdb->get_col($query); foreach ($colInfo as $col){ echo "<p>$col</p>"; } //-------テーブル名を変数にするときはprefixをつける-------- $query = "DESC $wpdb->prefix$senTbNam"; $check_list = $wpdb->get_col($query); $c_dataform->form_check($check_list); form関係 ボタン処理 hoge.php //選択ボタンを押したかどうか $senInfo=filter_input(INPUT_POST,"sentaku"); if($senInfo=="選択") { $senFlg=1; $senTbNam=isset($_POST['テーブルを選択してください']) ? htmlspecialchars($_POST['テーブルを選択してください']) : null; echo $senTbNam; } hoge.html <tbody> <form method="POST" action=""></form> <tr> <!--プルダウンとテキストボックス--> <td style="border-style: none;">テーブル選択:</td> <td style="border-style: none;"><select name="テーブルを選択してください"> <option value=""></option> <option value="posts">posts</option> <option value="comments">comments</option> <option value="links">links</option> <option value="options">options</option> <option value="postmeta">postmeta</option> <option value="terms">terms</option> <option value="term_taxonomy">term_taxonomy</option> <option value="term_relationships">term_relationships</option> <option value="termmeta">termmeta</option> <option value="commentmeta">commentmeta</option> <option value="shops">shops</option> </select> </td> <td style="border-style: none;"><input type="submit" name="sentaku" value="選択"> </td> </tr> </tbody
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Larabelポートフォリオ作成#1

Larabelポートフォリオ作成#1 ポートフォリオ作成記録 現在プログラミングスクールに通っていてカリキュラムが終了したので転職活動用のポートフォリオを作成することにしました。 こまめにポートフォリオ作成記録をつけていきたいと思います 作るもの 作るものは色々迷いましたが勤怠管理アプリを作ることにしました。 ここまででやっていること ・Laravelの開発環境 ・git、GitHubで開発管理準備 終わりに これからGitHubで擬似チーム開発のようにしながらポートフォリオを作成していきます
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

主キー以外を外部キーに設定する時に詰まった話

お久しぶりです、やすのりです! 今日はLaravelでテーブル作成時に詰まったので備忘録も兼ねて記事を書いていきたいと思います。 結論 外部キーと参照元カラムの設定は同じにしよう!! 何があったのか? 業務上でLaravelアプリを使用していますが、その中で新しいテーブルを3つ作って全部リレーションさせて、尚且つその内の2つは『外部キーの参照元を主キー以外に設定』する仕様になっていました。 1つ目のテーブルを作成するのは難なくできましたが...2つ目のテーブルを作成する時にエラーが出まくりました! SQLSTATE[HY000]: General error: 1215 Cannot add foreign key constraint (S QL: alter table `test2` add constraint `test2_test1_id_foreign` foreign key (`test1_id`) references `test1` (`test1_id`)) ※カラム名は仮称にしてあります。 とSQLの『test2テーブルのtest1_idは外部キー設定が出来ませんよ!!』というエラーが出てきました... このエラーでよく見かける解決ほうに『主キーの型と外部キーの型が間違っている』というものがありました。 そして実際のコードを見てみましょう。 public function up() { Schema::create('test1', function (Blueprint $table) { $table->increments('id'); $table->integer('user_id')->unsigned(); $table->integer('test1_id')->unsigned()->unique(); $table->timestamps(); $table->foreign('user_id') ->references('id') ->on('users') ->onDelete('cascade'); }); } public function up() { Schema::create('test2', function (Blueprint $table) { $table->increments('id'); $table->integer('test1_id'); $table->integer('test2')->unique(); $table->timestamps(); $table->foreign('test1_id') ->references('test1_id') ->on('test1'); }); } はい、この時点で分かる人には分かりますね。 そう、『test1テーブルのtest1_idに'符号なし'を意味する'unsigned'を設定しているのに、test2テーブルの外部キーである'test1_id'には設定していない』からでした! 今思えば単純なことだし、初歩的なことなんですけど... エラーが発生している時は『主キーの型と外部キーの型が間違っている』という部分に引っ張られて『え〜?主キーである'id'と'test1_id'は同じinteger型なんだけどなぁ』とか思っていました... いや、普通はそれでいいんだけど、今回は主キー以外を外部キーにしてるから『外部キーと参照元のカラムが同じ型かどうか?』を確認しないとダメでした! いやぁ、意外と初歩的なことって間違えちゃいますね!! ってことで型が間違ってないか分かりやすくするためにさっきまで使用していた『unsigned』という記述ではなくて、そもそもの型の記述を『unsignedInteger』に変更しちゃいました! public function up() { Schema::create('test1', function (Blueprint $table) { $table->increments('id'); $table->unsignedInteger('user_id'); $table->unsignedInteger('test1_id')->unique(); $table->timestamps(); $table->foreign('user_id') ->references('id') ->on('users') ->onDelete('cascade'); }); } public function up() { Schema::create('test2', function (Blueprint $table) { $table->increments('id'); $table->unsignedInteger('test1_id'); $table->unsignedInteger('test2')->unique(); $table->timestamps(); $table->foreign('test1_id') ->references('test1_id') ->on('test1'); }); } これならパッと見で違ってたらわかるでしょう!! 感想 みなさん、初心は忘れないようにしましょう... お兄さんとの約束ですよ!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む