Micromodal

Micromodal.js

簡単にモーダルウィンドウを実装できるライブラリ。
CSSのデザインは標準でははいっていないので追加する。
デザインされた雛形を使う

  • 軽量
  • jQuery未使用
  • 背景スクロールを固定
  • 閉じるボタン、背景クリック、ESCボタン押下で閉じる事が可能 micromodal.vercel.app
install

インストールできたらpackage.jsonに追加される。

import

app.cssでインポート

@import 'micromodal';
@tailwind base;
@tailwind components;
@tailwind utilities;

下記フォルダに記載。
initメソッドの中にオプションを書く。
今回はdisableScrollを設定
これにより、モーダルが開いている間はページのスクロールが無効になります。デフォルト値はfalse

import MicroModal from 'micromodal';  // es6 module

MicroModal.init({
  disableScroll: true
});

書き方↓

スタイリング

CSSの雛形↓をコピーしてresource/css/micromodal.cssファイルを作り、貼り付ける。
npm run dev実行しコンパイルする。

gist.github.com

View

product新規作成時、画像を4枚選択できるようにする

コンポーネントを作成し、雛形のHTMLを貼り付け。

buttonはそのままだとsubmitになってpost通信してしまうのでtype="button"を追加

モーダルを表示するためのボタンを追加
<a data-micromodal-trigger="modal-1" href='javascript:;'>Open Modal Dialog</a>

<x-select-image name="image1" />
<div class="modal micromodal-slide" id="modal-1" aria-hidden="true">
  <div class="modal__overlay" tabindex="-1" data-micromodal-close>
    <div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-1-title">
      <header class="modal__header">
        <h2 class="modal__title" id="modal-1-title">
          Micromodal
        </h2>
        <button type="button" class="modal__close" aria-label="Close modal" data-micromodal-close></button>
      </header>
      <main class="modal__content" id="modal-1-content">
        <p>
          Try hitting the <code>tab</code> key and notice how the focus stays within the modal itself. Also, <code>esc</code> to close modal.
        </p>
      </main>
      <footer class="modal__footer">
        {{-- `button`はそのままだとsubmitになってpost通信してしまうので`type="button"`を追加 --}}
        <button type="button" class="modal__btn modal__btn-primary">Continue</button>
        <button type="button" class="modal__btn" data-micromodal-close aria-label="Close this dialog window">Close</button>
      </footer>
    </div>
  </div>
</div>
{{-- モーダルを表示するためのボタンを追加 --}}
<a data-micromodal-trigger="modal-1" href='javascript:;'>Open Modal Dialog</a>

Eager Loading

概要

リレーション先の情報を取得する際には、N+1問題が発生するので、EagerLoading Withメソッドを使い取得する必要がある。

画像一覧のindex.bladeを確認すると
:filename="$product->imageFirst->filename"
とリレーションでリレーション先の情報を取得している

SQLを確認すると
画像が表示されている情報分SQLが発行されて折り、画像が1000件なら1000回SQLが発行されてパフォーマンス落ちる
→N+1問題

N + 1問題の対策

→EagerLoading

readouble.com

リレーション先のリレーション情報を取得 withメソッド、リレーションをドットでつなぐ
‘id’, Auth::id()をwhereすることでログインしているOwnerをgetできる。
$ownerInfo = Owner::with(‘shop.product.imageFirst’) ->where(‘id’, Auth::id())->get();

$ownerInfoをforeachで回してview側で確認していく dd($owner->shop->product);を確認すると、productが5つ表示されていて、これを1つずつ表示する必要があるので、さらにforeachをかく。

ownerに紐付いたproduct情報を表示。
dd($product->imageFirst->filename);を確認すると、画像が取得されてるとわかる。

<?php
foreach($ownerInfo as $owner)
dd($owner->shop->product);
  foreach($owner->shop->product as $product)
  {
     dd($product->imageFirst->filename);
   }
foreachをview側で実装する


→発行されているSQLを確認すると
select * fromimageswhereimages.idin (1, 2, 3, 4)と1つにまとまっていることがわかる。

Laravel 外部キーやリレーションなど

productsテーブル作成

productモデル、マイグレーション作成

外部キーを設定する際には、親のモデルを削除するかどうか、親を削除したときに同時に削除するかを考える必要がある。

ownerを削除したらshopも消える、shopが消えたらproductも消えるようにする→cascadeで削除
今回はcategoryは消えないようにするので、cascadeなし。

モデル名_idとすることでLaravelが自動でどのモデルか推測してくれるがimage1はできないので、constrained('images')でどのモデルか指定する

