20210329のlaravelに関する記事は14件です。

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.php
MyGuard 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.php
class 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.php
Route::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)というメソッドを実行し、$rememberMetrueにすることで次回から自動ログインできる、と書いてあります。

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()メソッドはその中で実装しなければいけないメソッドの一つ」ということです。

StatefulGuardlogin()メソッドの他にこれらのメソッドを実装する必要があります。

  • attempt()
  • once()
  • login()
  • loginUsingId()
  • onceUsingId()
  • viaRemember()
  • logout()

これらのメソッドを生真面目に実装しても良いのですが、認証状態を継続したい、という要件だけならSessionGuardを継承してしまうのが早いです。

まず親クラスのSessionGuardのuser()を呼び出し、ステートフルな認証情報からユーザーが取得できたら、それを返します。見つからなかった場合は、自前の認証ロジックを呼び出し、SessionGuardlogin()メソッド(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());
        });
    }
}

以上!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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失敗時のメッセージを返却できる

参照

すみません。ブックマークし忘れてしまいました...

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelのルーティングの状態をファイルで確認してみる

環境

バージョン等
Laravel 6.20.19
MacOS 10.15.7 Catarina

①普通にルーティングを確認してみる

Laravelの通常のWebページとしてのルーティングはRoutes/web.phpにいろいろと記載していますが、
prefixとかgroupeなどを記述していると、実際にどうなっているか分からなくなったりしますよね。

これを確認する時に使うのが、

ターミナル
$ php artisan route:list

というコマンド。

なのですが、これがずら〜と表示されてちょっと分かりづらい。

Before

image.png

②見やすくルーティングを表示する

ということでこれをファイルとして保存して見やすく表示してみましょう。
route:listコマンドに> .routeを追加すればOKです。

ターミナル
$ php artisan route:list > .route

これでプロジェクトの直下に.routeというファイルが保存されました。

After

中身を表示するとこんな感じで見やすく表示されます。
image.png
作成してしまえばその後はコマンドを実行せずに確認できるのも嬉しいですね。
なおルーティングにもし変更があれば、再度同じコマンドを実行すれば上書きされます。

また作成した.routeファイルは、チームで共有する必要はないと思いますので.gitignoreに追加すると良いです。

.gitignore
.route #追記



以上、小技ではありますが最後までお読みいただきありがとうございます〜!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】ルーティングの状態をファイルで見やすく確認する小技

環境

バージョン等
Laravel 6.20.19
MacOS 10.15.7 Catarina

①普通にルーティングを確認してみる

Laravelの通常のWebページとしてのルーティングはRoutes/web.phpにいろいろと記載していますが、
prefixとかgroupeなどを記述していると、実際にどうなっているか分からなくなったりしますよね。

これを確認する時に使うのが、

ターミナル
$ php artisan route:list

というコマンド。

なのですが、これがずら〜と表示されてちょっと分かりづらい。

Before

image.png

②見やすくルーティングを表示する

ということでこれをファイルとして保存して見やすく表示してみましょう。
route:listコマンドに> .routeを追加すればOKです。

ターミナル
$ php artisan route:list > .route

これでプロジェクトの直下に.routeというファイルが保存されました。

After

中身を表示するとこんな感じで見やすく表示されます。
image.png
作成してしまえばその後はコマンドを実行せずに確認できるのも嬉しいですね。
なおルーティングにもし変更があれば、再度同じコマンドを実行すれば上書きされます。

また作成した.routeファイルは、チームで共有する必要はないと思いますので.gitignoreに追加すると良いです。

.gitignore
.route #追記



最後までお読みいただきありがとうございます〜!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP if文とcount()の混合条件の作成

はじめに

今回は基本的な投稿システムが完了したので少しこだわりを入れて備忘録として投稿いたします。これを利用してlaravelに組み込みます。

今回の条件

  1. 1件以上のコメントがある場合は現在〇〇件コメント数という風に表示
  2. コメントが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

“スクリーンショット” 2021-03-29 17.29.14.jpg

解説

今回は各々が投稿した投稿内容の詳細にコメントを掲載するようになっています。
$postは投稿の変数になります。commentsはPost.modelのcommentsを表しています。
>= 1で1件以上のコメントがある場合は不等号を使用して表示させる様にしています。

