僕なりの Laravel のTips

# プロジェクト作成

sailで作成する（最新バージョンで作成される）

```bash
curl -s https://laravel.build/プロジェクト名 | bash
```



composerで作成する（バージョン指定するならこっち）

```bash
composer create-project laravel/laravel プロジェクト名 --prefer-dist "^10.0"
cd プロジェクト名
composer require laravel/sail --dev
php artisan sail:install
sail up -d
```

> バージョン9とBreezeは一緒に使わない。レイアウトがバグる。

以下のようなエラーが出る時があるが、composerのバージョンを最新にしてあげると解決する場合が多い

```bash
The "https://repo.packagist.org/p2/carbonphp/carbon-doctrine-types~dev.json" file could not be downloaded: Failed to open stream: Network is unreachable
```



開発環境の起動

```bash
./vendor/bin/sail up
# or
sail up -d
```

> `.bashrc` にエイリアスを貼ってあげると便利



開発環境の終了

```bash
$ sail stop
```



データベースへの接続

```bash
$ sail mysql -u sail -ppassword

$ sail mysql <dbName>
```



# PHPMyAdmin コンテナの追加

```yaml
    phpmyadmin:
        image: phpmyadmin/phpmyadmin
        depends_on:
            - mysql
        ports:
            - 8888:80
        environment:
            PMA_USER: '${DB_USERNAME}'
            PMA_PASSWORD: '${DB_PASSWORD}'
            PMA_HOST: mysql
        networks:
            - sail
```







# 初期設定

言語を日本語に

logを`daily`channelにする





# サービスの追加

```bash
php artisan sail:add
php artisan sail:add mailpit
```

## Mailpit コンテナの追加

```yaml
    mailpit:
        image: 'axllent/mailpit:latest'
        ports:
            - '${FORWARD_MAILPIT_PORT:-1025}:1025'
            - '${FORWARD_MAILPIT_DASHBOARD_PORT:-8025}:8025'
        networks:
            - sail
```



# デバックバーのインストール

```bash
composer require barryvdh/laravel-debugbar
```

```env
// .envで切り替えできる
APP_DEBUG=true 
```







# ページネーション

次のページへのリンクを整形する

```bash
php artisan vendor:publish --tag=laravel
```







# コード整形