<?php
public function up()
{
    Schema::create('products', function (Blueprint $table) {
        $table->id();
        //ownerを削除したらshopも消える、shopが消えたらproductも消えるようにする→cascade
        $table->foreignId('shop_id')->constrained()->onUpdate('cascade')->onDelete('cascade');
        $table->foreignId('secondary_category_id')->constrained();
        //モデル名_idとすることでLaravelが自動でどのモデルか推測してくれるがimage1はできないので、constrained('images')どのモデルか指定する
        //画像はからの場合もあるのでnullableをつける
        $table->foreignId('image1')->nullable()->constrained('images');
        $table->timestamps();
            
    });
}
Route

ルーティング(リソース)

<?php
//productのルート(resource)
Route::resource('products', ProductController::class)
//adminで認証していたらで表示
->middleware('auth:owners')->except('show');

ProductController作成(リソース)

モデルの中でリレーションを定義

productモデルにリレーションを追記していく。
categoryやimageを複数の中から1つを選ぶのでbelongs toとする

  • リレーション関係のメソッドの書き方
    第1引数・・紐付けるモデル
    第2引数・・カスタム外部キー名
    第3引数・・親モデルが主キーとしてidを使用していない場合、カスタムキーを指定する

モデル名_idとすることでLaravelが自動でどのモデルか推測してくれるがimage1のように
_idをつけないと推測されないので、idと紐付けると追加

<?php

namespace Database\Seeders;
use Illuminate\Support\Facades\DB;

use Illuminate\Database\Seeder;

class ProductSeeder extends Seeder
class Product extends Model
{
    use HasFactory;

    public function shop()
    {   //productはshopに属する。
        return $this->belongsTo(Shop::class);
    }
    public function imageFirst()
    {   //productはImageに属する。
        return $this->belongsTo(Image::class,'image1','id');
    }
    public function category()
    {   //productはsecondaryCategoryに属する。メソッド名を変えたので外部キーもかく
        return $this->belongsTo(SecondaryCategory::class,'secondary_category_id');
    }
}
Tinkerでリレーションの確認

Stocksテーブル作成

在庫管理・履歴用のテーブルとして作成。
在庫管理は処理が頻繁に発生するので、実際の開発環境では
マスターテーブル(参照用), トランザクションテーブル(処理用) に分けて管理する。

$ php artisan make:model Stock -m

1つのproductが複数のStockをもつ→hasMany

<?php
public function stock()
    {   //1つのproductが複数のStockをもつ。
        return $this->hasMany(Stock::class);
    }

トランザクションテーブルとしてt_テーブル名という名前で作るので、テーブル名をかえる場合はモデルに記載
する
Stock.phpprotected $table = ’t_stocks’;

マイグレーション

テーブル名をt_stocksへ変更
Upメソッド、downメソッドともにテーブル名変更

<?php
  public function up()
    {
        Schema::create('t_stocks', function (Blueprint $table) {
            $table->id();
            $table->foreignId('product_id')->constrained()->onUpdate('cascade')->onDelete('cascade');
            $table->tinyInteger('type');
            $table->integer('quantity');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('t_stocks');
    }
Seeder
root@aba451969df6:/var/www/html# php artisan make:seed StockSeeder
Seeder created successfully.

root@aba451969df6:/var/www/html# php artisan migrate:refresh --seed
<?php
public function run()
    {
        DB::table('t_stocks')->insert([
            [
                'product_id' => 1,
                'type' => 1,
                'quantity' => 5,
            ],
            [
                'product_id' => 1,
                'type' => 1,
                'quantity' => -2,
            ]]);
    }
Tinkerでリレーションの確認

Laravel Seeder複数テーブル書く方法

primary_categoriesテーブルとsecondary_categoriesテーブルを作成。


どちらも関係のあるカテゴリーなので1つのマイグレーションファイルに書いていくので1つだけ作成。

primary_categoriesは複数のカテゴリーをもつので1対多の関係になる。

php artisan make:model PrimaryCategory -m
php artisan make:model SecondaryCategory


PrimaryCategoryモデル
 <?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use APP\Models\SecondaryCategory;

class PrimaryCategory extends Model
{
    use HasFactory;

    public function secondary()
    {
        //primaryは複数のcategoryをもてるのでhasManyとする
        return $this->hasMany(SecondaryCategory::class);
    }
}
SecondaryCategoryモデル
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use APP\Models\PrimaryCategory;

class SecondaryCategory extends Model
{
    use HasFactory;

    public function primary()
    {
        //primaryに属する
        return $this->belongsTo(PrimaryCategory::class);
    }
}
マイグレーションファイル

2つのテーブルを記載
※secondaryテーブルから先に削除しないと外部キーエラーになる。

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateCategoriesTable extends Migration
{
    public function up()
    {   //schema::create('テーブル名')
        Schema::create('primary_categories', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->integer('sort_order');
            $table->timestamps();
        });

        Schema::create('secondary_categories', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->integer('sort_order');
            //外部キーFK設定
            $table->foreignId('primary_category_id')->constrained();
            $table->timestamps();
        });
    }

    public function down()
    {
        //migate:refreshなどのコードでテーブルを削除する処理を書く
        //secondaryテーブルから先に削除しないと外部キーエラーになるので注意
        Schema::dropIfExists('secondary_categories');
        Schema::dropIfExists('primary_categories');
    }
}
Seeder作成