Post.php
    public function comments()
    {
        // 投稿は複数のコメントを持つ
        return $this->hasMany('App\Models\Comment');
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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つです。

  1. タイムゾーン
  2. 言語設定
  3. DBの文字コード
  4. デバッグバー
  5. DB設定
  6. エラーメッセージの日本語訳
  7. 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などの値がユーザーに見えてしまうため本番環境ではデバッグバーを非表示にする必要があります。

.env
APP_DEBUG=false

DB設定

.envファイルのDBに関する記載の部分を修正していきます。

.env
DB_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を修正

.env
DB_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内に格納します。

任意修正箇所

エラーメッセージが一部英語で表示されます。

スクリーンショット 2021-03-29 12.46.06.png

エラーメッセージの変更は以下のように行います。

resources/lang/ja/validation.php
"attributes" => [
  "password" => "パスワード"
]

スクリーンショット 2021-03-29 12.48.11.png

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.php
Route::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/UserController
use 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.php
public 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.php
public 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
  |__ img
resources/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.php
use App\Models\Post

public function run() {
    // 200個作成
    factory(Post::class, 200)->create();
}
③データベースシーダーに追記
database/seeds/DatabaseSeeder.php
public 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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windows10にLaravel 8.xの環境を構築する

Windows10にLaravel 8.*の環境を作成するにあたっての備忘録です。
Laravel 7まではcomposerで環境を構築できていましたが、
Laravel 8~ではDocker上に環境を構築するようです。
Windows10に環境を作成するにあたっての備忘録。

Docker Desktopをインストール

ダイアログに従って普通にインストール。
Docker Desktop
https://www.docker.com/products/docker-desktop

Windows Subsystem for Linux 2(WSL2)インストール

以下URLの「Manual Installation Steps」を参考に。
https://docs.microsoft.com/en-us/windows/wsl/install-win10

PowerShellを管理者権限で起動

image.png

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 /norestart

WSL2 Linuxカーネルアップデートパッケージをダウンロード

リンク先の「WSL2 Linux kernel update package for x64 machines」から
ダウンロードしてインストール。
https://docs.microsoft.com/en-us/windows/wsl/install-win10

WSL2をデフォルトバージョンとして設定

これからインストールする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 --verbose

Docker Desktopの設定

DockerがWSL2をベースに動き、またインストールしたLinuxサブシステムを認識するための設定。

Docker Desktopを起動し、メニューバーの「Settings」(歯車のアイコン)をクリック。
image.png

左メニューの「General」が選択された状態で、
「Use the WSL 2 based engine」が有効になっていることを確認。
image.png

左メニューの「Resources」→「WSL INTEGRATION」で、
インストールしたLinuxサブシステムにチェックを入れ(自分の場合はkaki-linuxです)、
Apply & RestartをクリックしてDockerを再起動。
image.png

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ページが表示されれば成功!
image.png

参考

https://readouble.com/laravel/8.x/ja/installation.html
https://docs.microsoft.com/en-us/windows/wsl/install-win10

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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-desktop

Windows Subsystem for Linux 2(WSL2)インストール

以下URLの「Manual Installation Steps」を参考に。
https://docs.microsoft.com/en-us/windows/wsl/install-win10

PowerShellを管理者権限で起動

image.png

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 /norestart

WSL2 Linuxカーネルアップデートパッケージをダウンロード

リンク先の「WSL2 Linux kernel update package for x64 machines」から
ダウンロードしてインストール。
https://docs.microsoft.com/en-us/windows/wsl/install-win10

WSL2をデフォルトバージョンとして設定

これからインストールする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 --verbose

Docker Desktopの設定

DockerがWSL2をベースに動き、またインストールしたLinuxサブシステムを認識するための設定。

Docker Desktopを起動し、メニューバーの「Settings」(歯車のアイコン)をクリック。
image.png

左メニューの「General」が選択された状態で、
「Use the WSL 2 based engine」が有効になっていることを確認。
image.png

左メニューの「Resources」→「WSL INTEGRATION」で、
インストールしたLinuxサブシステムにチェックを入れ(自分の場合はkaki-linuxです)、
Apply & RestartをクリックしてDockerを再起動。
image.png

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ページが表示されれば成功!
image.png

参考

https://readouble.com/laravel/8.x/ja/installation.html
https://docs.microsoft.com/en-us/windows/wsl/install-win10

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel PDF日本語化で一部文字化けが起きる時の対処

環境

  • PHP 7.2
  • Laravel 6.x
  • Composer library
    • barryvdh/laravel-dompdf

事象

前提の日本語対応済み。
請求書のPDFでご請求金額と表示されるはずが、ご☒☒☒☒といった感じで一部の日本語が文字化けを起こす。

前提

日本語対応

  1. barryvdh/laravel-dompdfライブラリをインストール
  2. IPAからフォントダウンロード
  3. storage/fonts/配下に配置
  4. inline cssで@font-face font-family定義

対処

  1. 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
  2. 念の為、キャッシュ一式クリアする
    • php artisan optimize:clear
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.4

Serverless 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.yml
service: 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: DBCluster
config/secret/.secret.dev.yml
USER_NAME: root
PASSWORD: password

データベース名、バケット名は任意で指定します。

config/config.dev.yml
DB_PORT: 3306
DB_DATABASE: hoge_app
AWS_BUCKET: kai-laravel-test

Lambda関数に環境変数を追加する

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-enginemode

    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でcharacter_setを指定していますが、これはMySQL5.7.7未満だと標準のcharasetがutf8mb4となり、unique制約をつけたカラムで文字数オーバーになるのを回避するためです。
詳細は下記が詳しいです。

Laravel5.4以上、MySQL5.7.7未満 でusersテーブルのマイグレーションを実行すると Syntax error が発生する - Qiita
https://qiita.com/beer_geek/items/6e4264db142745ea666f

    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"

デプロイ

serverless deployコマンドでデプロイします。時間がかかるのでデプロイ中はお茶でものんでまったりと待ちます。

> ./vendor/bin/sail npm run prod
> serverless deploy

image.png
デプロイ後、エンドポイントにアクセスして以下のように表示されたらOKです。
image.png

静的ファイルの配置

S3バケットにプロジェクトのpublicディレクトリをコピーします。これはAWS Lambda関数へのアクセスだけだと、public/css/app.cssなどへアクセスできないので、デプロイで作成した、アセット用のS3バケットへファイルを配置するためです。
ファイルがないとブラウザでアクセスした場合、ログイン画面へアクセスすると以下のようになります。
image.png

> aws s3 sync public s3://kai-laravel-test/public --delete

ファイルコピー後、下記のようになっていたらOKです。
image.png

マイグレーション

Brefにはcliコマンドが提供されているので、それを利用してAWS Lambdaのlaravel-dev-artisan関数を利用してマイグレーションします。
cliコマンドはAWSのクレデンシャルファイルを参照するので、sailコマンドを挟まないほうが手っ取り早いです。

> ./vendor/bin/bref cli --region=ap-northeast-1 laravel-dev-artisan -- migrate

image.png

動作確認

実際にデプロイした環境にアクセスしています。
image.png
image.png
今回はメール送信については対応していないので、パスワード変更のためのメール送信はエラーとなります。
image.png
image.png

リソースの削除

動作確認ができてリソースが不要であれば削除します。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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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.js
const 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.js
import './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.js
try {
    window.Popper = require('popper.js').default;
    window.$ = window.jQuery = require('jquery');

    require('bootstrap');
} catch (e) {}

以降省略

上記箇所でも、jQueryを呼んでいたので、app.blade.phpとダブルで呼び出していたことが原因でした!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ボクとPHP・Laravel・Dockerとの約200時間

始まりは突然に…

あれは、ある企業に面接したときの事でした。それまで、私はRubyでの開発を行っており、ポートフォリオもRailsで作成していました。その面接で、ポートフォリオの説明を行い面接終了時に次のようなお言葉を頂いた時から始まりました。
あなたに課題を出します。それを期限を決めませんが行ってください。とのことでした。課題とは以下の通りです。
Image from Gyazo

:fire:それを成し遂げた理由:fire:

それは、至って単純な話です。
やってみたい・挑戦してみたいと思ったかです!
やりたいやってみせるという気持ちで望みました!

で、一体何から着手したのか

プロセスは以下の通りです。
①プロゲートの初級(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

:computer:各種解説内容投稿

Dockerの内容解説
マークダウン導入内容解説
投稿内容の解説

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ポートフォリオの解説投稿(自分なりのアウトプットです!!)

自分が書いたコードを説明できないのは恥だよ!!

これは、あるYouTuberの言葉です。
確かにそうですよね…
はい、やります。やらせてください!
解説やらせてください!
てな感じで始めます笑
と、その前にこちらがポートフォリオの内容です。

三部構成でいきます!

Controller・Model・Viewの三部構成で解説していきます。
主にTweetの内容からです。

まずControllerから行きます!

app/Http/Controllers/TweetController.php
namespace 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.php
namespace 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

とりあえず、これでアウトプットとします。
自分なりのアウトプットですので間違い等はあるかもしれませんが、自分のための投稿しています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelでマークダウンを導入するには??

ポートフォリオにMarkdownを導入したい

ポートフォリオになぜMarkdownを導入するに至った経緯は、こちらを読んでください!笑
で、どの様にMarkdownを導入したのかは今回はこれを使用しました。

composer require cebe/markdown

のライブラリをcomposerでインストールして使用しました。

実際の記述解説

app/Models/Tweet.php
use 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の導入を行う前は、かなり難しそうで手を出しにくいと考えていましたが、使用してみたらハードルは低いと感じました。しかし、これをライブラリなしで導入できる様に勉強に励まないとなと考えます!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む