[Code Sniffer](https://github.com/squizlabs/PHP_CodeSniffer) の導入

```bash
 composer require "squizlabs/php_codesniffer=*"
```

独自の `phpcs.xml` を作成

```bash
./vendor/bin/phpcs --standard=phpcs.xml ./
./vendor/bin/phpcbf --standard=phpcs.xml ./
```

`compser.json`のscriptとして登録する

```diff
         "post-create-project-cmd": [
             "@php artisan key:generate --ansi"
+        ],
+        "phpcs": [
+            "./vendor/bin/phpcs --standard=phpcs.xml ./"
+        ],
+        "phpcbf": [
+            "./vendor/bin/phpcbf --standard=phpcs.xml ./"
```



[Husky](https://typicode.github.io/husky/) の導入

```bash
npm install --save-dev husky
npx husky init
```

huskyの`pre-commit.sh`に登録する

```diff
+./vendor/bin/sail composer phpcbf
 ./vendor/bin/sail test
```





# Breeze(Inertia)の導入

```bash
composer require laravel/breeze --dev
```

```bash
php artisan breeze:install
 
php artisan migrate
npm install
npm run dev
```







# モデルと他全てを一気に自動生成

```bash
php artisan make:model -a
```









# APIを作ってみる

```bash
$ php artisan make:model Product -mfs --api

   INFO  Model [app/Models/Product.php] created successfully.

   INFO  Factory [database/factories/ProductFactory.php] created successfully.

   INFO  Migration [database/migrations/2024_01_11_010607_create_products_table.php] created successfully.

   INFO  Seeder [database/seeders/ProductSeeder.php] created successfully.

   INFO  Controller [app/Http/Controllers/ProductController.php] created successfully.
```

`-m`：マイグレーションファイル作成

`-f`：ファクトリ作成

`-s`：シーダー作成

`--api`：APIのCRUD用のコントローラー作成



## マイグレーションファイルの定義

```php
#database/migrations/2024_01_11_010607_create_products_table.php

<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('text'); //追加
            $table->longText('description'); //追加
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('products');
    }
};

```



## モデルの定義

```php
#app/Models/Product.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    use HasFactory;

    //以下4行追加
    protected $fillable = [
        'title',
        'description',
    ];
}

```



## ファクトリの定義

```php
# database/factories/ProductFactory.php

<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Product>
 */
class ProductFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'title' => $this->faker->title(), //追加
            'description' => $this->faker->text(), //追加
        ];
    }
}

```



## シーダー定義

```php
#database/seeders/ProductSeeder.php

<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Product; //追加

class ProductSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        Product::factory()->count(3)->create(); //追加
    }
}

```

```php
#database/seeders/DatabaseSeeder.php

<?php

namespace Database\Seeders;

// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     */
    public function run(): void
    {
        //以下3行追加
        $this->call([
            ProductSeeder::class,
        ]);
    }
}
```

マイグレートとシーディング

```bash
$ php artisan migrate --seed
```

tinkerで確認

```bash
$ sail tinker
> \App\Models\Product::all()
= Illuminate\Database\Eloquent\Collection {#5699
    all: [
      App\Models\Product {#5956
        id: 1,
        title: "Mr.",
        description: "Nulla sit facilis cumque et. Omnis non eligendi suscipit. Occaecati voluptatem quo porro occaecati. Qui sed est officiis ut nihil architecto.",
        created_at: "2024-01-11 02:10:37",
        updated_at: "2024-01-11 02:10:37",
      },
      App\Models\Product {#5957
        id: 2,
        title: "Dr.",
        description: "Explicabo omnis perferendis repudiandae repudiandae. Fuga pariatur saepe cupiditate dolor.",
        created_at: "2024-01-11 02:10:37",
        updated_at: "2024-01-11 02:10:37",
      },
      App\Models\Product {#5958
        id: 3,
        title: "Dr.",
        description: "Aut voluptate id officiis ea dolores. Cumque nihil totam maiores. Et odit placeat itaque ut est impedit. Nisi quo ab inventore nemo vel.",
        created_at: "2024-01-11 02:10:37",
        updated_at: "2024-01-11 02:10:37",
      },
    ],
  }

```



## データの取得

controllerの定義

```php
#app/Http/Controllers/ProductController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Product; //追加

class ProductController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        //以下4行追加
        $products = Product::all();
        return response()->json([
            'products' => $products,
        ], 200);
    }

...省略...
```

routesの定義

```php
#routes/api.php

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProductController; //追加

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "api" middleware group. Make something great!
|
*/

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

Route::apiResource('products', ProductController::class); //追加
```

postmanで確認



## データの登録

requestの定義

```bash
$ sail php artisan make:request StoreProductRequest
```

```php
# app/Http/Requests/StoreProductRequest.php

<?php

namespace App\Http\Requests;

use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException; //追加

class StoreProductRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true; //修正
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'title' => ['required', 'string'], //追加
            'description' => ['required', 'string'], //追加
        ];
    }

    //以下追加
    protected function failedValidation(Validator $validator)
    {
        $res = response()->json([
            'message' => 'Failed create product',
            'errors' => $validator->errors(),
        ], 400);
        throw new HttpResponseException($res);
    }
}
```



controllerの定義

```php
# app/Http/Controllers/ProductController.php

...省略...
    
use App\Http\Requests\StoreProductRequest; //追加

class ProductController extends Controller
{
    
...省略...
    
    public function store(StoreProductRequest $request) //修正
    {
        //以下5行追加
        $product = Product::create($request->all());
        return response()->json([
            'message' => 'Product created successfully!',
            'product' => $product,
        ], 200);
    }

...省略...
```



postmanで確認

- jsonオブジェクト無のボディでpostリクエストする

- jsonオブジェクト有のボディでpostリクエストする

```bash
$ sail php artisan
> \App\Models\Product::all()
= Illuminate\Database\Eloquent\Collection {#5960
    all: [
    
	...省略...

      App\Models\Product {#5965
        id: 4,
        title: "input title",
        description: "input description",
        created_at: "2024-01-11 02:51:08",
        updated_at: "2024-01-11 02:51:08",
      },
    ],
  }
```



## sanctum

laravelでapiを作成する時に使うもの





# Formを作ってみる

## 確認画面をどう作るか

## sessionを使う

大きな流れは

- フォームからの遷移先でセッションに入力値を保存
- 確認画面の表示はセッションの入力値を使う
- 確認画面からの遷移先もセッションの入力値を使う
- 送信処理（確認画面からの遷移先）で二重投稿にならないようにセッションの値を空にする

show→post(sessionに入力値を保存)→confirm（sessionから入力値を取り出す）→send（sessionから入力値を取り出して送信、sessionの入力値を削除）→complete



## DBを使う

- 入力データをデータベースに一時保存し、確認画面でそのデータを参照する。最終的に送信する際に、データベースから情報を取得して処理を行う。
- この方法はデータが大量である場合や、複数ステップにわたるフォーム処理に適している。



## inputの値を受け継いでいく

- こっちを基本的に使う
- sessionを上手く使っていく

## File Upload

## 基本のコード

```php
    public function store(Request $request)
    {
		...

        if ($request->file('image')) {
            $imageName = $request->file('image')->store('public/upload');
            $formData['image'] = $imageName;
        }

        Ramen::create($formData);

        return to_route('admin.ramen.index');
    }

    public function store(Request $request)
    {
		...

        // bladeの場合はこっちを使う
        if ($request->file('image')) {
            $imageName = $request->file('image')->store('public/upload');
            $formData['image'] = $imageName;
        }
        
        // inertiaの場合はこっちを使う
        if ($image = $request->file('image')) {
            $imageName = $image->getFilename(). '.'. $image->getClientOriginalExtension();
            $image->storeAs('public/upload', $imageName);
            $formData['image'] = $imageName;
        }

        Ramen::create($formData);

        return to_route('admin.ramen.index');
    }
```

## 公開

- Laravelでは画像の参照先は`storage/app/public/`
- ただし、そのままではビューで参照することがでない
- `storageディレクトリ`を参照するためにはシンボリックリンクが必要

```bash
php artisan storage:link
```

Inertia の場合は注意。

```bash
cd public
ln -s ../storage/public/upload
```



# Deployしてみる

> [10.x デプロイ Laravel](https://readouble.com/laravel/10.x/ja/deployment.html)

オートローダーの最適化

```bash
composer install --optimize-autoloader --no-dev
```

設定のキャッシュ

```bash
php artisan config:cache
```

ルートのキャッシュ

```bash
php artisan route:cache
```

ビューのキャッシュ

```bash
php artisan view:cache
```

デバッグモード

```php
# .env or config/app.php
APP_DEBUG=false
```



## Conoha Wing

流れ

1. 本番環境とリポジトリをSSH接続可能に

2. publicディレクトリ内にルーティングさせる

    ```
    # /
    
    <IfModule mod_rewrite.c>
    	RewriteEngine On
        RewriteRule ^(.*)$ appName/public/$1 [L]
    </IfModule>
    ```

3. バックエンド

    1. composer

        ```bash
        cd app-name
        ~/bin/composer install
        ```

    2. application encryption key

        ```bash
        php artisan key:generate
        ```

    3. 環境変数

        ```bash
        cp .env.example .env
        ```

        ```diff
        -bash-4.2$ diff -uwBb .env.example .env
        --- .env.example        2024-05-21 09:23:34.117917782 +0900
        +++ .env        2024-05-21 09:37:47.499062046 +0900
        @@ -1,19 +1,19 @@
        -APP_NAME=Laravel
        -APP_ENV=local
        +APP_NAME=hoge
        +APP_ENV=production
         APP_KEY=
        -APP_DEBUG=true
        -APP_URL=http://localhost
        +APP_DEBUG=false
        +APP_URL=https://hoge
        +ASSET_URL=https://hoge
        
         LOG_CHANNEL=stack
         LOG_DEPRECATIONS_CHANNEL=null
        -LOG_LEVEL=debug
        +LOG_LEVEL=error
        
         DB_CONNECTION=mysql
        -DB_HOST=127.0.0.1
        +DB_HOST=hoge_host
         DB_PORT=3306
        -DB_DATABASE=app_name
        -DB_USERNAME=root
        -DB_PASSWORD=
        +DB_DATABASE=hoge_db
        +DB_USERNAME=hoge_user
        +DB_PASSWORD=hoge_pw
        
         BROADCAST_DRIVER=log
         CACHE_DRIVER=file
        ```

    4. *public/.htaccess*

        ```diff
        --- .old.htaccess       2024-05-22 21:14:32.710679534 +0900
        +++ .htaccess   2024-05-22 21:14:55.664738128 +0900
        @@ -1,8 +1,4 @@
         <IfModule mod_rewrite.c>
        -    <IfModule mod_negotiation.c>
        -        Options -MultiViews -Indexes
        -    </IfModule>
        -
             RewriteEngine On
        
             # Handle Authorization Header
        ```

    5. AppServiceProvider

        > [Laracasts](https://laracasts.com/discuss/channels/vite/laravel-vite-assets-blockedmixed-content-issues-in-production-environment)

        ```diff
        diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
        index 452e6b6..03e735c 100644
        --- a/app/Providers/AppServiceProvider.php
        +++ b/app/Providers/AppServiceProvider.php
        @@ -2,6 +2,7 @@
        
         namespace App\Providers;
        
        +use Illuminate\Support\Facades\URL;
         use Illuminate\Support\ServiceProvider;
        
         class AppServiceProvider extends ServiceProvider
        @@ -19,6 +20,8 @@ public function register(): void
              */
             public function boot(): void
             {
        -        //
        +        if($this->app->environment('production')) {
        +            URL::forceScheme('https');
        +        }
             }
         }
        ```

    6. dbのテーブル作成

        ```bash
        php artisan migrate
        ```

        

4. フロントエンド

    - ローカルでプロダクションビルドしてアップロード

        ```diff
            "scripts": {
                "dev": "vite",
                "build": "vite build",
        +       "build:prd": "vite build --mode production"
            },
        ```

        ```bash
        sail npm run build:prd
        ```

    - 本番環境でビルドする

        - nodeとnpmをインストールする

            > [【ConoHa WING】Laravelアプリを公開 | チグサウェブ](https://chigusa-web.com/blog/conoha-wing-laravel/)

            - nodebrewのインストール

                ダウンロードする

                ```bash
                cd ~/bin
                wget git.io/nodebrew
                perl nodebrew setup
                ```

                パスを通す

                ```bash
                vim ~/.bash_profile
                > #以下に変更もしくは追記
                > PATH=$HOME/.nodebrew/current/bin:$PATH:$HOME/.local/bin:$HOME/bin
                
                source ~/.bash_profile
                nodebrew -v
                ```

            - node.jsをインストール

                ```bash
                nodebrew install-binary v16.20.0
                nodebrew ls-remote
                nodebrew list
                nodebrew use v16.20.0
                node -v
                npm -v
                ```

                v20.9.0だと必要パッケージが無くて怒られたので注意。

        - Nodeパッケージのインストールとビルド

            - Nodeパッケージのインストール

                ```bash
                cd app
                npm install
                ```

            - ビルド

                ```bash
                npm run buil
                ```




## さくらのレンタルサーバー

> [さくらのレンタルサーバでLaravelを動かす方法  |  さくらのナレッジ](https://knowledge.sakura.ad.jp/35587/)

流れ

1. 本番環境とリポジトリをSSH接続可能に

2. publicディレクトリ内にルーティングさせる

    ```
    # /
    
    <IfModule mod_rewrite.c>
    	RewriteEngine On
        RewriteRule ^(.*)$ appName/public/$1 [L]
    </IfModule>
    ```

3. バックエンド

    1. composer

        ```bash
        cd app-name
        ~/bin/composer install
        ```

    2. 環境変数

        ```bash
        cp .env.example .env
        ```

        ```diff
        -bash-4.2$ diff -uwBb .env.example .env
        --- .env.example        2024-05-21 09:23:34.117917782 +0900
        +++ .env        2024-05-21 09:37:47.499062046 +0900
        @@ -1,19 +1,19 @@
        -APP_NAME=Laravel
        -APP_ENV=local
        +APP_NAME=hoge
        +APP_ENV=production
         APP_KEY=
        -APP_DEBUG=true
        -APP_URL=http://localhost
        +APP_DEBUG=false
        +APP_URL=https://hoge
        +ASSET_URL=https://hoge
        
         LOG_CHANNEL=stack
         LOG_DEPRECATIONS_CHANNEL=null
        -LOG_LEVEL=debug
        +LOG_LEVEL=error
        
         DB_CONNECTION=mysql
        -DB_HOST=127.0.0.1
        +DB_HOST=mysql○○.<ユーザー名>.sakura.ne.jp
         DB_PORT=3306
        -DB_DATABASE=app_name
        -DB_USERNAME=root
        -DB_PASSWORD=
        +DB_DATABASE=hoge_db
        +DB_USERNAME=hoge_user
        +DB_PASSWORD=hoge_pw
        
         BROADCAST_DRIVER=log
         CACHE_DRIVER=file
        ```

    3. application encryption key

        ```bash
        php artisan key:generate
        ```

    4. dbのテーブル作成

        ```bash
        php artisan migrate
        ```

    5. セキュリティ

        ```htaccess
        <Files .env>
         deny from all
        </Files>
        ```

    6. AppServiceProvider

        > https://laracasts.com/discuss/channels/vite/laravel-vite-assets-blockedmixed-content-issues-in-production-environment

        ```diff
        diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
        index 452e6b6..03e735c 100644
        --- a/app/Providers/AppServiceProvider.php
        +++ b/app/Providers/AppServiceProvider.php
        @@ -2,6 +2,7 @@
        
         namespace App\Providers;
        
        +use Illuminate\Support\Facades\URL;
         use Illuminate\Support\ServiceProvider;
        
         class AppServiceProvider extends ServiceProvider
        @@ -19,6 +20,8 @@ public function register(): void
              */
             public function boot(): void
             {
        -        //
        +        if($this->app->environment('production')) {
        +            URL::forceScheme('https');
        +        }
             }
         }
        ```

​	

## さくらのレンタルサーバー改めて

- リポジトリをtarしてデプロイ

- ローカルでリモートリポジトリをベアリポジトリとしてクローンする。それを本番環境の/git以下に任意の名前でデプロイ

- 公開鯖上で鍵ペアを作って、秘密鍵をgithubアクションのsecretsに登録する

- viteの設定を変える

    ```js
    base: process.env.APP_ENV === 'production' ? '/build/' : '/',
    ```

    

- CD用のスクリプトを作成する

```yaml
name: Deploy to Production

on:
  push:
    branches:
      - master
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: setting .ssh
        run: |
          mkdir -p ~/.ssh
          touch ~/.ssh/id_ecdsa.pem
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ecdsa.pem
          chmod 600 ~/.ssh/id_ecdsa.pem
          touch ~/.ssh/config
          cat <<EOF > ~/.ssh/config
          Host hoge-blog.sakura.ne.jp
          HostName        www1841.sakura.ne.jp
          Port            22
          User            hoge-blog
          IdentityFile    /home/runner/.ssh/id_ecdsa.pem
          StrictHostKeyChecking no
          UserKnownHostsFile=/dev/null
          EOF

      - name: checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: master push to production repository
        run: |
          branch=$(git rev-parse --abbrev-ref HEAD)
          echo "BRANCH=${branch}" >> $GITHUB_OUTPUT
          git remote add production ssh://hoge-blog.sakura.ne.jp/~/git/production-repository-for-moga.git
          git remote -v
          git push -f production ${branch}
        id: set-current-branch

      - name: fetch and pull on server
        run: |
          ssh -tt hoge-blog.sakura.ne.jp '\
            cd ~/www/moga &&\
            git fetch -p origin &&\
            git checkout origin/${{ steps.set-current-branch.outputs.BRANCH }} &&\
            exit
          '
```