- 投稿日:2021-03-29T23:12:22+09:00
Laravelのカスタムガードでステートフルな認証を実現する
前置き
Laravelの認証では
Illuminate\Contracts\Auth\Guard
を実装することで、自前の認証ロジックを組み込むことができます。https://readouble.com/laravel/8.x/ja/authentication.html#adding-custom-guards
例えば、このようなGuardを作成すると、パラメータに
id
というキーの値が含まれている時、そのIDのUserとして認証をさせることができます。
この認証ロジックを工夫することで、JWTを使った認証や、その他外部サービスと連携した認証が行えるという訳です。。app/Auth/MyGuard.phpMyGuard implements Guard { use GuardHelpers; private Request $request; private UserProvider $userProvider; /** * MyGuard constructor. * @param Request $request * @param UserProvider $userProvider */ public function __construct(Request $request, UserProvider $userProvider) { $this->request = $request; $this->userProvider = $userProvider; } public function user() { $id = $this->request->get('id'); if ($id) { $this->user = $this->userProvider->retrieveByCredentials(['id' => $id]); } return $this->user; } public function validate(array $credentials = []): bool { return false; }この自前のGuardをサービスプロバイダに登録します。
app/Providers/AppServiceProvider.phpclass AppServiceProvider extends ServiceProvider { ... public function boot() { Auth::extend('custom', function ($app, $name, array $config) { return new MyGuard(request(), Auth::createUserProvider($config['provider'])); }); } }そして、
auth.php
を編集し登録したサービスをアプリケーションで使用するように設定します。config/auth.php'guards' => [ 'web' => [ 'driver' => 'custom', 'provider' => 'users', ],最後に、
web.php
に以下のようなルートを追加し、http://localhost/users?id=1にアクセスすると、DBにID1のユーザーが登録されている時、そのユーザーのnameが表示されます。routes/web.phpRoute::get('/users', function () { /** @var User $user */ $user = Auth::user(); return $user->name ?? 'not logged in'; });本題
Laravelの認証に自前のロジックを組み込む方法はわかりました。しかし、上で作成したGuardはステートレスな物になっており、認証をするためには毎回
id=1
のパラメータを付与する必要があります。一度
id=1
で認証したら、二度目以降はパラメータなしでも認証できるようにするにはどうしたら良いのでしょうか?Laravelのドキュメントには
Auth::login($user, $rememberMe)
というメソッドを実行し、$rememberMe
をtrue
にすることで次回から自動ログインできる、と書いてあります。https://readouble.com/laravel/8.x/ja/authentication.html#authenticate-a-user-instance
なので、以下のようにユーザーを取得したタイミングで、このメソッドを呼び出せば良さそうです。
public function user() { $id = $this->request->get('id'); if ($id) { $this->user = $this->userProvider->retrieveByCredentials(['id' => $id]); Auth::login($this->user, true) } return $this->user; }が、実はこれでは動きません。この
Auth
ファサードのlogin()
メソッドは実はアプリケーションで使用されているGuard(今回はCustomGuard
)のlogin()
メソッドのエイリアスになっているので、CustomGuard
自体にlogin()
メソッドを実装する必要があるのです。もう少し正確な話をすると「ステートフルな認証を行う場合、アプリケーションで使用するGuardは
Illuminate\Contracts\Auth\StatefulGuard
を実装する必要があり、login()
メソッドはその中で実装しなければいけないメソッドの一つ」ということです。
StatefulGuard
はlogin()
メソッドの他にこれらのメソッドを実装する必要があります。
attempt()
once()
login()
loginUsingId()
onceUsingId()
viaRemember()
logout()
これらのメソッドを生真面目に実装しても良いのですが、認証状態を継続したい、という要件だけなら
SessionGuard
を継承してしまうのが早いです。まず親クラスのSessionGuardの
user()
を呼び出し、ステートフルな認証情報からユーザーが取得できたら、それを返します。見つからなかった場合は、自前の認証ロジックを呼び出し、SessionGuard
のlogin()
メソッド(Auth::login()
と同じ意味)を呼び出し、ログイン状態を記憶します。class MyGuard extends SessionGuard { public function __construct($name, UserProvider $provider, Session $session, Request $request = null) { parent::__construct($name, $provider, $session, $request); } public function user() { $this->user = parent::user(); if ($this->user) { return $this->user; } $id = $this->request->get('id'); if ($id) { $this->user = $this->provider->retrieveByCredentials(['id' => $id]); $this->login($this->user); } return $this->user; } }プロバイダーへの登録はこのようになります。
class AppServiceProvider extends ServiceProvider { ... public function boot() { Auth::extend('custom', function ($app, $name, array $config) { $session = $this->app->make(Session::class); return new MyGuard($name, Auth::createUserProvider($config['provider']), $session, request()); }); } }以上!
- 投稿日:2021-03-29T21:56:07+09:00
Laravelで複雑なValidationをやりたい時のFormRequest
概要
DB接続なども織り交ぜた複雑なValidationをやりたい時、最近忘れがちなので備忘録として。
前提
- Laravel5.5~6.xで確認
実装
HogeRequest.php. . . class HogeRequest extends FormRequest { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { return true; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { return [ 'fuga_id' => [ 'required', function ($attribute, $value, $fail) { // ここにロジックを書く $fuga = Fuga::find($this->input('fuga_id')); if ($fuga !== null) { return $fail('fugaをすでにしています'); } }, ], ]; }解説
- rulesに
function ($attribute, $value, $fail) {}
を使用することで、複雑なValidationを表現できる$this->input({$name})
で値を取得できるreturn $fail({$message})
でValidation失敗時のメッセージを返却できる参照
すみません。ブックマークし忘れてしまいました...
- 投稿日:2021-03-29T17:34:50+09:00
Laravelのルーティングの状態をファイルで確認してみる
環境
バージョン等 Laravel 6.20.19 MacOS 10.15.7 Catarina ①普通にルーティングを確認してみる
Laravelの通常のWebページとしてのルーティングは
Routes/web.php
にいろいろと記載していますが、
prefixとかgroupeなどを記述していると、実際にどうなっているか分からなくなったりしますよね。これを確認する時に使うのが、
ターミナル$ php artisan route:list
というコマンド。
なのですが、これがずら〜と表示されてちょっと分かりづらい。
Before
②見やすくルーティングを表示する
ということでこれをファイルとして保存して見やすく表示してみましょう。
route:list
コマンドに> .route
を追加すればOKです。ターミナル$ php artisan route:list > .routeこれでプロジェクトの直下に.routeというファイルが保存されました。
After
中身を表示するとこんな感じで見やすく表示されます。
作成してしまえばその後はコマンドを実行せずに確認できるのも嬉しいですね。
なおルーティングにもし変更があれば、再度同じコマンドを実行すれば上書きされます。また作成した.routeファイルは、チームで共有する必要はないと思いますので.gitignoreに追加すると良いです。
.gitignore.route #追記
以上、小技ではありますが最後までお読みいただきありがとうございます〜!
- 投稿日:2021-03-29T17:34:50+09:00
【Laravel】ルーティングの状態をファイルで見やすく確認する小技
環境
バージョン等 Laravel 6.20.19 MacOS 10.15.7 Catarina ①普通にルーティングを確認してみる
Laravelの通常のWebページとしてのルーティングは
Routes/web.php
にいろいろと記載していますが、
prefixとかgroupeなどを記述していると、実際にどうなっているか分からなくなったりしますよね。これを確認する時に使うのが、
ターミナル$ php artisan route:list
というコマンド。
なのですが、これがずら〜と表示されてちょっと分かりづらい。
Before
②見やすくルーティングを表示する
ということでこれをファイルとして保存して見やすく表示してみましょう。
route:list
コマンドに> .route
を追加すればOKです。ターミナル$ php artisan route:list > .routeこれでプロジェクトの直下に.routeというファイルが保存されました。
After
中身を表示するとこんな感じで見やすく表示されます。
作成してしまえばその後はコマンドを実行せずに確認できるのも嬉しいですね。
なおルーティングにもし変更があれば、再度同じコマンドを実行すれば上書きされます。また作成した.routeファイルは、チームで共有する必要はないと思いますので.gitignoreに追加すると良いです。
.gitignore.route #追記
最後までお読みいただきありがとうございます〜!
- 投稿日:2021-03-29T17:30:41+09:00
PHP if文とcount()の混合条件の作成
はじめに
今回は基本的な投稿システムが完了したので少しこだわりを入れて備忘録として投稿いたします。これを利用してlaravelに組み込みます。
今回の条件
- 1件以上のコメントがある場合は現在〇〇件コメント数という風に表示
- コメントが0件の場合は現在〇〇件コメント数を非表示にする
これらの条件を使用するにはif文とcount()の両方を使用することになります。以下にサンプルを掲載します。
if文の構文
if.php$value1 = 200; $value2 = 100; if ($value1 > $value2){ echo 'value1はvalue2より大きい'; $value2 = $value1; }countの構文
count.php$collection = collect(['鈴木', '佐藤', '田中']); echo $collection->count(); // 3上記の条件を纏めたコード
sample.php@if ($post->comments->count() >= 1) <p> <span class="badge badge-primary"> 現在のコメント数 {{ $post->comments->count() }}件 </span> </p> @endif解説
今回は各々が投稿した投稿内容の詳細にコメントを掲載するようになっています。
$postは投稿の変数になります。comments
はPost.modelのcommentsを表しています。
>= 1
で1件以上のコメントがある場合は不等号を使用して表示させる様にしています。Post.phppublic function comments() { // 投稿は複数のコメントを持つ return $this->hasMany('App\Models\Comment'); }
- 投稿日:2021-03-29T16:03:56+09:00
Laravel6 チートシート
はじめに
Laravel触り始めたばかりの初心者です。
いちいち調べるのもめんどくさくなってきたので基本的な部分をまとめてみました。
自分用のチートシートにはなりますが、Laravel6で開発中の初心者の方も参考にしてください。
開発環境に関しては、MAMPで構築しています。Laravelのインストールから初期設定
Laravelのインストール
Laravelのインストールはcomposerコマンドで行います。
ターミナルcomposer create-project laravel/laravel (フォルダ名) --prefer-dist "6.0.*"
composer create-project
には2つのオプションがあります。
1. --prefer-dist
2. –prefer-source
--prefer-dist
は、zipファイルでダウンロードします、こっちのほうが高速なのでこちらを採用。
--prefer-source
は、git cloneでソースを落とします。Laravelの動作確認
Laravelのインストールが正常に行われたかを確認するために開発サーバーを起動します。
ターミナルphp artisan serve
初期設定
Laravelでやっておくべき初期設定は以下の7つです。
- タイムゾーン
- 言語設定
- DBの文字コード
- デバッグバー
- DB設定
- エラーメッセージの日本語訳
- HTMLにCSRFトークンの設置
タイムゾーン
タイムゾーンを日本時間に変更します。
config/app.php(70行目あたり)'timezone' => 'Asia/Tokyo'言語設定
言語設定を日本語に変更します。
config/app.php(83行目あたり)'locale' => 'ja'DBの文字コード
文字コードをUTF-8に変更します。
config/database.php(55行目~56行目あたり)'charset' => 'utf8', 'collation' => 'utf8_unicode_ci'デバッグバー
デバッグ用にデバッグバーを表示させます。
ターミナルcomposer require barryvdh/laravel-debugbar
現時点での設定では本番環境でもデバッグバーが表示されます。
DBなどの値がユーザーに見えてしまうため本番環境ではデバッグバーを非表示にする必要があります。.envAPP_DEBUG=falseDB設定
.envファイルのDBに関する記載の部分を修正していきます。
.envDB_CONNECTION=mysql //DB種類 DB_HOST=127.0.0.1 //ホスト DB_PORT=3306 //ポート DB_DATABASE=laravel //データベース名 DB_USERNAME=root //ユーザー名 DB_PASSWORD= //パスワード接続確認
ターミナルphp artisan migrate
エラー発生
修正すべきポイントは3つです。
①.envのDB_HOSTを修正
.envDB_HOST=localhost②config/database.phpのunix_socketを修正
config/database.php(54行目あたり)'unix_socket' => '/Applications/MAMP/tmp/mysql/mysql.sock'③ターミナルでキャッシュクリア
ターミナルphp artisan cache:clear php artisan config:cache
エラーメッセージの日本語訳
resources/lang/en
内にエラーメッセージのファイルがあるがこれらすべて英語となっています。
そのため、日本語訳するために以下のサイトからダウンロードします。
ダウンロードしたファイルを
resources/lang/ja
内に格納します。任意修正箇所
エラーメッセージが一部英語で表示されます。
エラーメッセージの変更は以下のように行います。
resources/lang/ja/validation.php"attributes" => [ "password" => "パスワード" ]HTMLにCSRFトークンの設置
こちらを入れておかないとフォームが必ずエラーとなります。
HTMLのheadタグ内の共通レイアウトbladeファイルを作り、そこに記載します。resources/views/layout.blade.php(レイアウトファイル)<meta name="csrf-token" content="{{ csrf_token() }}">フロントエンドでVue.jsを使うとき
Laravel6ではインストールが必要となりました。
ターミナル// laravel/uiをインストール composer require laravel/ui:^1.0 --dev // どちらかを選択 // ①auth機能あり php artisan ui vue --auth // ②auth機能なし php artisan ui vue // コンパイルとインストール npm install && npm run dev
npm run devでエラーが発生した場合
エラーメッセージは以下の通りです。
ERROR Failed to compile with 2 errors error in ./resources/sass/app.scss【原因】
sass-loaderのバージョンの互換性がない。
【対応】
バージョンの変更を行う
ターミナル// sass-loaderをアンインストール npm uninstall --save-dev sass-loader // sass-loaderをインストール npm install --save-dev sass-loader@7.1.0 // 実行 npm run dev
MVCアーキテクチャ
大きく分けて2つの動きが考えられます。
①DBの情報を必要としないとき
クライアント → ルーティング → コントローラー → ビュー②DBの情報を必要とするとき
クライアント → ルーティング → (コントローラー ⇄ モデル ⇄ DB) → ビュールーティング
フォルダ
routes/web.php
api
の場合は、routes/api.php
routes/web.php// ①直接Viewに移動 // Viewのwelcome Route::get("/", function() { return view("welcome"); } // ②Controllerに移動 // ControllerのUserControllerのindexメソッドに移動 Route::get("/user", "UserController@index");コントローラー
フォルダ
app/Http/Controllers
コントローラーの作成方法
ターミナルphp artisan make:controller UserController
リソースコントローラー(RESTful)
1行のコードで典型的なCRUDルートをコントローラーで作成できます。
ターミナルphp artisan make:controller PhotoController --resource
動詞 URI アクション ルート名 GET /tests index Tests.index GET /tests/create create Tests.create POST /tests store Tests.store GET /tests/{test} show Tests.show GET /tests/{test}/edit edit Tests.edit PUT/PATCH /tests/{test} update Tests.update PUT/PATCH /tests/{test} destroy Tests.destroy ルーティングを制限する
Route::resouceはCRUDのルーティングを一度に行えるのですごく便利ですが、これらのメソッドを全て使うケースは多くないと思います。
削除するだけでは例外のエラーが発生するためルーティングを制限する必要があります。routes/web.php// index, createのみ使うとき Route::resource('photo', 'PhotoController', ['only' => ['index', 'create']]); // show, update以外すべて使うとき Route::resource('photo', 'PhotoController', ['only' => ['index', 'create', 'store', 'edit', 'destroy']]);RestFulでよく使われる書き方
認証がされているときに表示する
routes/web.phpRoute::group(["prefix" => "photos", "middleware" => "auth"], function() { Route::get("index", "PhotosController@index")->name(Photos.index); Route::get("create", "PhotosConroller@create")->name(Photos.create); Route::posts("store", "PhotosController@store")->name(Photos.store); Route::get("show/{id}", "PhotosController@show")->name(Photos.show); Route::get("edit/{id}", "PhotosController@edit")->name(Photos.edit); Route::post("update/{id}", "PhotosController@update")->name(Photos.update); Route::post("destroy/{id}", "PhotosController@destroy")->name(Photos.destroy); })
id
の値をどのようにして取ればいいのか?resources/views/show.blade.php<form action="{{ route('test.edit', [ "id" => $test->id ]) }}" method="GET"> </form>値の取り出し方
値の取り出し方は2通りあります。
①Viewのフォーム経由から値を受け取る
②ModelでDBから値を受け取るView経由
app/Http/Controllers/UserControlleruse App\Models\User; public function store(Request $request) { // 値を取得する // $email = $request->input("email"); // $password = $request->input("password"); // 正しく取得できているか確認 // dd($email); // Modelを呼び出すだけでOK $user = new User; // インスタンス化したものに保存 $user->email = $request->input("email"); $user->password = $request->input("password"); // DBに保存 $user->save(); // リダイレクト return redirect("user/index"); }
POST
がうまくいかないとき
一旦キャッシュをすべてクリアしてみましょう。ターミナルphp artisan optimize:clear
Model経由
app/Http/Controller/UserController.php// ModelsのUserを読み込み use App\Models\User // クエリビルダーを使うためのおまじない use Illuminate\Support\Facades\DB // ①Eloquent // all() = すべてのデータを取得する $values = User::all(); // find() = 引数の値のidのデータを取得する $value = User::find($id); // where() = 条件指定(categoryカラムがPHPのデータを取得する) $value = User::where("category", "PHP")->get(); // ②クエリビルダー // get() = 条件にあったデータのすべてを取得する $values = DB::table("users")->get(); // first() = 最初の1件を取得する $value = DB::table("users")->first(); // SELECT = 取得するカラムを指定する $values = DB::table("users")->select("name", "email")->get(); // user.indexのViewに移動し、$valuesの値を渡す return view("user.index", compact("values"));
モデル
フォルダ
app/Models
モデルの作成方法
同時にマイグレーションファイルを作成すると便利
ターミナルphp artisan make:model Models/Post --migration
マイグレーションとは?
マイグレーションとは、DBテーブルの履歴を管理する仕組みです。
フォルダ
database/migrations
マイグレーションの頻出カラムタイプ
database/migrations/XXXX_XX_XXXXXX_create_users_table.phppublic function up() { Schema::create('registers', function (Blueprint $table) { // 符号なしBIGINTを使用した自動増分ID $table->bigIncrements('id')->comment('id'); // 20文字以内の文字列 $table->string('name', 20)->comment('名前'); // 255文字以内で独自の文字列 $table->string('email', 255)->unique('email')->comment('メールアドレス'); // 日付 $table->date('birthday')->comment('生年月日'); // 0~255までの整数値で符号なし $table->tinyInteger('gender')->unsigned()->comment('1:男性 2:女性'); // 最大65535文字で未記入可 $table->text('memo', 65535)->nullable()->comment('メッセージ'); $table->timestamps(); }); }テーブルにカラムを作成する
ターミナルphp artisan migrate
マイグレーションを修正する
①migrationファイルを新規に作成する
--table
オプションでテーブル名を指定します。
ファイル名は何をするかをわかりやすくするのがおすすめです。
下記の場合、「usersテーブルにemailを追加する」です。ターミナルphp artisan make:migration add_email_users_table --table=users
②migrationファイルを修正
database/migrations/XXXX_XX_XXXXXX_add_email_users_table.phppublic function up() { Schema::table('users', function (Blueprint $table) { $table->string('email'); //カラム追加 }); } public function down() { Schema::table('users', function (Blueprint $table) { $table->dropColumn('email'); //カラムの削除 }); }③migration
ターミナルphp artisan migrate
ビュー
フォルダ
resources/views
assetsヘルパー
Bladeファイル内にCSSやJSを記述すると可読性が著しく落ちるので別ファイルに保存します。
public |__ css |__ js |__ imgresources/views/index.blade.php<!-- css --> <link rel="stylesheet" href="{{ asset('/css/style.css') }}"> <!-- js --> <script src="{{ asset('/js/index.js') }}"></script> <!-- img --> <img alt="ロゴ" src="{{ asset('/img/logo.jpg') }}">ダミーデータ
Factory & Faker
フォルダ
database/factories
ターミナルphp artisan make:factory PostFactory
ここで修正が必要です
database/factories/PostFactory.php// これは誤り // use App\Model // こちらが正しい use App\Models\Post // ModelではなくPostのためこちらも誤り /* $factory->difine(Model::class, function (Faker $faker) { return [ ]; } */ //こちらが正しい $factory->define(Post::class, function (Faker $faker) { return [ "name" => $faker->name, "title" => $faker->realText(50), "email" => $faker->unique()->email, "tel" => $faker->phoneNumber, "password" => $faker->password, "url" => $faker->url, "gender" => $faker->randomElement(["0", "1"]), "age" => $faker->numberBetween($min = 1, $max = 6), "contact" => $faker->realText(200) ]; });その他のFakerに関しては以下を確認してください。
作成手順
①config/app.phpの修正
config/app.php(109行目あたり)"faker_locale" => "ja_JP"②シーダの作成
ターミナルphp artisan make:seeder PostSeeder
PostSeeder.phpuse App\Models\Post public function run() { // 200個作成 factory(Post::class, 200)->create(); }③データベースシーダーに追記
database/seeds/DatabaseSeeder.phppublic function run() { $this->call([ // ここに付け足していく UsersTableSeeder::class, PostsSeeder::class, ]); }④ターミナル
ターミナル// Composerのオートローダーを再生成する composer dump-autoload // データベースに入っている値を初期化し入力する場合 php artisan migrate:fresh --seed
バリデーション
フォルダ
app/Http/Requests
ターミナルphp artisan make:request StoreBlogPost
app/Http/Requests/StoreBlogPost.php// ①trueに変更(これをしないとうまく作用しない) public function authorize() { return true; } public function rules() { return [ // 【必須】最大20文字 "name" => "required|string|max:20", // 【必須】最大255文字のメールアドレスであり被りがあってはならない "email" => "required|email|unique:users|max:255", // 【必須】8文字以上で確認用と同じパスワード "password" => "required|confirmed|min:8", // 【任意】 "hobby" => "nullable", ] }app/Http/Controllers/StoreBlogPostController// ファイルを読み込む use App\Http\Requests\StoreBlogPost; // これで自動的にバリデーションができるようになる public function store(StoreBlogPost $request) { }resources/views/blogpost.blade.php// ①それぞれ書く @error("email") <span> <strong>{{ $message }}</strong> </span> @enderror // ②まとめて書く @if ($errors->any()) <div> <ul> @foreach($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif
- 投稿日:2021-03-29T15:13:19+09:00
Windows10にLaravel 8.xの環境を構築する
Windows10にLaravel 8.*の環境を作成するにあたっての備忘録です。
Laravel 7まではcomposerで環境を構築できていましたが、
Laravel 8~ではDocker上に環境を構築するようです。
Windows10に環境を作成するにあたっての備忘録。Docker Desktopをインストール
ダイアログに従って普通にインストール。
Docker Desktop
https://www.docker.com/products/docker-desktopWindows Subsystem for Linux 2(WSL2)インストール
以下URLの「Manual Installation Steps」を参考に。
https://docs.microsoft.com/en-us/windows/wsl/install-win10PowerShellを管理者権限で起動
Windows SubsystemforLinuxオプション機能の有効化
WindowsでLinuxサブシステムを使えるようにするための設定。
PowerShell上で次のコマンドを実行し、マシンを再起動する。dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart仮想マシン機能の有効化
仮想マシン利用を有効にするための設定。
PowerShellで次のコマンドを実行し、マシンを再起動。dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestartWSL2 Linuxカーネルアップデートパッケージをダウンロード
リンク先の「WSL2 Linux kernel update package for x64 machines」から
ダウンロードしてインストール。
https://docs.microsoft.com/en-us/windows/wsl/install-win10WSL2をデフォルトバージョンとして設定
これからインストールするLinuxサブシステムで、WSL2をデフォルトとして使用するための設定。
PowerShell上で次のコマンドを実行。
wsl --set-default-version 2
Linuxサブシステムのインストールと設定
サブシステムのインストール
Windows上で動作するLinuxサブシステムをインストールする。
色々あるが、今回は深く考えずにKali-linuxをインストールした。MicrosoftStoreから、
各Linuxサブシステムをダウンロード&インストール。
各サブシステムへのリンクは下記リンクの「Step 6 - Install your Linux distribution of choice」参照。
https://docs.microsoft.com/en-us/windows/wsl/install-win10サブシステムのユーザー、パスワードを設定
サブシステムをインストールし、初回起動すると
ユーザー名とパスワードの設定を求められるので、設定する。以上でWindows上にLinuxサブシステムがセットアップされる。
PowerShell上から次のコマンドを打ってみて、エラーなどが出なければ正しくセットアップされているはず。
Linuxサブシステムに割り当てられているWSLの確認
wsl --list --verboseDocker Desktopの設定
DockerがWSL2をベースに動き、またインストールしたLinuxサブシステムを認識するための設定。
Docker Desktopを起動し、メニューバーの「Settings」(歯車のアイコン)をクリック。
左メニューの「General」が選択された状態で、
「Use the WSL 2 based engine」が有効になっていることを確認。
左メニューの「Resources」→「WSL INTEGRATION」で、
インストールしたLinuxサブシステムにチェックを入れ(自分の場合はkaki-linuxです)、
Apply & RestartをクリックしてDockerを再起動。
PowerShellでLinuxサブシステムを起動する。
自分の場合はkali-linuxの起動コマンド「kali」。PS C:\Users\azumak> kali起動したLinuxサブシステム上で、「docker -v」などと打ってみて
Dockerを認識しているか確認する。Laravelインストール
Linuxサブシステム上で、以下コマンドを実行してLaravel最新版をインストールする。
sudo curl -s https://laravel.build/<プロジェクト名>| bashここで、「Docker is not running」と表示されて先に進まないようであれば、
Dockerが起動していないか、LinuxサブシステムがDockerを認識していない・・・かも。
自分の場合はDockerとkali-linuxの関連付けができていなかった。うまくいけば、指定したフォルダにLaravel最新版がインストールされる。
Laravel Sail起動
インストールが成功すると指定したフォルダにLaravelプロジェクトが作成されるので、
プロジェクトフォルダに移動してSailコマンドを実行する。cd <プロジェクト名> ./vendor/bin/sail up初回はとても時間がかかる。
build成功のログが出たらhttp://localhostにアクセスし、
LaravelのTOページが表示されれば成功!
参考
https://readouble.com/laravel/8.x/ja/installation.html
https://docs.microsoft.com/en-us/windows/wsl/install-win10
- 投稿日:2021-03-29T15:13:19+09:00
【Laravel/Docker/Windows10】Docker上にLaravel環境を構築する
Windows10にLaravel 8.x/Dockerの環境を作成するにあたっての備忘録です。
Laravel 8.x 公式ドキュメントのインストール手順には、
Docker上にLaravel環境を構築する方法が紹介されています。
その手順に従い、Windows10にLaravel環境を作成するにあたっての備忘録。Docker Desktopをインストール
ダイアログに従って普通にインストール。
Docker Desktop
https://www.docker.com/products/docker-desktopWindows Subsystem for Linux 2(WSL2)インストール
以下URLの「Manual Installation Steps」を参考に。
https://docs.microsoft.com/en-us/windows/wsl/install-win10PowerShellを管理者権限で起動
Windows SubsystemforLinuxオプション機能の有効化
WindowsでLinuxサブシステムを使えるようにするための設定。
PowerShell上で次のコマンドを実行し、マシンを再起動する。dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart仮想マシン機能の有効化
仮想マシン利用を有効にするための設定。
PowerShellで次のコマンドを実行し、マシンを再起動。dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestartWSL2 Linuxカーネルアップデートパッケージをダウンロード
リンク先の「WSL2 Linux kernel update package for x64 machines」から
ダウンロードしてインストール。
https://docs.microsoft.com/en-us/windows/wsl/install-win10WSL2をデフォルトバージョンとして設定
これからインストールするLinuxサブシステムで、WSL2をデフォルトとして使用するための設定。
PowerShell上で次のコマンドを実行。
wsl --set-default-version 2
Linuxサブシステムのインストールと設定
サブシステムのインストール
Windows上で動作するLinuxサブシステムをインストールする。
色々あるが、今回は深く考えずにKali-linuxをインストールした。MicrosoftStoreから、
各Linuxサブシステムをダウンロード&インストール。
各サブシステムへのリンクは下記リンクの「Step 6 - Install your Linux distribution of choice」参照。
https://docs.microsoft.com/en-us/windows/wsl/install-win10サブシステムのユーザー、パスワードを設定
サブシステムをインストールし、初回起動すると
ユーザー名とパスワードの設定を求められるので、設定する。以上でWindows上にLinuxサブシステムがセットアップされる。
PowerShell上から次のコマンドを打ってみて、エラーなどが出なければ正しくセットアップされているはず。
Linuxサブシステムに割り当てられているWSLの確認
wsl --list --verboseDocker Desktopの設定
DockerがWSL2をベースに動き、またインストールしたLinuxサブシステムを認識するための設定。
Docker Desktopを起動し、メニューバーの「Settings」(歯車のアイコン)をクリック。
左メニューの「General」が選択された状態で、
「Use the WSL 2 based engine」が有効になっていることを確認。
左メニューの「Resources」→「WSL INTEGRATION」で、
インストールしたLinuxサブシステムにチェックを入れ(自分の場合はkaki-linuxです)、
Apply & RestartをクリックしてDockerを再起動。
PowerShellでLinuxサブシステムを起動する。
自分の場合はkali-linuxの起動コマンド「kali」。PS C:\Users\azumak> kali起動したLinuxサブシステム上で、「docker -v」などと打ってみて
Dockerを認識しているか確認する。Laravelインストール
Linuxサブシステム上で、以下コマンドを実行してLaravel最新版をインストールする。
sudo curl -s https://laravel.build/<プロジェクト名>| bashここで、「Docker is not running」と表示されて先に進まないようであれば、
Dockerが起動していないか、LinuxサブシステムがDockerを認識していない・・・かも。
自分の場合はDockerとkali-linuxの関連付けができていなかった。うまくいけば、指定したフォルダにLaravel最新版がインストールされる。
Laravel Sail起動
インストールが成功すると指定したフォルダにLaravelプロジェクトが作成されるので、
プロジェクトフォルダに移動してSailコマンドを実行する。cd <プロジェクト名> ./vendor/bin/sail up初回はとても時間がかかる。
build成功のログが出たらhttp://localhostにアクセスし、
LaravelのTOページが表示されれば成功!
参考
https://readouble.com/laravel/8.x/ja/installation.html
https://docs.microsoft.com/en-us/windows/wsl/install-win10
- 投稿日:2021-03-29T14:33:19+09:00
Laravel PDF日本語化で一部文字化けが起きる時の対処
環境
- PHP 7.2
- Laravel 6.x
- Composer library
barryvdh/laravel-dompdf
事象
前提の日本語対応済み。
請求書のPDFでご請求金額
と表示されるはずが、ご☒☒☒☒
といった感じで一部の日本語が文字化けを起こす。前提
日本語対応
barryvdh/laravel-dompdf
ライブラリをインストール- IPAからフォントダウンロード
storage/fonts/
配下に配置- inline cssで
@font-face
font-family
定義対処
storage/fonts
配下に自動生成されるファイル削除。
- dompdf_font_family_cache.php
- ipag-bold_*.ttf
- ipag-bold_*.ufm
- ipag-bold_*.ufm.php
- ipag-normal_*.ttf
- ipag-normal_*.ufm
- ipag-normal_*.ufm.php
- Times-Bold.afm.php
- Times-Roman.afm.php
- 念の為、キャッシュ一式クリアする
php artisan optimize:clear
- 投稿日:2021-03-29T10:00:16+09:00
LaravelをAWS Lambdaで動作させてデータベースにはAurora Serverlessを使ってみた
LaravelをAWS Lambdaで動作させることができる素敵な時代になっていたので、Laravel SailとLaravel Breezeでログイン機能を実装したプロジェクトをServerless Frameworkを利用してデプロイしてみました。
前提
こちらのGitHubリポジトリを利用してデプロイ環境を構築する手順です。
環境構築後の内容はこちらのブランチにおいています。手順
Serverless Frameworkのインストール
セットアップスクリプトが提供されているのでそれを利用します。
> curl -o- -L https://slss.io/install | bash > serverless -v Framework Core: 2.30.3 (standalone) Plugin: 4.5.1 SDK: 4.2.0 Components: 3.7.4Serverless Getting Started Guide
https://www.serverless.com/framework/docs/getting-started/Brefを利用してAWS Lambda用にLaravelを構成する
Brefというパッケージを利用するとAWS LambdaでLaravelを動作させる環境構築がとても簡単にできます。
Bref - Serverless PHP made simple
https://bref.sh/
brefphp/laravel-bridge: Package to use Laravel on AWS Lambda with Bref
https://github.com/brefphp/laravel-bridge
LaravelをコンテナにしてLambdaでデプロイするのが超簡単になった2021年 - Qiita
https://qiita.com/umihico/items/514cf792d30bf3706ef5
パッケージをインストールしてServerless Framework用の設定ファイルを生成します。> ./vendor/bin/sail composer require bref/bref bref/laravel-bridge > ./vendor/bin/sail artisan vendor:publish --tag=serverless-config
serverless.yml
の編集生成された
serverless.yml
ファイルにAurora Serverlessに必要となるリソースを追加します。下記の記事を参考にさせてもらいました。(感謝Serverless FrameworkでAurora Postgres + VPC Lambda + RDS Proxyをデプロイする | DevelopersIO
https://dev.classmethod.jp/articles/rds-proxy-deploy-with-serverless-framework/
Aurora Serverless をCloudFormationを利用して設置してみた | DevelopersIO
https://dev.classmethod.jp/articles/aurora-serverless-by-cfn/
Brefを使って完全サーバレスな、Symfony + Vue.js + Aurora Serverless製TODOリストを作る - Qiita
https://qiita.com/ippey_s/items/bbb4e329c0a919d1a8b5
serverless.yml
と設定ファイルは下記のようになりました。serverless.ymlservice: laravel provider: name: aws # The AWS region in which to deploy (us-east-1 is the default) region: ap-northeast-1 # The stage of the application, e.g. dev, production, staging… ('dev' is the default) stage: dev runtime: provided.al2 custom: defaultStage: dev profiles: dev: sls-itg stg: sls-stg prd: sls-prd environments: ${file(./config/config.${opt:stage, self:custom.defaultStage}.yml)} secret: ${file(./config/secret/.secret.${opt:stage, self:custom.defaultStage}.yml)} package: # Directories to exclude from deployment exclude: - node_modules/** - public/storage - resources/assets/** - storage/** - tests/** functions: # This function runs the Laravel website/API web: handler: public/index.php timeout: 28 # in seconds (API Gateway has a timeout of 29 seconds) layers: - ${bref:layer.php-74-fpm} events: - httpApi: "*" # This function lets us run artisan commands in Lambda vpc: securityGroupIds: - !Ref LambdaSecurityGroup subnetIds: - !Ref PrivateSubnetA - !Ref PrivateSubnetC environment: DB_PORT: ${self:custom.environments.DB_PORT} DB_HOST: !GetAtt DBCluster.Endpoint.Address DB_PASSWORD: ${self:custom.secret.PASSWORD} artisan: handler: artisan timeout: 120 # in seconds layers: - ${bref:layer.php-74} # PHP - ${bref:layer.console} # The "console" layer vpc: securityGroupIds: - !Ref LambdaSecurityGroup subnetIds: - !Ref PrivateSubnetA - !Ref PrivateSubnetC environment: DB_PORT: ${self:custom.environments.DB_PORT} DB_HOST: !GetAtt DBCluster.Endpoint.Address DB_PASSWORD: ${self:custom.secret.PASSWORD} plugins: # We need to include the Bref plugin - ./vendor/bref/bref resources: Resources: # The S3 bucket that stores the assets Assets: Type: AWS::S3::Bucket Properties: BucketName: ${self:custom.environments.AWS_BUCKET} # The policy that makes the bucket publicly readable AssetsBucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: !Ref Assets # References the bucket we defined above PolicyDocument: Statement: - Effect: Allow Principal: "*" # everyone Action: "s3:GetObject" # to read Resource: !Join ["/", [!GetAtt Assets.Arn, "*"]] # things in the bucket # alternatively you can write out Resource: 'arn:aws:s3:::<bucket-name>/*' ## VPC Resource VPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/24 Tags: - { Key: Name, Value: Sample VPC } PrivateSubnetA: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: 10.0.0.0/25 AvailabilityZone: ap-northeast-1a Tags: - { Key: Name, Value: Sample Private A } PrivateSubnetC: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: 10.0.0.128/25 AvailabilityZone: ap-northeast-1c Tags: - { Key: Name, Value: Sample Private C } LambdaSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: SecurityGroup for Lambda Functions VpcId: !Ref VPC Tags: - Key: "Name" Value: "LambdaSecurityGroup" AuroraSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: SecurityGroup for Aurora VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: ${self:custom.environments.DB_PORT} ToPort: ${self:custom.environments.DB_PORT} CidrIp: 10.0.0.0/24 Tags: - Key: "Name" Value: "AuroraSecurityGroup" DependsOn: VPC ## RDS Resource DBSubnetGroup: Type: AWS::RDS::DBSubnetGroup Properties: DBSubnetGroupDescription: "SampleDB subnet group" DBSubnetGroupName: sampledb-subnet-group SubnetIds: - !Ref PrivateSubnetA - !Ref PrivateSubnetC DBCluster: Type: AWS::RDS::DBCluster Properties: DatabaseName: ${self:custom.environments.DB_DATABASE} Engine: aurora-mysql EngineMode: serverless MasterUsername: ${self:custom.secret.USER_NAME} MasterUserPassword: ${self:custom.secret.PASSWORD} DBClusterParameterGroupName: !Ref DBClusterParameterGroup DBSubnetGroupName: !Ref DBSubnetGroup VpcSecurityGroupIds: - !Ref AuroraSecurityGroup DependsOn: DBSubnetGroup DBClusterParameterGroup: Type: AWS::RDS::DBClusterParameterGroup Properties: Description: A parameter group for aurora Family: aurora-mysql5.7 Parameters: time_zone: "Asia/Tokyo" character_set_client: "utf8" character_set_connection: "utf8" character_set_database: "utf8" character_set_results: "utf8" character_set_server: "utf8" AuroraSecret: Type: AWS::SecretsManager::Secret Properties: Name: Sample/aurora SecretString: '{"username":"${self:custom.secret.USER_NAME}", "password":"${self:custom.secret.PASSWORD}"}' SecretTargetAttachment: Type: AWS::SecretsManager::SecretTargetAttachment Properties: SecretId: !Ref AuroraSecret TargetId: !Ref DBCluster TargetType: "AWS::RDS::DBCluster" DependsOn: DBClusterconfig/secret/.secret.dev.ymlUSER_NAME: root PASSWORD: passwordデータベース名、バケット名は任意で指定します。
config/config.dev.ymlDB_PORT: 3306 DB_DATABASE: hoge_app AWS_BUCKET: kai-laravel-testLambda関数に環境変数を追加する
Brefが自動生成してくれるLambda関数のリソースにAurora Serverlessへアクセスするための環境変数を設定します。Aurora Serverlessの場合、インスタンスは自動で生成されるので、
DBCluster.Endpoint.Address
でエンドポイントを取得します。functions: # This function runs the Laravel website/API web: handler: public/index.php timeout: 28 # in seconds (API Gateway has a timeout of 29 seconds) layers: - ${bref:layer.php-74-fpm} events: - httpApi: "*" # This function lets us run artisan commands in Lambda vpc: securityGroupIds: - !Ref LambdaSecurityGroup subnetIds: - !Ref PrivateSubnetA - !Ref PrivateSubnetC environment: DB_PORT: ${self:custom.environments.DB_PORT} DB_HOST: !GetAtt DBCluster.Endpoint.Address DB_PASSWORD: ${self:custom.secret.PASSWORD} artisan: handler: artisan timeout: 120 # in seconds layers: - ${bref:layer.php-74} # PHP - ${bref:layer.console} # The "console" layer vpc: securityGroupIds: - !Ref LambdaSecurityGroup subnetIds: - !Ref PrivateSubnetA - !Ref PrivateSubnetC environment: DB_PORT: ${self:custom.environments.DB_PORT} DB_HOST: !GetAtt DBCluster.Endpoint.Address DB_PASSWORD: ${self:custom.secret.PASSWORD}クラスターの設定
EngineMode: serverless
と設定するとAurora Serverlessとしてクラスター作成できます。AWS::RDS::DBCluster - AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbcluster.html#cfn-rds-dbcluster-enginemodeDBCluster: Type: AWS::RDS::DBCluster Properties: DatabaseName: ${self:custom.environments.DB_DATABASE} Engine: aurora-mysql EngineMode: serverless MasterUsername: ${self:custom.secret.USER_NAME} MasterUserPassword: ${self:custom.secret.PASSWORD} DBClusterParameterGroupName: !Ref DBClusterParameterGroup DBSubnetGroupName: !Ref DBSubnetGroup VpcSecurityGroupIds: - !Ref AuroraSecurityGroup DependsOn: DBSubnetGroupDBClusterParameterGroupで
character_set
を指定していますが、これはMySQL5.7.7未満だと標準のcharasetがutf8mb4
となり、unique制約をつけたカラムで文字数オーバーになるのを回避するためです。
詳細は下記が詳しいです。Laravel5.4以上、MySQL5.7.7未満 でusersテーブルのマイグレーションを実行すると Syntax error が発生する - Qiita
https://qiita.com/beer_geek/items/6e4264db142745ea666fDBClusterParameterGroup: Type: AWS::RDS::DBClusterParameterGroup Properties: Description: A parameter group for aurora Family: aurora-mysql5.7 Parameters: time_zone: "Asia/Tokyo" character_set_client: "utf8" character_set_connection: "utf8" character_set_database: "utf8" character_set_results: "utf8" character_set_server: "utf8"デプロイ
serverless deploy
コマンドでデプロイします。時間がかかるのでデプロイ中はお茶でものんでまったりと待ちます。> ./vendor/bin/sail npm run prod > serverless deploy
デプロイ後、エンドポイントにアクセスして以下のように表示されたらOKです。
静的ファイルの配置
S3バケットにプロジェクトのpublicディレクトリをコピーします。これはAWS Lambda関数へのアクセスだけだと、
public/css/app.css
などへアクセスできないので、デプロイで作成した、アセット用のS3バケットへファイルを配置するためです。
ファイルがないとブラウザでアクセスした場合、ログイン画面へアクセスすると以下のようになります。
> aws s3 sync public s3://kai-laravel-test/public --deleteマイグレーション
Brefには
cli
コマンドが提供されているので、それを利用してAWS Lambdaのlaravel-dev-artisan
関数を利用してマイグレーションします。
cli
コマンドはAWSのクレデンシャルファイルを参照するので、sail
コマンドを挟まないほうが手っ取り早いです。> ./vendor/bin/bref cli --region=ap-northeast-1 laravel-dev-artisan -- migrate動作確認
実際にデプロイした環境にアクセスしています。
今回はメール送信については対応していないので、パスワード変更のためのメール送信はエラーとなります。
リソースの削除
動作確認ができてリソースが不要であれば削除します。S3バケットにファイルがあるまま削除処理を実行するとエラーになるので、ファイルを削除しておきます。
> aws s3 rm --recursive s3://kai-laravel-test/ > serverless removeまとめ
実用するにはまだまだ対応するべきことがありますが、ひとまずはやりたいことが実現できました。
やったぜ参考
Serverless Getting Started Guide
https://www.serverless.com/framework/docs/getting-started/
Bref - Serverless PHP made simple
https://bref.sh/
brefphp/laravel-bridge: Package to use Laravel on AWS Lambda with Bref
https://github.com/brefphp/laravel-bridge
LaravelをコンテナにしてLambdaでデプロイするのが超簡単になった2021年 - Qiita
https://qiita.com/umihico/items/514cf792d30bf3706ef5
Serverless FrameworkでAurora Postgres + VPC Lambda + RDS Proxyをデプロイする | DevelopersIO
https://dev.classmethod.jp/articles/rds-proxy-deploy-with-serverless-framework/
Aurora Serverless をCloudFormationを利用して設置してみた | DevelopersIO
https://dev.classmethod.jp/articles/aurora-serverless-by-cfn/
Brefを使って完全サーバレスな、Symfony + Vue.js + Aurora Serverless製TODOリストを作る - Qiita
https://qiita.com/ippey_s/items/bbb4e329c0a919d1a8b5
AWS::RDS::DBCluster - AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbcluster.html#cfn-rds-dbcluster-enginemode
Laravel5.4以上、MySQL5.7.7未満 でusersテーブルのマイグレーションを実行すると Syntax error が発生する - Qiita
https://qiita.com/beer_geek/items/6e4264db142745ea666f
CreateDBClusterParameterGroup-Amazon Relational Database Service
https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_CreateDBClusterParameterGroup.html
AWS::RDS::DBInstance - AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-rds-database-instance.html#cfn-rds-dbinstance-masterusername
エラー: The policy failed legacy parsing - Qiita
https://qiita.com/sot528/items/f248db0b69c7e1d34220
Cloudformationでエラー: Has prohibited field Resource - Qiita
https://qiita.com/sot528/items/2263fab0dbb55d6ce032
Aurora Serverlessの導入時に気をつけるべきこと | DevelopersIO
https://dev.classmethod.jp/articles/lessons-learned-from-up-and-running-aurora-serverless/
Console commands - Bref
https://bref.sh/docs/runtimes/console.html
Serverless Frameworkで環境変数を外部ファイルから読み込み、環境毎に自動で切り替えてみる | DevelopersIO
https://dev.classmethod.jp/articles/serverless-framework-conf-change/
データベースの使用-Bref
https://bref.sh/docs/environment/database.html
Brefが進化してたのでサーバーレスLaravel環境作ってみた - Qiita
https://qiita.com/ippey_s/items/25129dde8c7fe85479e4
Creating serverless PHP websites - Bref
https://bref.sh/docs/websites.html#architectures
- 投稿日:2021-03-29T09:44:03+09:00
【laravel】vue.jsを導入したら、bootstrap動かなくなったよ
はじめに
laravelにvue.jsを導入が完了し、喜んでいた最中に発生した、boostrapのモーダルが動いていない問題。
これを解決するのに詰まったので、解決策を記録致します!
前提
環境
PHP 7.4.16
Laravel 6.20.17
mysql 8.0.23
nginx 1.18.0コード
app.blade.php<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <title> @yield('title') </title> <!-- Font Awesome --> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css"> <!-- Bootstrap core CSS --> <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"> <!-- Material Design Bootstrap --> <link href="https://cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.8.11/css/mdb.min.css" rel="stylesheet"> <script src="https://ajaxzip3.github.io/ajaxzip3.js" charset="UTF-8"></script> </head> <body> @include('nav') <div id="app"> @yield('content') </div> <script src="{{ mix('js/app.js') }}"></script> <!-- JQuery --> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> <!-- Bootstrap tooltips --> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.4/umd/popper.min.js"></script> <!-- Bootstrap core JavaScript --> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/js/bootstrap.min.js"></script> <!-- MDB core JavaScript --> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.8.11/js/mdb.min.js"></script> </body> </html>webpack.mix.jsconst mix = require('laravel-mix'); /* |-------------------------------------------------------------------------- | Mix Asset Management |-------------------------------------------------------------------------- | | Mix provides a clean, fluent API for defining some Webpack build steps | for your Laravel application. By default, we are compiling the Sass | file for the application as well as bundling up all the JS files. | */ mix.js('resources/js/app.js', 'public/js').version();app.jsimport './bootstrap' import Vue from 'vue' import LocationTagsInput from './components/LocationTagsInput' const app = new Vue({ el: '#app', components: { LocationTagsInput, } })解決法
app.blade.php
から下記をコメントアウトする<!-- JQuery --> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>原因
bootstrap.jstry { window.Popper = require('popper.js').default; window.$ = window.jQuery = require('jquery'); require('bootstrap'); } catch (e) {} 以降省略上記箇所でも、jQueryを呼んでいたので、
app.blade.php
とダブルで呼び出していたことが原因でした!
- 投稿日:2021-03-29T03:03:45+09:00
ボクとPHP・Laravel・Dockerとの約200時間
始まりは突然に…
あれは、ある企業に面接したときの事でした。それまで、私はRubyでの開発を行っており、ポートフォリオもRailsで作成していました。その面接で、ポートフォリオの説明を行い面接終了時に次のようなお言葉を頂いた時から始まりました。
あなたに課題を出します。それを期限を決めませんが行ってください。とのことでした。課題とは以下の通りです。
それを成し遂げた理由
![]()
それは、至って単純な話です。
やってみたい・挑戦してみたいと思ったかです!
やりたいやってみせるという気持ちで望みました!で、一体何から着手したのか
プロセスは以下の通りです。
①プロゲートの初級(PHP基礎学習)
↓
②ドットインストールPHPに関するもの全て(PHP基礎学習)
↓
③ドットインストールDockerに関するもの全て(Docker基礎学習)
↓
④ドットインストールLaravel基礎の部分のみ(Laravel基礎学習)
↓
⑤Docker環境下でLaravel立ち上げYouTube等の動画を元に実践(Laravel実践学習)
↓
⑥ちょっとした掲示板を作成(Laravel実践学習)
↓
⑦ポートフォリオ作成(ErrorStocker作成)
この様な流れで実施
以下が着手時間です。学習管理に関しては、Studyplusを利用しました。
・PHP基礎学習に費やした時間:31時間11分(2月6日〜2月11日)
・Docker基礎学習に費やした時間:8時間10分(2月7・11・12日)
・Laravel基礎学習に費やした時間:31時間24分(2月11〜18日)
・ErrorStocker作成に費やした時間:130時間4分(2月18日〜3月18日)
合計時間:約200時間?作成したポートフォリオ
題名:ErrorStocker
URL:http://error-st.com
GitHub:https://github.com/miyaseinto/ErrorStokcer何を目的に作成したのか??の前に軸を話させてください!
少し私の話になりますが少々お付き合いください!(どうでもいいんだよテメェの話なんかと思った人は飛ばしてください笑)
これは、私がエンジニアを目指した軸にあります。
【不便に感じたものをデジタル化でより楽ができるように便利に変えたい】これが私がエンジニアを目指した軸です。
なぜその様な軸ができたかというと、前職での公務員経験からこの様な考えが生じたからです。
前職の公務員は超田舎で勤務していました。そのため、デジタル化が都会より著しく劣っており何をするにもアナログで不効率でした。何より住民の方が大変な思いをすることが多々ありました。回覧物だったり、各種手続きだったり、仕方がないことだと考えていました。ですが、エンジニアという職業がそれらを解決に導く職業であるのでは無いかと調べるうちに、それを担ってやりたいという思いが日に日に増していきました。
で、それを勉強したいと思い勉強していく中で自分が書いたコードがブラウザ画面で変化していくことにとてもやりがいを感じ、これを仕事にしたいと考え公務員を退職しエンジニアを目指そうと思い勉強に励みました。
退職した職場には申し訳なかったのですが、自分の腹の中を上司の方々に話すとそれならば挑戦してみろと背中を強く押してくれました。ですので、その思いを全部背負っとるじゃいと思いながらエンジニアになりたいと考えてます。これがエンジニアを目指した軸のお話です。(長々と申し訳ないです笑)作成背景&目的
上記の軸を基に、プログラミングの勉強をしている時に不便に感じたことを便利に変えるために作成しました。
私が、エラーと遭遇した時にリファレンスサイトやQiitaの内容からエラー解決に導こうとします。そこで、いつもは一度エラーした内容をGoogleのブックマークの中にファイルごとに保存して、もう一度確認をしたい時に見直しをしておりました。しかし、情けない話そのファイルをどこに保存したのかがわからなくなり、探すことに時間を掛けることがありました。そこで、検索をかけてその内容を短時間で探せることはできないかと考え作成に至りました。工夫した・苦労したPOINT
・投稿内容をマークダウンで投稿できるようにしたこと(リンクを文字列内に入れたいと考えたため)
・上記と同じだが、コメント機能にもマークダウンを使用したこと(上記と同じ)
・投稿した時間を表記させたこと
・コメントした時間を表記させたこと
・Bootstrapを使用したが、Bootstrap感を排除したこと
・写真の圧縮を行ったこと
・タグを一覧でも表示させたこと
・閲覧用としてログイン簡略化させたこと(通常のログインと表示は異なる)
・ページネーションを導入したこと
・キーワード検索をタイトルの内容と本文の内容で検索できるようにしたこと
・キーワード検索で検索件数を表示させたこと?機能一覧
ユーザー機能
・ユーザー登録(投稿用ログイン)
・ゲストログイン(閲覧用ログイン)
・マイページにて以下の投稿の一覧表示
・自分の投稿内容
投稿機能
・エラーのストックをログインアカウントが投稿・編集・削除
・一覧表示、詳細表示
・投稿一覧表示で10個の投稿数をページネーションを実施
・写真投稿及び圧縮(intervention/image)
・タグ付け(タグ検索)
・キーワード検索(タイトル・内容)
・投稿内容にマークダウンを採用(cebe/markdown)
コメント機能
・投稿にコメントを投稿・編集・削除
・投稿詳細ページにコメント一覧表示
・コメント内容にマークダウンを採用(cebe/markdown)?使用技術
フロントエンド
・HTML / CSS / Bootstrap
バックエンド
・PHP 8.0.2
・Laravel 8.28.1
データベース
・Mysql 8.0
開発環境
・Docker 20.10.2
・docker-compose 1.27.4
本番環境
・AWS(VPC、EC2、S3、Route53)
・Nginx
各種解説内容投稿
- 投稿日:2021-03-29T03:03:28+09:00
ポートフォリオの解説投稿(自分なりのアウトプットです!!)
自分が書いたコードを説明できないのは恥だよ!!
これは、あるYouTuberの言葉です。
確かにそうですよね…
はい、やります。やらせてください!
解説やらせてください!
てな感じで始めます笑
と、その前にこちらがポートフォリオの内容です。三部構成でいきます!
Controller・Model・Viewの三部構成で解説していきます。
主にTweetの内容からです。まずControllerから行きます!
app/Http/Controllers/TweetController.phpnamespace App\Http\Controllers; #namespaceとは、名前空間といい、こちらを使用してクラス被りをしないようにする。というもの。 use App\Models\Tweet; use App\Models\Tag; use App\Models\Comment; #各種のModelを読み込む際の記述 use Storage; use Illuminate\Support\Str; #ストレージファイルの読み込む際の記述 use InterventionImage; use Image; #写真の編集ライブラリ(Intervention Image)を使用したため、その読み込みの記述 use Illuminate\Http\Request; use App\Http\Requests\TweetRequest; #Requestファイル(バリデーションのルールが書いてあるファイル)を読み込む際の記述 class TweetController extends Controller { /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { $q = \Request::query(); #$qにリクエストのクエリストリングを入れる。 if(isset($q['tag_name'])){#もし、$qの中のtag_nameに値があればtrueを実行する $tweets = Tweet::with(['user', 'tags'])->latest()->where('tag_box', 'like', "%{$q['tag_name']}%")->paginate(10); #$tweetsにN+1問題を解決するためにwithを使用し[userとtags]のModel情報を取得し、最新の情報を取得し、カラム名tag_boxを文字列検索で$q['tag_name']を取得し、ページネーションで10ページを表示させる。 #withとはリレーションを解決したい時に使用する。 #whereの引数について、第1引数はカラム名です。第2引数はデータベースがサポートしているオペレーターです。第3引数はカラムに対して比較する値である。 $tags = \DB::table('tags')->get(); #$tagsにtagsテーブルの情報を取得する。 return view('tweets.index', [ 'tweets' => $tweets, 'tags' => $tags, 'tag_name' => $q['tag_name'] ]); #返り値でViewに値を渡す。 }else { $tweets = Tweet::with(['user', 'tags'])->latest()->paginate(10); $tags = \DB::table('tags')->get(); $tags_name = []; foreach ($tags as $tag) { array_push($tags_name, $tag->tag_name); } #tags_nameに配列として値を渡す。foreachで値を回し、タグテーブルの情報からtag_nameを配列に追加していく。 return view('tweets.index', [ 'tweets' => $tweets, 'tags' => $tags ]); } } /** * Show the form for creating a new resource. * * @return \Illuminate\Http\Response */ public function create() { return view('tweets.create' ); } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(TweetRequest $request) { $tweet = new Tweet; $tweet->user_id = $request->user_id; $tweet->content = $request->content; $tweet->tag_box = $request->tag_box; $tweet->title = $request->title; #$tweetにパラメーターで取得した情報をそれぞれ値を入れていく。 if($request->hasFile('image')){#パラメーターの中にimageが入っていたらtrueで値を返す。 $filename = $request->file('image');#リクエストでimageファイルとして$filenameに入れる。 $name = $filename->getClientOriginalName(); #$filenameの画像の名前を取得する $ext = strtolower(substr($filename->getClientOriginalName(), strrpos($filename->getClientOriginalName(), '.')+1));#strtolowerで大文字を小文字に戻し、substrで文字を切り出す。これで、写真の拡張子を取得する。 if(!in_array($ext, ['png', 'jpg', 'gif', 'jpeg'], true)) {#['png', 'jpg', 'gif', 'jpeg']の配列に$extの値があるかチェックする。 $tag_view = '画像以外のファイルが指定されています。画像ファイル(png/jpg/jpeg/gif)を指定して下さい'; return view('tweets.tag', compact('tag_view')); #tweets.tagのViewにcompactでtag_viewの情報を渡して表示させる。 } $imageFile = time(). '_' . $name; $imagePath = storage_path('app/public/') . $imageFile; $image = Image::make($filename)#imageを作成する ->resize(1000, null, function ($constraint) { $constraint->aspectRatio();#横幅を1000にして縦横比を保持したまま変更を行う。 $constraint->upsize();#小さい写真を無理やり1000にすることをせずにそのままのサイズを維持する。 }) ->orientate()#画像の向きを自動的に調整する。 ->save($imagePath);#その情報を一旦ローカルに保存する。 $path = Storage::disk('s3')->putFile('myprefix',$imagePath, 'public');#config/filesystems.phpの中に設定した情報からawsのs3にファイル名は自動で生成し保存する。 $tweet->image = Storage::disk('s3')->url($path); Storage::disk('local')->delete('app/public/' . $imageFile);#ローカルの情報を削除する。 } preg_match_all('/#([a-zA-Z0-90-9ぁ-んァ-ヶー一-龠]+)/u', $request->tag_box, $match);#ハッシュタグで始まる単語を取得。結果は、$matchに多次元配列で代入される。 # $match[0]に#(ハッシュタグ)あり、$match[1]に#(ハッシュタグ)なしの結果が入ってくるので、$match[1]で#(ハッシュタグ)なしの結果のみを使う。 $tags = []; foreach ($match[1] as $tag) { $found = Tag::firstOrCreate(['tag_name' => $tag]);#firstOrCreateメソッドで、tags_tableのnameカラムに該当のない$tagは新規登録される。 array_push($tags, $found);#$foundを配列に追加します(=$tags) } #投稿に紐付けされるタグのidを配列化 $tag_ids = []; foreach ($tags as $tag) { array_push($tag_ids, $tag['id']); } $tag_count = count($tag_ids);#tagの数を取得する。 if ($tag_count <= 5){#もしtagの数が5以下ならtrueを実施 $tweet->save(); $tweet->tags()->attach($tag_ids);#投稿ににタグ付するために、attachメソッドをつかい、モデルを結びつけている中間テーブルにレコードを挿入 return redirect('/top'); } else{ $tag_view = 'タグ数が5つ以上ですので変更してください。'; $tweet_id = $id; return view('tweets.tag-edit', compact('tag_view','tweet_id')); } } /** * Display the specified resource. * * @param \App\Models\Tweet $tweet * @return \Illuminate\Http\Response */ public function show(Tweet $tweet) { $tweetid = $tweet->id; $comments = Comment::where('tweet_id', '=', $tweetid)->get();#commentの情報をtweet_idと$tweetidの情報が同じならその情報を取得する。 return view('tweets.show',[ 'tweet' => $tweet, 'comments' => $comments, ]); } /** * Show the form for editing the specified resource. * * @param \App\Models\Tweet $tweet * @return \Illuminate\Http\Response */ public function edit($id) { $tweet = Tweet::with(['user','comments'])->findOrFail($id);#[userとcomments]のModel情報を取得し、$id(パラメーター)の情報から取得する。 return view('tweets.edit',[ 'tweet' => $tweet, ]); } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param \App\Models\Tweet $tweet * @return \Illuminate\Http\Response */ public function update(Request $request) { #ここはstoreクラスとほとんど同じ $id = $request->tweet_id; $tweet = Tweet::findOrFail($id); $tweet->content = $request->content; $tweet->title = $request->title; $tweet->user_id = $request->user_id; $tweet->tag_box = $request->tag_box; if($request->hasFile('image')){ $filename = $request->file('image'); $name = $filename->getClientOriginalName(); $ext = strtolower(substr($filename->getClientOriginalName(), strrpos($filename->getClientOriginalName(), '.')+1)); if(!in_array($ext, ['png', 'jpg', 'gif', 'jpeg'], true)) { $tag_view = '画像以外のファイルが指定されています。画像ファイル(png/jpg/jpeg/gif)を指定して下さい'; return view('tweets.tag', compact('tag_view')); } $imageFile = time(). '_' . $name; $imagePath = storage_path('app/public/') . $imageFile; $image = Image::make($filename) ->resize(1000, null, function ($constraint) { $constraint->aspectRatio(); $constraint->upsize(); }) ->orientate() ->save($imagePath); $path = Storage::disk('s3')->putFile('myprefix',$imagePath, 'public'); $tweet->image = Storage::disk('s3')->url($path); Storage::disk('local')->delete('app/public/' . $imageFile); } preg_match_all('/#([a-zA-Z0-90-9ぁ-んァ-ヶー一-龠]+)/u', $request->tag_box, $match); $tags = []; foreach ($match[1] as $tag) { $found = Tag::firstOrCreate(['tag_name' => $tag]); array_push($tags, $found); } $tag_ids = []; foreach ($tags as $tag) { array_push($tag_ids, $tag['id']); } $tag_count = count($tag_ids); if ($tag_count <= 5){ $tweet->save(); $tweet->tags()->sync($tag_ids); return redirect('/top'); } else{ $tag_view = 'タグ数が5つ以上ですので変更してください。'; $tweet_id = $id; return view('tweets.tag-edit', compact('tag_view','tweet_id')); } } /** * Remove the specified resource from storage. * * @param \App\Models\Tweet $tweet * @return \Illuminate\Http\Response */ public function destroy($id) { #Tweetモデルから$idを見つけ出す。 $tweet = TWeet::find($id); preg_match_all('/#([a-zA-Z0-90-9ぁ-んァ-ヶー一-龠]+)/u', $tweet->tag_box, $match); $tags = []; foreach ($match[1] as $tag) { $found = Tag::firstOrCreate(['tag_name' => $tag]); array_push($tags, $found); } $tag_ids = []; foreach ($tags as $tag) { array_push($tag_ids, $tag['id']); } $tweet->tags()->delete($tag_ids);#deleteで削除する。 $tweet->delete();#上記と同じ return redirect('/top'); } public function search(Request $request) { $tweets = Tweet::where('title' ,'like', "%{$request->search}%") ->orwhere('content' ,'like', "%{$request->search}%") ->paginate(10);#Tweetモデルから情報を取得し、where句を使用してパラメータで取得した値をtitleカラムとcontentカラムの中に同じ値があるかを取得する。でそのページネーションを10で表示させる。ものを$tweetsに入れる。 $search_result = '【'. $request->search. '】の検索結果は'.$tweets->total().'件';#検索情報と検索にヒットした数を$search_resultに入れる。 $tags = \DB::table('tags')->get(); return view('tweets.index',[ 'tweets' => $tweets, 'search_result' => $search_result, 'search_query' => $request->search, 'tags' => $tags, ]); } }次はModelの解説していきます!
app/Models/Tweet.phpnamespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Tweet extends Model { protected $table = 'tweets'; protected $fillable = [ 'title', 'user_id','tag_box','content', 'image', ]; public function user(){ return $this->belongsTo(\App\Models\User::class,'user_id'); }#tweetとuserとの1対多の関係 public function tags(){ return $this->belongsToMany('App\Models\Tag'); }#tweetとtagとの多対多の関係 public function comments(){ return $this->hasMany(\App\Models\Comment::class,'tweet_id', 'id'); }#tweetとcommentとの1対多の関係 }最後にViewの解説をします!
resources/views/tweets/index.blade.php@extends('layouts.app')#部分テンプレート @section('content') <h2 class="card-header" style="text-align: center;"> 投稿一覧 </h2> @isset($search_result)#$search_resultの情報が入っていればtrueで実施 <h5 class="card-title" style="text-align: center; padding-top: 30px; font-size: 20px; color: #55c500">{{ $search_result }}</h5> @endisset <div class="row m-3"> <div class="col-sm-3"> <h5 class="card-title"><i class="fas fa-tags"></i>タグ一覧</h5> @foreach($tags as $tag)#Controllerから$tagsの情報を取得して$tagに入れて回す。 <a href="{{ route('tweets.index', ['tag_name' => $tag->tag_name]) }}" class="btn btn-outline-success m-1"> {{ $tag->tag_name }}#tag_nameを表示させる </a> @endforeach </div> <div class="col-sm-9"> @if (session('status')) <div class="alert alert-success" role="alert"> {{ session('status') }} </div> @endif @foreach ($tweets as $tweet) <div class="toast fade show" role="alert" aria-live="assertive" aria-atomic="true"> <div class="toast-header"> <strong class="mr-auto"> <a href="{{ route('users.show', $tweet->user_id) }}" class="btn btn-outline-primary btn-sm">{{ "@".$tweet->user->name }}</a> が <span class="text-muted" style="font-size:15px;">{{ $tweet->created_at->format('Y年m月d日') }}にストック</span> </strong> </div> <div class="toast-body"> <h5 class="card-title"> <i class="fas fa-tags"></i> @foreach($tweet->tags as $tag) <a href="{{ route('tweets.index', ['tag_name' => $tag->tag_name]) }}" class="badge badge-success"> #{{ $tag->tag_name }} </a> @endforeach </h5> <a href="{{ route('tweets.show', $tweet->id) }}" class="text-dark"> <h2 class="card-title">{{ $tweet->title }}</h2> </a> </div> </div> @endforeach @if(isset($tag_name))#もし$tag_nameに情報があればtrueで実施 {{ $tweets->appends(['tag_name' => $tag_name])->links() }}#tag_nameを基にペジネーションリンクにクエリ文字列を付け加えたいときは、appendsメソッドを使用する。linksメソッドは結果の残りのページヘのリンクをレンダーする。 @elseif(isset($search_query))#もし$search_queryの値が入っていればtrueを実施 {{ $tweets->appends(['search' => $search_query])->links() }} @else {{ $tweets->links() }} @endif </div> </div> @endsectionとりあえず、これでアウトプットとします。
自分なりのアウトプットですので間違い等はあるかもしれませんが、自分のための投稿しています。
- 投稿日:2021-03-29T03:03:11+09:00
Laravelでマークダウンを導入するには??
ポートフォリオにMarkdownを導入したい
ポートフォリオになぜMarkdownを導入するに至った経緯は、こちらを読んでください!笑
で、どの様にMarkdownを導入したのかは今回はこれを使用しました。composer require cebe/markdownのライブラリをcomposerでインストールして使用しました。
実際の記述解説
app/Models/Tweet.phpuse cebe\markdown\Markdown as Markdown; #useでインポートを行う。 class Tweet extends Model { public function parse(){ $parser = new Markdown(); #$parserにMarkdownを使用する記述を行う return $parser->parse($this->content); #返り値としてtweetのcontentの内容にparse()メソッドを追加を行う。 } #パース(コンピュータプログラムの機能・処理の一つで、一定の書式や文法に従って記述されたデータを解析し、 #プログラムで扱えるようなデータ構造の集合体に変換することをパースという) public function getMarkdownBodyAttribute(){ return $this->parse(); #返り値としてparse()を実施する。 } }resources/views/tweets/show.blade.php<div> {!! $tweet->markdown_body !!} #これでtweetの中で保存したcontentの内容をmarkdownで表示させる。 </div>参考にしたサイトは以下のとおりです。
・cebe/markdownのGitHub:https://github.com/cebe/markdown
・上の情報を簡略化されたQiita投稿内容実際に使用して
markdownの導入を行う前は、かなり難しそうで手を出しにくいと考えていましたが、使用してみたらハードルは低いと感じました。しかし、これをライブラリなしで導入できる様に勉強に励まないとなと考えます!