DatabasesSeederに記載→CategorySeeder::class,

楽天のカテゴリーを参考にSeederを作成。2つのテーブルを書いていく。

CategorySeeder.php

migrate

Tinkerでリレーションの確認

Laravel 画像の複数アップロード

inputタグに追記

multiple属性、name属性をfiles[image]とする。 files[image]としてファイルを選択して送信すると、Requestとして配列で渡ってくるので配列のバリデーションが必要。 →フォームリクエストに追記する。

<div class="p-2 w-1/2 mx-auto">
<div class="relative">
<label for="image" class="leading-7 text-sm text-gray-600">画像</label>
{{-- 画像の場合file,name属性でコントローラのほうでimageとすれば画像を取得できる --}}
<input type="file" id="image" name="files[][image]" multiple accept=“image/png,image/jpeg,image/jpg” class="w-full ~~">
</div>
</div>
バリデーション追記

readouble.com 配列内の属性をバリデーションするには、「ドット記法」が使える。
リクエストにfiles[][image]が渡るので、以下のように書ける。
files[].*.image
→フォームリクエストに記述。

<?php
public function rules()
{
    $rules = [
        'image' => ['image','mimes:jpg,jpeg,png','max:2048'],
        'files.*.image' => ['image','mimes:jpg,jpeg,png','max:2048'],
    ];
    if($this->route === 'owner.images.store'){
        $rules = array_merge($rules,[
            'files' => 'required',
        ],);
    }
    return $rules;
}

public function messages() {
    return [
    'image' => '指定されたファイルが画像ではありません。',
    'mines' => '指定された拡張子(jpg/jpeg/png)ではありません。',
    'max' => 'ファイルサイズは2MB以内にしてください。',
    ];
    }
Controller

画像を複数アップロードする場合、取得する方法が変わってくる。
fileメソッドの引数としてname属性であるfilesをいれることで複数の画像を配列形式で取得できる。

<?php
public function store(UploadImageRequest $request)
{
    //create.blade側のinputのname属性filesを引数とすることで、複数の画像を配列形式で取得
    $imageFiles = $request->file('files');
    if(!is_null($imageFiles)){
        //foreachで1つずつImageServiceでファイル名作る→拡張子取得→interventionImageでリサイズ
        //出来上がったファイル名を$fileNameToStoreとして取得し、createで保存
        foreach($imageFiles as $imageFile){
            $fileNameToStore = ImageService::addByImage($imageFile, 'products');
             Image::create([
                'owner_id' => Auth::id(),
                'filename' => $fileNameToStore
             ]);
        }
    }
    return redirect()->route('owner.images.index')->with(['message' => '画像登録を実施しました。','status'=>'info']);
}

Docker上のLAMP環境にXdebugをいれる。(VScode)

概要

VScode拡張機能Xdebug v3.2.0のインストール

Docker上に構築したLAMP環境にxdebugを入れてステップイン・アウトを実行する
vscode上で実行できるようにする

現在のversion
  • mysql5.7
  • phpmyadmin
  • Laravel Framework 8.83.26
  • PHP Version 8.1.13
  • Apache/2.4.54 (Debian)
    php.ini作成

    (Xdebugのv3系)として設定する。
    compose.ymlのvolumesでphp.iniのPATH追加、デフォルトではdockerにはphp.iniはないので、ローカルにphp.iniを作ってdocker側にマウント。→Dockerfileと同じ場所に配置する。

[xdebug]
xdebug.mode=debug
xdebug.start_with_request = yes
; ホスト側のIP
; host.docker.internalとすることでdockerのhostマシンのIPを解決
xdebug.client_host=host.docker.internal
xdebug.client_port=9013
xdebug.discover_client_host = 1
Dockerfile

Dockerhubからwith apacheのものを使用し、xdebugをインストール。
またdocker-php-ext-enableでpecl拡張を有効化できるのでdocker-php-ext-enable xdebugを実行。

