- 投稿日:2021-05-16T19:21:48+09:00
【Laravel】初めてのAWSデプロイ - 3~5分ごとにヘルパー関数が生成するリンクがHTTPとHTTPSで切り替わってしまう問題編 -
Laravel + vue.jsで作成したポートフォリオをデプロイするために初めてAWSを利用したところ様々な問題にぶつかり、解決に時間を要しました。 発生した問題 JSファイルやCSSファイルが正しく読み込まれない問題 ブラウザを問わず、数分毎にHTTPとHTTPSが切り替わってしまう問題(★ 本記事) 前回、JSファイルやCSSファイルが正しく読み込まれない問題を無事に解決しましたが、次なる問題が発生しました。 3~5分ごとにasset()関数が生成するリンクのHTTPとHTTPSが切り替わってしまう問題 タイトルだけだと何のことやらと思います。 前回、無事に信用するプロキシを記述し、ヘルパー関数が生成するリンクをhttpsにすることに成功。無事にJSやCSSが読み込まれた...と思いきや 「別のブラウザでもちゃんと表示されるのか確認しよう!」 そしてfirefox、Edge、Safariで開いてみた結果... フ ァ イ ル が 読 み 込 ま れ て い な い JSファイルもCSSファイルも読み込まれず、テキストだけの寂しい画面が現れました。Chromeではちゃんと読み込んでくれていたというのに一体どういうことなのでしょう。 今まで無事に読み込まれていたGoogleChromeも更新してみると なんとChromeもダメになってしまいました。ほんの5分前には正しく読み込まれていたというのに... 今までhttpsでリンクを作ってくれていたヘルパー関数が、元のhttpによるリンクを生成してしまっていたのです。 キャッシュの問題でしょうか...?しかしながら最初に開いたブラウザにキャッシュも何もないはず... 理由が解明されないままF5でページのリロードを繰り返した結果 3~5分程度経過すると再度asset()がhttpsで読み込むようになったのです。 症状のまとめ 3~5分のスパンでasset()が生成するリンクがhttpとhttpsで切り替わる。 この症状は、ブラウザを問わず発生する。 生成するリンクのスパンはブラウザごとに独立している(例えば、Chromeでhttpsリンクが生成されている時でも、safariではhttpリンクになっていることがある、など。) 時間差でリンク生成が切り替わるということなんて聞いたことがなく、検索をかけてもそれらしき記事やまとめが見つかりませんでした。 そのため出来ることを1つ1つ試して解決に繋げようと模索しました。 試したこと1 リンクの強制HTTPS化 前回信用するプロキシとしてapp/Http/Middleware/TrustProxies.phpへの記述を行いましたが、もしかしたら不十分だったかもしれないと考えました。 [Laravel]常時SSLなアプリケーションでのURL生成のベストプラクティスを考える laravelでリンクがhttpsにならない そこで上記記事を参考にし、forceSchemeにより本番環境の場合にリンク生成をhttpsとするように設定しました。 app/Providers/AppServiceProvider.php <?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Illuminate\Routing\UrlGenerator; // 追記 class AppServiceProvider extends ServiceProvider { // 中略 /** * Bootstrap any application services. * * @return void */ public function boot(UrlGenerator $url) { // ★ 以下を追記!!! // 本番環境の時、URLをhttpsにする if (config('app.env') === 'production') { $url->forceScheme('https'); } } } 結果: ダメでした 状況は変わらず、3~5分のスパンでaseet()はhttpになったりhttpsになったり... 原因はどうもここではないようです。 試したこと2 APP_URLの修正 .envファイルのAPP_URLを修正すればもしかしたら正しく動作するかもしれません。 早速本番環境の.envファイルを修正していきます。 // 修正前 APP_URL=http://xxx.xx.x.xx // 修正後 APP_URL=https://xxx.xx.x.xx .envファイルを修正した後は以下コマンドでキャッシュをリフレッシュして、その後ブラウザをリロードします。 $ php artisan config:cache 結果: ダメでした httpsのリンクすら生成されなくなってしまいました。どうやら原因はここではなかったようです。 .envファイルは元に戻します。 試したこと3 そもそも本番環境になっているかのチェック 「試したこと1」で記述したapp/Providers/AppServiceProvider.phpを眺める中でふと思いました。 // 本番環境の時、URLをhttpsにする if (config('app.env') === 'production') 「そもそも本番環境になっているのか...?」 いや、まさか、そんなはずはない。 そう思いつつも、再び.envファイルを確認してみると... .env APP_ENV=local やってしまっていました...。デプロイしておきながら、ローカル環境のまま動いていました。 とんでもないことです。 早速修正します。 .env APP_ENV=production $ php artisan config:cache ブラウザをロードしてみると... 無事に表示されました! この後別のブラウザで時間をかけて何度もリロードしましたが、asser()がhttpリンクを生成することはなくなりました! 原因に気付けたもう1つの要因 原因に気付くことができた理由は実はもう1つあります。 私はローカル環境の時、SQLが発行された際にそのSQL文をログファイルに乗るように設定していました。 app/Providers/AppServiceProvider.php <?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Illuminate\Routing\UrlGenerator; class AppServiceProvider extends ServiceProvider { // 中略 /** * Bootstrap any application services. * * @return void */ public function boot(UrlGenerator $url) { // 開発環境の時、SQLをログに表示する if (config('app.env') !== 'production') { \DB::listen(function ($query) { \Log::info("Query Time:{$query->time}s] $query->sql"); }); } // 本番環境の時、URLをhttpsにする if (config('app.env') === 'production') { $url->forceScheme('https'); } } } そして、原因が一向に掴めない私は、Laravelプロジェクトのlogファイル(storage/logs/laravel.log)を確認していました。 その中で、 (ローカルでしか表示されないはずのSQLログが表示されている...?) と気付けたところから解決に結びつけることができました。 調査しきれていないところ 結局のところ、裏でどういうロジックが働いて、時間経過によってasset()が生成するリンクがhttpになったりhttpsになったりするのかというのはわかりませんでした。 引き続き調査を行いますが、こういったところが怪しいといったものがあればご指摘いただけると幸いです。 まとめ しっかり.envファイルは確認すること、どんな小さなことからも原因を探すことが出来るということを学ぶことができたと思います。 次にデプロイする機会があればこういった部分もしっかりチェックしていきます。
- 投稿日:2021-05-16T17:39:53+09:00
[Laravel]エラーを返す方法
はじめに こんにちは! タイトルの通り、エラーを発生させる方法を紹介します。 個人的な用途としては、意図的に401や404などのエラーを発生させることでHandler.phpで処理してもらうために調べました。 方法1 abort() Laravelのヘルパー関数のabortを使います。 abort(401) これだけで401のエラーを発生させることができます。 方法2 abort_if() or abort_unless() こちらもLaravelのヘルパー関数のabort_if/abort_unlessを使います。 abort_ifは評価式がtrueの場合に、abort_unlessはfalseの場合にHTTP例外を投げます。 abort_if(true, 404); abort_unless(false, 404); 方法3 HttpException vendor/symfony/http-kernel/Excptionにある〜HttpExcptionを使います。 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; // 省略 throw new AccessDeniedHttpException(); 個人的には、引数が必要なものもあり少し使いづらい印象があります。 参考 Helper Functions
- 投稿日:2021-05-16T16:21:39+09:00
【Laravel】Laravelでプロジェクトを作成する手順
Laravelでプロジェクトを作成し開発を進めていく手順をまとめておきます。 諸々のインストール手順などは割愛します。 laravelプロジェクトの作成 $ compoaser create-project laravel/laravel app このコマンドでプロジェクトの作成ができる。 $ php artisan serve このコマンドでlocalhostでサーバーが起動される。
- 投稿日:2021-05-16T16:21:16+09:00
OctoberCMS 管理画面実装テク:シンプルなリソースの権限管理を実装する
概要 先日こちらの記事でLaravel Authorization Policyを使った管理画面におけるリソースアクセス制限について書きましたが、シンプルな権限管理であればモデルのイベントフックの方がよっぽどシンプルに実装できたのでメモ。Backend.Behaviors.FormControllerを実装しているコントローラを対象とします。 やり方 モデルイベントをフック 下記の例ではProductというモデルデータがロードされたタイミング(model.afterFetchイベント)で Product::extend(function ($model) use ($user) { $model->bindEvent('model.afterFetch', function () use ($model, $user) { if (/* $modelと$userをつかって権限チェックをするロジックをここに記述 */) { // アクセス権がないときにtrueになるようにして、アクセス権なしだと例外を投げる throw new NoAccessRightToResourceException("アクセス権がありません"); } }); }); ドキュメントではモデルのイベントフック登録は基本的に Plugin::boot() に記述すると書かれているが、結局は使用される前に登録されていればよい。本記事の場合、管理画面でのみ使用したいので、管理画面のコントローラのコンストラクタに記述すれば十分間に合う。 そして、権限がなかった時の処理を例外ハンドラとして登録する。こちらも同様にコンストラクタに記述するので十分。 App::error(function (NoAccessRightToResourceException $exception) { // アクセス権がない時の処理 Flash::error($exception->getMessage()); }); 基本的にはこれだけなのだが、これだけだと例外が上記で登録した例外ハンドラまで届かない場合がある。ここが落とし穴。 \Backend\Behaviors\FormController::update を見ると catch (Exception $ex) で全ての例外がキャッチされて$this->controller->handleError($ex); でハンドルされてしまっている。つまり、このままだと上記のようにApp::errorで登録したハンドラまで例外が届かない。なので、このhandleErrorメソッドをオーバーライドしてハンドラに登録した例外の場合はthrowし直すようにする。 public function handleError($exception){ if ($exception instanceof NoAccessRightToResourceException) { // 例外を投げ直してApp::errorで登録したハンドラでハンドルさせる throw $exception; } else { parent::handleError($exception); } } 以上
- 投稿日:2021-05-16T15:25:14+09:00
Laravelで作成したAPIをフロントエンドで受け取り画面へ描画する
前提 今回はaxiosを利用して下記URLで作成した一覧取得のapiを受け取って表示するまでを行います。 LaravelでAPIを作成 axiosとは... バックエンドとのデータのやり取りを簡単にできるライブラリです。 下記コマンドで取得するAPIのパスを確認する php artisan route:list axiosを利用してみる tsxファイルに下記を記述する。 import React, { useEffect, useState } from "react" import axios from "axios" # 型を定義する type Hoge = { id: number hoge: string fuga: boolean created_at: Date updated_at: Date } const HogePage: React.VFC = () => { const [hoges, setHoges] = useState<Hoge[]>([]) # apiを取得する const getHoges = async () => { const { data } = await axios.get<Hoge[]>('api/hoges') console.log(data) setHoges(data) } # ページにアクセスしたら実行する useEffect(() => { getHoges }) } return ( <> <div clasName="inner"> <ul className="hoge-list"> {hoges.map(hoge => ( <li key={hoge.id}> <label className="checkbox-label"> <input type="checkbox" className="checkbox-input" /> </label> <div><span>{task.title}</span</div> </li> )) } </ul> </div> <> 下記の記事のようにreactRouterのルーティングになっている前提でlocalhost:8000//hogesへアクセスすると... Laravelで作成した投稿一覧APIを取得して画面に描画することができました! Laravel環境でreactRouterのルーティングを使用できるようにする
- 投稿日:2021-05-16T15:18:58+09:00
Laravel環境でreactRouterのルーティングを使用できるようにする
前提 reactRouterとTypeScriptで使用するための定義ファイルのライブラリをインストールする。 $ npm i -D react-router-dom @types/react-router-dom reactRouterの定義ファイルを作成する touch resources/ts/router.tsx import React from 'react' import { BrowserRouter, Switch, Route, Link } from "react-router-dom" import TaskPage from './pages/hoges' const Router = () => { return ( <BrowserRouter> <Switch> <Route path="/"> <HogePage /> </Route> </Switch> </BrowserRouter> ); } export default Router resources/ts/index.tsxへ下記を追記する import Router from "./router" ReactDom.render( <Router /> document.getElementById('app') ) 現在LaravelのRouterと競合してhomeディレクトリへ遷移してもreactRouterのRouteが反映されないのでweb.phpを下記のように編集する Route::get('{all}', function () { return view('index'); })->where(['all' => '.*']); reactRouterで遷移できるようになりました!
- 投稿日:2021-05-16T15:17:23+09:00
Stripe CLIを利用して、Webhookを試す(Laravel版)
homebrewでstripe cliをインストール。 brew install stripe/stripe-cli/stripe ログインする。 stripe login 初回時は下記が表示されるので、エンターを押す。 Press Enter to open the browser (^C to quit) ブラウザが開くので、そちらで許可する。 次にstripe listenを実行。localhost:8000/hooksはエンドポイントのURLを指定。 stripe listen --forward-to localhost:8000/hooks --latest ちなみにイベントをLaravel側に転送したくない場合は、stripe listenだけで実行すればログだけ確認できて便利だよ~~~ ターミナルの別タブでAPIリクエストを発行。 stripe payment_intents create --amount=100 --currency=jpy ちなみに簡易的にしたいイベントをトリガーしたい場合は下記の方法もあり。 stripe trigger payment_intent.created これで、リッスンしてる側でログが出力される。 csrf保護を外す必要があるので、下記のように記述。 app/Http/Middleware/VerifyCsrfToken.php protected $except = [ 'stripe/*', ];
- 投稿日:2021-05-16T13:27:17+09:00
Laravelで作成したAPIをテストする
前提 下記に対してPHPUnitでテストを作成していきます。 LaravelでAPIを作成する テストの設定 phpunit.xmlの設定 <server name="DB_DATABASE" value="TodoApp_testing"/> 上記のように記述されているため下記コマンドでテスト用の.envファイルを作成する cp .env .env.testing .env.testingに下記を記述する。(テスト用のデータベースを設定する) DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=HogeApp_testing DB_USERNAME=root DB_PASSWORD= テストの作成 テストディレクトリには以下の2つがあるが今回はAPIのテストなので①のテストを作成する ①Feature...一つのリクエストから始まる一連の流れをテスト ②Unit...クラスや関数の機能テスト <?php namespace Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; use App\Models\Hoge; class HogeTest extends TestCase { use RefreshDatabase; // データのキャッシュを消す /** * @test * 上記のように記述することでメソッド名にtestを付けなくて良い */ public function CanGetAList() { $hoges = Hoge::factory()->count(10)->create(); $response = $this->getJson('api/hoges'); $response ->assertOk() ->assertJsonCount($hoges->count()); } /** * @test */ public function CanRegister() { $data = [ 'title' => 'テスト投稿' ]; $response = $this->postJson('api/hoges', $data); $response ->assertCreated() ->assertJsonFragment($data); } /** * @test */ public function CanBeUpdated() { $hoge = Hoge::factory()->create(); $hoge->title = '書き換え'; $response = $this->patchJson("api/hoges/{$hoge->id}", $hoge->toArray()); $response ->assertOk() ->assertJsonFragment($hoge->toArray()); } /** * @test */ public function CanBeDeleted() { $hoges = Hoge::factory()->count(10)->create(); $response = $this->deleteJson("api/hoges/1"); $response->assertOk(); $response = $this->getJson("api/hoges"); $response->assertJsonCount($hoges->count() -1); } } テストは下記のコマンドで実行する ./vendor/bin/phpunit tests/Feature/【テストファイル名】 実行するとpassしているのが分かります! (もし全体のテストを実行してpassしなかった場合は1メソッドずつに分割して試してみてください。)
- 投稿日:2021-05-16T11:53:02+09:00
LaravelでAPIを作成する
前提 LaravelでAPIを作成していきます 今回は一覧取得/新規登録/更新/削除を実装します。 前提としてはhogeコントローラーとhogeモデル、hogeテーブルが作成されている前提です controllerで個々にインポートしなくても良くなる設定 app/Providers/RouteServiceProvider.phpへ以下のコメントアウトを解除する protected $namespace = 'App\\Http\\Controllers'; DBは下記のマイグレーションファイルで作成しておきデータも数件入れておく Schema::create('hoges', function (Blueprint $table) { $table->id(); $table->string('hoge'); $table->boolean('fuga')->default(false); $table->timestamps(); }); コントローラーの編集 app/Http/Controllers/HogeController.phpへ以下を追記する <?php namespace App\Http\Controllers; use App\Models\Task; use App\Http\Requests\HogeRequest; use Illuminate\Http\Request; class HogeController extends Controller { /** * Display a listing of the resource. * 一覧取得 * @return \Illuminate\Support\Collection */ public function index() { return Task::orderByDesc('id')->get(); } /** * Store a newly created resource in storage. * 新規登録 * @param HogeRequest $request * @return \Illuminate\Http\JsonResponse */ public function store(HogeRequest $request) { $task = Hoge::create($request->all()); return $task ? response()->json($task, 201) : response()->json([], 500); } /** * Update the specified resource in storage. * 更新 * @param \Illuminate\Http\TaskRequest $request * @param \App\Models\Hoge $hoge * @return \Illuminate\Http\JsonResponse */ public function update(HogeRequest $request, Hoge $hoge) { $hoge->title = $request->title; return $hoge->update() ? response()->json($hoge) : response()->json([], 500); } /** * Remove the specified resource from storage. * 削除 * @param \App\Models\Hoge $hoge * @return \Illuminate\Http\JsonResponse */ public function destroy(Hoge $hoge) { return $hoge->delete() ? response()->json($hoge) : response()->json([], 500); } } モデルの編集 app/Models/Hoge.phpへ以下を追記する。 # fillable内にはDBのデータを触るカラムを記述する。 protected $fillable = [ 'hoge', 'fuga', ]; ルートの編集 routes/api.phpへ以下を追記する。 Route::apiResource('hoges', 'HogeController'); 下記のURLにアクセスすると一覧取得のAPIが表示されます! localhost:8000/api/hoges
- 投稿日:2021-05-16T11:37:24+09:00
データベースを設計する前に!
データベースを設計する前に データベースは多くの場合、アプリケーション本体よりも長く付き合っていくものになります。(開発している段階から使用する+他のアプリケーションでもそのデータベースを使う可能性があるため) 適当な名前をつけたり、一貫性のない名前の付け方をすると、読みづらくなるだけではなくトラブルの温床になります。自分だけしか利用しない場合であっても、以下の点を最低限考慮して設計すると良いでしょう。 予約語 データベースのテーブル名やカラム名などを決める際にまず考えなくてはいけないのは、使用したい言葉が予約語にないものかどうかを確認をすることです。予約語にその単語がある場合、SQLコマンドが正常に動作しない場合があります。 例えば、PHPだと使用するmigrationやseedingをartisanコマンドで実行した場合には、うまくいくけれど、その時発行されるSQL文を直接コンソールから入力すると予約語のエラーになるなどして、トラブルの原因特定が難しくなります。そのため、設計の段階から予約語は使わないように心がけましょう。 実際に使ってしまった予約語 column order こちらでMySQL公式サイトから予約語の確認ができます ページ内検索をかけて確認しましょう テーブル名は複数形 基本的にはテーブルに複数のカラムが入ることになるので、入る名称の複数形をつけることが一般的です。 例えばPHPでLaravelを使用しているとき、Modelクラスで定義したクラス名(単数形)とデータベース側のテーブル名の複数形が紐づくようになっているため、特別な理由がなければそのルールに従いましょう。 例)Model:Userクラス→Database:usersテーブル ※余談ですがLaravelの場合、「複数形」は英語のルールに則ったものになります。 例) box->boxes 単語と単語の間は「_」で区切ろう データベース内では大文字小文字が基本的に区別されません。 そのため、単語と単語の間は「_」で区切るような名前をつけると良いでしょう。この形式を「スネークケース」という。 例) user_id 外部キーを設定するときには、「テーブル名+_+id」で指定しよう テーブル同士に関連性を持たせる場合、外部キーなどを設定することがあります。多くの場合、外部キーの制約はPRIMARY KEYとなるidに対する制約をすることが多いでしょう。カラム名をみただけでどのテーブルの何カラムに対する外部キーなのかがわかるようにしましょう。 例) usersテーブルのidカラムに対する外部キーをuser_infoテーブルに設定する場合 usersテーブル user_infoテーブル id id account name password address users_id
- 投稿日:2021-05-16T11:37:24+09:00
データベースを設計する前に
データベースを設計する前に データベースは多くの場合、アプリケーション本体よりも長く付き合っていくものになります。(開発している段階から使用する+他のアプリケーションでもそのデータベースを使う可能性があるため) 適当な名前をつけたり、一貫性のない名前の付け方をすると、読みづらくなるだけではなくトラブルの温床になります。自分だけしか利用しない場合であっても、以下の点を最低限考慮して設計すると良いでしょう。 予約語 データベースのテーブル名やカラム名などを決める際にまず考えなくてはいけないのは、使用したい言葉が予約語にないものかどうかを確認をすることです。予約語にその単語がある場合、SQLコマンドが正常に動作しない場合があります。 例えば、PHPだと使用するmigrationやseedingをartisanコマンドで実行した場合には、うまくいくけれど、その時発行されるSQL文を直接コンソールから入力すると予約語のエラーになるなどして、トラブルの原因特定が難しくなります。そのため、設計の段階から予約語は使わないように心がけましょう。 実際に使ってしまった予約語 column order こちらでMySQL公式サイトから予約語の確認ができます ページ内検索をかけて確認しましょう テーブル名は複数形 基本的にはテーブルに複数のカラムが入ることになるので、入る名称の複数形をつけることが一般的です。 例えばPHPでLaravelを使用しているとき、Modelクラスで定義したクラス名(単数形)とデータベース側のテーブル名の複数形が紐づくようになっているため、特別な理由がなければそのルールに従いましょう。 例)Model:Userクラス→Database:usersテーブル ※余談ですがLaravelの場合、「複数形」は英語のルールに則ったものになります。 例) box->boxes 単語と単語の間は「_」で区切ろう データベース内では大文字小文字が基本的に区別されません。 そのため、単語と単語の間は「_」で区切るような名前をつけると良いでしょう。この形式を「スネークケース」という。 例) user_id 外部キーを設定するときには、「テーブル名+_+id」で指定しよう テーブル同士に関連性を持たせる場合、外部キーなどを設定することがあります。多くの場合、外部キーの制約はPRIMARY KEYとなるidに対する制約をすることが多いでしょう。カラム名をみただけでどのテーブルの何カラムに対する外部キーなのかがわかるようにしましょう。 例) usersテーブルのidカラムに対する外部キーをuser_infoテーブルに設定する場合 usersテーブル user_infoテーブル id id account name password address users_id
- 投稿日:2021-05-16T11:04:06+09:00
PHP・Laravelで検索機能を実装(備忘録)
概要 PHP・Laravelで簡単な記事投稿アプリを作成してます。 検索機能を実装したので、備忘録として残しておきます。 ビュー index.blade.php(formタグのみ記載) <form method="GET" action="{{ route('articles.index') }}" class="d-flex"> <input class="form-control me-2" name="search" type="search" placeholder="検索" aria-label="Search"> <button class="btn btn-outline-success" type="submit">検索する</button> </form> bootstrapのナビバーに検索フォームがあるので、該当箇所をコピペしてください。 データを渡すにはname属性が必要になるので、name="search"を追記します。 また、formでキーワードを取得するので、formタグの中にmethod(データベースに保存はしないので"GET"でOK)とaction(ルートを指定)を追記します。 コントローラー ArticleController.php(indexアクションのみ記載) public function index(Request $request) { $search = $request->input('search'); $query = Article::query(); if (!empty($search)) { $query->where('title', 'LIKE', "%{$search}%") ->orWhere('body', 'LIKE', "%{$search}%"); } $articles = $query->get()->sortByDesc('created_at'); return view('articles.index', ['articles' => $articles]); } formの値を持ってくるために、Requestクラスのインスタンスをindexアクションの引数に渡しています。 $search = $request->input('search');では、index.blade.phpに書いたname="search"からデータを持ってきて、変数に代入します。 (indexアクションの一部を抜粋) $query = Article::query(); if (!empty($search)) { $query->where('title', 'LIKE', "%{$search}%") ->orWhere('body', 'LIKE', "%{$search}%"); } $articles = $query->get()->sortByDesc('created_at'); ここでクエリビルダでクエリを組み立てています。 $searchが空ではない場合は、whereメソッドを実行します。 ・第1引数・・・検索するテーブル名を指定する。 ・第2引数・・・曖昧検索をするためにオペレーターとしてLIKEを使用する。 ・第3引数・・・変数の前後に%を入れて部分一致を行う。%は曖昧検索を指示する記号のことで、0文字以上の任意の文字列を表す。 最後にgetメソッドで構成してきたクエリによるデータを取得し、作成日時が新しい順に並べています。 参考記事 Laravelで検索機能の実装 [基本] Laravel 6.x データベース:クエリビルダ 【SQL】意外と簡単!これならわかるLIKE句でのあいまい検索 【Laravel】検索機能の実装 laravelで検索機能を実装する 補足情報 PHP 7.4.1 Laravel 6.20.26
- 投稿日:2021-05-16T01:34:57+09:00
Yamlファイルを直接手書きすることからの脱却
はじめに OSSライブラリなどを使っていると定義はYaml形式で記述するというものが多いですが、手書きするのが面倒くさく視認性があまり良くないのが弱点です。 当記事は、そんな悩みを解消する為のLaravel製ライブラリ「spread-sheet-converter」の解説記事です。 ※spread-sheet-converterのREADMEの翻訳及び使い方記事です 使用ライブラリ spread-sheet-converter https://github.com/stepupdream/spread-sheet-converter 要件 スプレッドシートコンバータアプリケーションの要件は次のとおりです。 PHP-サポートされているバージョン:>= 7.3 Laravel-サポートされているバージョン:>= 6.0 インストール composer require --dev stepupdream/spread-sheet-converter 準備 このライブラリはGoogleSheetAPIを使用してスプレッドシートの内容を参照しにいくため、API使用のための設定ファイルを事前に用意する必要があります。 API実行のための設定ファイルを用意する https://blog.capilano-fw.com/?p=1816 こちらのサイトなどを参考にcredentials.jsonファイルを用意し、storage/app/json/フォルダに入れます。 スプレッドシートの編集メンバーにAPI対象を含める https://blog.capilano-fw.com/?p=1816#i-3 こちらのサイトを参考にyaml化したい定義が書かれているスプレッドシートの閲覧メンバーに1で用意したJSONファイルの中に書かれているクライアント・メールアドレスを入力します。 yaml化したいスプレッドシートのIDを控えておく https://blog.capilano-fw.com/?p=1816#ID こちらのサイトを参考にyaml化したい定義が書かれているスプレッドシートのIDを控えておきます。 定義ファイル php artisan vendor:publish コマンドを実行し、spread-sheet-converterのconfigファイル及びサンプルテンプレートファイルをローカル側に出力します。 なお、テンプレート定義はbladeファイルであるため、bladeテンプレートの専用関数が使用可能です。 スプレッドシートに記載した内容を自由に改変した状態でYaml化することができます。 configファイル // ↑の準備で用意したjsonファイルの保存場所を指定する。配置場所を変えていなければ基本的には変更不要 'credentials_path' => storage_path('app/json/credentials.json'), // API定義をyaml出力する場合に使用する。FormRequestルールを定義している列名を指定する。基本的には変更不要 'request_rule_column_name' => 'RequestRule', // API定義をyaml出力する場合に使用する。FormRequestルールを定義しているシート名を指定する。基本的には変更不要 'request_rule_sheet_name' => 'RequestRule', // 読み込むスプレッドシートを配列形式で設定する 'read_spread_sheets' => [ [ // Yaml化したいスプレッドシートのIDを指定する(↑の準備で控えたID) // ※実装中のシステムがオープンソースの場合はシートIDはenvファイル化しておきgit管理外とすることを推奨 'sheet_id' => env('READ_SHEET_ID_01', '***************************'), // 該当のスプレッドシートを管理するためのユニークな名前を指定する。 // Yamlファイルの出力コマンドの引数に使用します。 // ※スプレッドシートのタイトル名と同名にすることを推奨 'category_name' => 'MasterData', // スプレッドシートがどのような記述方法で書かれているかを指定します // 当該ライブラリは3種類のタイプに対応しています。("SingleGroup"または "MultiGroup"または "Other") 'read_type' => 'SingleGroup', // Yaml出力に使用するbladeファイルのファイル名を指定します // bladeファイルは自由に追加することも可能です。自分で作った場合はそのファイル名を指定します // ※resouces/DefintionDocument以下で管理されています 'use_blade' => 'single', // Yamlファイルを出力するディレクトリ先を指定します 'output_directory_path' => base_path('definition_document/database/master_data'), // 定義の記述の親と子の区切りとなっている列名を指定します 'separation_key' => 'ColumnName', // 定義の記述の子グループの区分けをするための列名を指定します // ※separation_keyと同じものを指定すること推奨 'attribute_group_column_name' => null, ], ] 定義サンプル SingleGroupパターン // $parentAttribute->sheetName()にはスプレッドシートのシート名が格納されています // $parentAttribute->spreadsheetCategoryName()にはconfigで指定したcategory_nameが格納されています // $parentAttribute->parentAttributeDetails()には親側のデータの配列が格納されています // $parentAttribute->getAttributesGroupByKeyName('*')には子側のデータの配列が格納されています - name: '{{ $parentAttribute->parentAttributeDetails()['TableName'] }}' database_directory_name: '{{ $parentAttribute->spreadsheetCategoryName() }}' connection_name: '{{ $parentAttribute->parentAttributeDetails()['ConnectionName'] }}' domain_group: '{{ $parentAttribute->sheetName() }}' description: '{{ $parentAttribute->parentAttributeDetails()['TableDescription'] }}' columns: @foreach($parentAttribute->getAttributesGroupByKeyName('*') as $attribute) - name: '{{ $attribute->attributeDetails()['ColumnName'] }}' description: '{{ $attribute->attributeDetails()['ColumnDescription'] }}' data_type: '{{ $attribute->attributeDetails()['DataType'] }}' migration_data_type: '{{ $attribute->attributeDetails()['MigrationDataType'] }}' is_real_column: {{ $attribute->attributeDetails()['IsRealColumn'] }} is_unsigned: {{ $attribute->attributeDetails()['IsUnsigned'] }} is_nullable: {{ $attribute->attributeDetails()['IsNullable'] }} @endforeach SingleGroupパターン // $parentAttribute->sheetName()にはスプレッドシートのシート名が格納されています // $parentAttribute->spreadsheetCategoryName()にはconfigで指定したcategory_nameが格納されています // $parentAttribute->parentAttributeDetails()には親側のデータの配列が格納されています // $parentAttribute->getAttributesGroupByKeyNameでは、引数に指定した子グループの配列を取得できます // $request_attribute->ruleMessage()には、'RequestRule'に対応したルールメッセージが格納されています - controller_name: '{{ Str::studly($parentAttribute->sheetName()) }}' route_prefix: '{{ Str::snake($parentAttribute->spreadsheetCategoryName()) }}' http_method: '{{ Str::studly($parentAttribute->parentAttributeDetails()['HttpMethod']) }}' name: '{{ Str::studly($parentAttribute->parentAttributeDetails()['ApiName']) }}' description: '{{ $parentAttribute->parentAttributeDetails()['HttpDescription'] }}' request: columns: @foreach($parentAttribute->getAttributesGroupByKeyName('Request') as $request_attribute) - name: '{{ $request_attribute->attributeDetails()['ColumnName'] }}' description: '{{ $request_attribute->attributeDetails()['ColumnDescription'] }}' data_type: '{{ $request_attribute->attributeDetails()['DataType'] }}' default_value: '{{ $request_attribute->attributeDetails()['DefaultValue'] }}' rules: '{{ $request_attribute->attributeDetails()['RequestRule'] }}' messages: {!! $request_attribute->ruleMessage() !!} @endforeach response: columns: @foreach($parentAttribute->getAttributesGroupByKeyName('Response') as $response_attribute) - name: '{{ $response_attribute->attributeDetails()['ColumnName'] }}' description: '{{ $response_attribute->attributeDetails()['ColumnDescription'] }}' data_type: '{{ $response_attribute->attributeDetails()['DataType'] }}' @endforeach Otherパターン // $parentAttribute->getAttributesGroupByKeyName('*')には子側のデータの配列が格納されています - names: @foreach($parentAttribute->getAttributesGroupByKeyName('*') as $attribute) - name: '{{ $attribute->attributeDetails()['ColumnName'] }}' description: '{{ $attribute->attributeDetails()['TableName'] }}' description: '{{ $attribute->attributeDetails()['TableDescription'] }}' description: '{{ $attribute->attributeDetails()['ColumnDescription'] }}' data_type: '{{ $attribute->attributeDetails()['DataType'] }}' @endforeach Yamlファイルの出力コマンド php artisan spread_sheet_converter:create_definition_document 関連
- 投稿日:2021-05-16T00:52:16+09:00
LaravelにReactとTypeScriptを導入する
前提 Laravelでプロジェクトを作成していて、nodeはインストール済みの想定で進めていきます。 TypeScriptをインストールする webpack.mix.js(webpackのラッパーライブラリ)を下記のように編集する mix.ts('resources/ts/index.tsx', 'public/js') .sass('resources/sass/app.scss', 'public/css'); 先ほどの編集内容を反映させる npm install プロジェクト内にnode_modulesディレクトリが作成されていればOK。 一旦下記コマンドでビルドする。 npm run prod package.jsonにwebpack.mix.jsで編集した内容の不足分が記載される(typescriptとsass) Reactをインストールする npm i -D react react-dom @types/react @types/react-dom npm install package.jsonに追記されているか確認する。 React用のTypeScriptの設定ファイルを作成する tsc --init --jsx react すると... tsconfig.jsonが作成される。 動作確認 ①webpack.mix.jsに記載した下記を作成する。 ■resources/ts/index.tsx import React from 'react' import ReactDOM from 'react-dom' const App = () => { return ( <h1>Laravel SPA</h1> ) } ReactDOM.render( <App />, document.getElementById('app') ) ■resources/sass/app.scss →resources/css/app.cssをリネームする ②下記コマンドでビルドする npm run prod ③resources/views/welcome.blade.phpをresources/views/index.blade.phpへリネームする 下記のように書き直す <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Laravel</title> <link rel="stylesheet" href="{{ mix('/css/app.css') }}"> </head> <body> <div id="app"></div> </body> <script src="{{ mix('/js/index.js') }}"></script> </html> ④web.phpでrouteをwelcomeからindexへ変更する ⑤キャッシュ対策でファイルパスにパラメーターを付与する webpack.mix.jsに下記を追記する if (mix.inProduction()) { mix.version() } ⑥ビルトインサーバーを起動してlocalhost:8000へアクセスする php artisan serve 編集した内容が表示されていることを確認できました!
- 投稿日:2021-05-16T00:06:23+09:00
CircleCIでテストを自動実行する
前提 今回は前回の記事にCircleCIを組み込んでいきます。 DockerでLEMP環境を構築する テストを作成する # Laravelプロジェクト内でテスト用の.envファイルを作成する cp .env.example .env.testing # APP_KEYを作成する php artisan key:generate --env=testing .env.testingへ下記を記載する DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=laravel-test DB_USERNAME=root DB_PASSWORD= テストコードを作成する php artisan make:test HogeTest 下記などを参考にPHPUnitでテストを記述する https://qiita.com/tsuuuuu_san/items/46246168dc36ad2369cc https://qiita.com/nakano-shingo/items/9446568a2f9e903922d4 作成したテストをCircleCI上で実行できるように設定情報を追記する config/database.phpの'connections'に下記を追記する 'circle_testing' => [ 'driver' => 'mysql', 'host' => '127.0.0.1', 'port' => '3306', 'database' => 'circle_test', 'username' => 'root', 'password' => '', 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, 'engine' => null, ], CircleCIへログインする アカウントがない場合GitHub連携で簡単に作成できます。 Projectsへ遷移するとGitHubのリポジトリが表示されるので設定したいプロジェクトのSet UP Projectボタンを押下する(開発言語など問われるので適宜選択する) CircleCIの設定ファイルを作成する 【ルートディレクトリ】へ移動して.circleci/config.ymlを作成する mkdir .circleci && touch .circleci/config.yml .circleci/config.ymlに下記のように設定を記載する version: 2 # 設定ファイルのバージョン jobs: # 実行させたいジョブの定義 build: # ジョブ名を定義する docker: # ジョブを実行するマシンを定義 # CircleCIのイメージを使用 - image: circleci/php:7.3.0-node-browsers - image: circleci/mysql:5.7 environment: - DB_CONNECTION: circle_testing working_directory: ~/CI-test steps: # マシンに対しての命令手順 - checkout # コードをレポジトリからチェックアウト - run: # コマンドを実行 name: Update apt-get command: sudo apt-get update - run: name: Docker php extensions install command: sudo docker-php-ext-install pdo_mysql - restore_cache: keys: - v1-dependencies-{{ checksum "src/composer.json" }} - v1-dependencies- - run: name: Install PHP libraries working_directory: src command: composer install -n --prefer-dist - save_cache: paths: - ./vendor key: v1-dependencies-{{ checksum "src/composer.json" }} - run: name: Run PHPUnit working_directory: src command: vendor/bin/phpunit # 定義したジョブを実行するタイミングを定義できる # 未定義だとGitプッシュがトリガーになり実行される workflows: version: 2 builid_workflow: # ワークフロー名を定義する jobs: # どのJOBを実行するか - build テストを自動で実行する GitHubにコミット/プッシュする。 CircleCIのセットアップ画面へアクセスしてStart buildingをクリックするとビルド画面へ遷移する。 すると... 無事テストが走り、テスト結果が表示されました! 次回からはGitHubにコミット/プッシュしたタイミングで自動でテストが走るようになります(.circleci/config.ymlのworkflowsでタイミングは変更できます。)