ビルド後、/usr/local/etc/php/conf.d/docker-php-ext-xdebug.inizend_extension=xdebugが出力。

# image
FROM php:8.1-apache
# Laravelの依存PHPモジュールのInstall
# Laravelで必要なmodRewriteを有効化
RUN apt update \
        && apt-get install -y wget git unzip libpq-dev libfreetype6-dev libjpeg62-turbo-dev libpng-dev \
        && mv /etc/apache2/mods-available/rewrite.load /etc/apache2/mods-enabled
# Install MySQL
RUN docker-php-ext-install pdo pdo_mysql
# コンテナの作業ディレクトリを指定
WORKDIR /var/www/html
# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Install xdebug
RUN pecl install xdebug \
    && docker-php-ext-enable xdebug
# Install node.js,InterventionImage
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - \
    && apt-get install -y nodejs \
    && docker-php-ext-install -j$(nproc) gd \
Docker-compose.yml

デフォルトではdockerにはphp.iniはないので、ローカルにphp.iniを作ってdocker側に追加。
アプリケーションコンテナのvolumesにマウントするPATHを記載。
→マウントされてるか確認。

version: '3'
services: 
  # php-apacheコンテナ
  web_1:
    container_name: laravel_umarche
    # Dockerfileを使って、コンテナをビルドするpath。web_umarcheディレクトリに宣言されたDockerfileがコンテナのビルドに使用される。
    build: ./web_umarche/app
    # php-apacheコンテナの前にまずDBコンテナをビルドする必要があることをDockerに認識
    depends_on: 
      - db
    # コンテナとホスト側のディレクトリをマウントする
    volumes: 
      # laravelのソースが入るpath
      - ./umarche/:/var/www/html/
      # Apacheによりデフォルトで有効化されている000-default.confホストとアプリケーションの仮想ホストの設定をリンク
      - ./web_umarche/app/default.conf:/etc/apache2/sites-enabled/000-default.conf
      # php.iniにxdebugのport設定
      - ./web_umarche/app/php.ini:/usr/local/etc/php/php.ini
    # コンテナ内部80番portを開いて、ホストの8005番にポートフォワーディング
    ports: 
      - "8005:80"
  phpmyadmin:
    container_name: phpmyadmin
    image: phpmyadmin
    restart: always
    #コンテナ内部80番portを開いて、ホストの8080番にポートフォワーディング
    ports:
      - "8080:80"
    #自動ログイン環境設定、host、ログイン情報指定
    environment:
     # 1に設定すると任意のサーバーへの接続が許可される 
     - PMA_ARBITRARY=1
     # SQLサーバーのportの設定
     - PMA_HOST=db:3306
     # SQLサーバーに接続するユーザー名
     - PMA_USER=root
     # SQLサーバーに接続するユーザーのパスワード
     - PMA_PASSWORD=rootroot
  db:
    container_name: mysql
    # DockerHubからMySQL5.7イメージをDLしてくる指定
    image: mysql:5.7
    # 起動時のコマンド
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
    # コンテナ内の環境変数→.envにも記載
    environment:
      MYSQL_DATABASE: laravel_umarche
      MYSQL_USER: docker
      MYSQL_PASSWORD: root
      MYSQL_ROOT_PASSWORD: rootroot
      TZ: Asia/Tokyo
    # mysqlはデフォルトで3306port
    ports: 
      - "3311:3306"
    # 設定ファイルとMySQLのデータが保存されるpathをマウント。コンテナは基本的に起動時に変更されてもコンテナ自体が止まるとデータが消えてしまうため、保存しておきたいものはホストマシンと同期しておく必要がある。
    volumes: 
      - ./web_umarche/db/data:/var/lib/mysql
      - ./web_umarche/db/my.cnf:/etc/mysql/conf.d/my.cnf
ビルド!
docker-compose down && docker-compose build && docker-compose up


インストールに成功するとphpinfoにxdebug項目が追加される

Vscode側の設定

xdebugをインストール

端を右クリックして実行とデバッグのアイコンを出す。

右の歯車マークを押してlaunch.jsonを編集。

「pathMappings」はドキュメントルートではなく、ローカルとコンテナ側でマウントしている同じPATHをマッピングする
→docker-compose.ymlのvolumes

{
  // IntelliSense を使用して利用可能な属性を学べます。
  // 既存の属性の説明をホバーして表示します。
  // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Xdebug Cloud",
      "type": "php",
      "request": "launch",
      "port": 9013,
      "pathMappings": {
      "/var/www/html":"${workspaceRoot}/umarche"
      }
    },
ブレイクポイントをはって動作確認

gyazo.com