Laravel初学者による私的メモ

# はじめに

前職では素のPHPやCakePHPを使用したり、オレオレフレームワークを作ってましたが、現職ではLaravelを使用しております。
機能がありすぎてわからないぐらい、Laravelって便利ですよね。
そこで、初学者な私が日常的に参照している内容をまとめてみます。
腕に覚えのある諸兄姉にとってはつまらない内容かと思いますが、間違い等ありましたら、コメントいただけますと幸いです。

:::note warn
※全機能を網羅しているわけではありません。
:::

# 勉強用の環境構築

[こちら](https://readouble.com/laravel/10.x/ja/installation.html)を参照しながら、Laravel Sailを使用しました。
[Laravelとの出会い](https://readouble.com/laravel/10.x/ja/installation.html) | [ReadDouble](https://readouble.com/)

```bash
# 適当なフォルダを作った後、以下のコマンドを実行
curl -s https://laravel.build/example-app | bash
```

# ■ artisanコマンド

Laravel Sail を使用する場合、`php artisan` のところを `./vendor/bin/sail artisan` に読み替えてください。

#### コントローラー作成
```bash
php artisan make:controller HogeController
```

#### リソースコントローラー作成
```bash
php artisan make:controller HogeController -r
```

#### リクエストクラス作成
```bash
# app/Http/Requests/User/StoreViewedContentRequest.php が作成される。
php artisan make:request User/StoreViewedContentRequest
```


#### モデル作成
```bash
# モデル名はテーブル名の単数形のパスカルケース
php artisan make:model Hoge
```
|  オプションスイッチ | 意味 |
| -- | -- |
| -c | コントローラを同時に作成する |
| -r | リソースを同時に作成する |
| -cr | コントローラとリソースを同時に作成する |
| -R | リクエストを同時に作成する |
| -m | マイグレーションを同時に作成する |

#### コマンドラインアプリケーション作成
```bash
php artisan make:command PayCommand
```

####  ルートの確認
```bash
php artisan route:list
```

#### キャッシュクリア
```bash
# キャッシュ
php artisan cache:clear

# 設定
php artisan config:clear

# ルート
php artisan route:clear

# ビュー
php artisan view:clear
```

#### マイグレーション

DBテーブル名は複数形

```bash
# マイグレーションファイル作成
php artisan make:migration ファイル名 --create=members
```
※ファイル名はわかりやすい方がよい
| 用途 | ファイル名の例 |
| -- | -- |
| 新規テーブル作成 | create_テーブル名_table |
| テーブル内容編集 | modify_テーブル名_table |

```bash
# すべてのマイグレーションを一括してロールバック
php artisan migrate:reset

# 全データをドロップしてから up() *down()は実行していない
php artisan migrate:fresh --seed

# すべてのマイグレーションをロールバック(down()) してから再度マイグレーション(up())
# down() の内容によっては up() に影響が出てしまう
php artisan migrage:refresh
```

#### 初期データ(seeder)投入
```bash
php artisan db:seed

# seederを指定する方法
php artisan db:seed --class HogeSeeder
```


# ■ ヘルパー

100種類以上あるようです。

#### 参考URL
- [全142種類！Laravelヘルパー関数実例](https://blog.capilano-fw.com/?p=837#auth)
- [Laravelのよく使うヘルパー関数７選！](https://blog.capilano-fw.com/?p=320)


# ■ サービスコンテナ

## サービスコンテナとは

Laravelでいう「サービス」とは
- メール送信する
- 文字列を暗号化する
- ファイルを操作する

などなど。  

これらサービスが入った入れ物(コンテナ)のようなものを **サービスコンテナ** と呼ぶ。

## サービスコンテナの使い方

### サービスコンテナへ入れる

**bind** メソッドを利用する。  

```php
app()->bind('myName', function(){
    return 'あいうえお';
})
```

### サービスコンテナから取り出す

**make** メソッドを利用する。

```php
$name = app()->make('myName');
dd($name); // あいうえお
```

取り出し方法は、いくつか存在する。
```php
$name = app()->make('myName');
$name = app('myName');
$name = resolve('myName');
$name = App::make('myName');
```

### 依存関係注入（Dependency Injection / DI）

例) 依存関係のある2つのクラス(MyClassクラス、Slackクラス)
```php
namespace aaaa;
class MyClass
{
    public $slack;
    public function __construct(Slack $slack) {
        $this->slack = $slack;
    }

    public function run() {
        $this->slack->send();
    }
}
```

```php
namespace aaaa;
class Slack
{
    public function send() {
        dd('something happens');
    }
}
```

通常ならば、下記のようにインスタンス化したSlackクラスをMyClassクラスの引数に渡す。
```php
$slack = new \aaaa\Slack();
$myClass = new \aaaa\MyClass($slack);
$myClass->run();
```

bindメソッドを使う（自動解決機能）
```php
app()->bind('myclass', \aaaa\MyClass::class);

$myClass = app()->make('myclass');
$myClass->run();
```
上記はSlackクラスについて何もコードを書いてないが、サービスコンテナの自動解決機能によって正常に実行される。

bindメソッドを使う（明示的に）
```php
app()->bind('myclass', function(){
    $slack = new \aaaa\Slack();
    return new \aaaa\MyClass($slack);
});
```


### singleton

サービスコンテナへの登録で  `bind()` 以外にも `singleton()` があるが、シングルトンはインスタンスを１つのみ作成・利用する。  


### 参考URL
- [Laravel サービスコンテナの理解を深める](https://reffect.co.jp/laravel/laravel-service-container-understand)

# ■MODEL / ORM

## 保存

```php
// プロパティに１つずつ設定して保存
$contact = new Conatct();
$contact->last_name = '山田';
$contact->first_name = '太郎';
$contact->save();
```

**マスアサイメント脆弱性**対策のため、配列で保存する場合は
モデルクラスの中で **fillable** の設定が必要。
```php
// モデル
class Contact extends Model
{
    use hasFactory;

    protected $fillable = ['last_name', 'first_name'];
}

// コントローラ
// 配列で一括指定して保存する。
$contact = new Contact([
    'last_name' => '山田',
    'first_name' => '太郎',
]);
$contact->save(); //保存

// create()メソッドで一括指定し、DBへ保存する
Contact::create([
    'last_name' => '山田',
    'first_name' => '太郎',
]);
```


## with() (Eager Loading)

N+1問題の回避策として使用される。  
### join との違い

| 　 | 取得形式 | 親子関係 | リレーション定義 |
| --- | --- | --- | --- |
| left join | フラット | 1対1 | 不要 |
| with | ネスト | 1対多 | 必要 |

例) usersテーブル、user_shipping_addressesテーブルを使った顧客別届け先住所一覧の取得 (1対多の関係)

●left join での結果
同じ値である member_id と shipping_name も表示される。（というか、いつもの表）

| user_id | name | zip_code | prefecture | city | address |
| -- | -- | -- | -- | -- | -- |
| 100 | 山田太郎 | 530-0001 | 大阪府 | 大阪市 | 北区梅田 | 
| 100 | 山田太郎 | 160-0023 | 東京都 | 新宿区 | 西新宿 |
| 100 | 山田太郎 | 450-0002 | 愛知県 | 名古屋市 | 中村区名駅 |


●with の場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42530/32161141-b5e5-883b-4bb9-7ee9b497b868.png)





### やってみる
```php
<?php
// Userモデル
namespace App\Models;
// << 中略 >>

use App\Models\UserShippingAddress;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

// << 中略 >>

    /**
     * withのための設定
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function user_shipping_address()
    {
        // hasMany を指定する際は文字列でモデル名を指定する。
        // ::class を使った記法でも、出力されれる文字列は「App\Models\UserShippingAddress」となるので
        // 完全修飾名を文字列として取得できる。
        // ただし、 use 句で該当クラスを呼び出す必要がある。
        // (use句で App\Modes\*** と記述するんだから、結局記述の省略にはなってない?)
        // →今回はhasMany()のところかしみてないので、そう感じるが、他にモデルを使用する処理もありえるので
        //   書いておいて損ではない。
//        return $this->hasMany('App\Models\UserShippingAddress');
        return $this->hasMany(UserShippingAddress::class);
    }
}
```
```php
<?php
// Withコントローラ
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\User;

class WithController extends Controller
{
    public function index()
    {
        $user = User::where('id', 1)->first();

        $queue = User::with([
            'user_shipping_address' => function ($query) use ($user) {
                $query->where('user_id', $user->id);
            }
        ]);

        return User::with([
            'user_shipping_address' => function ($query) use ($user) {
                $query->where('user_id', $user->id);
            }
        ])
        ->get([
            'users.id',
            'users.name',
        ]);
    }
}
```
●with() での結果  
`->get()` の第1引数では、usersテーブルのカラムを指定できる。  
指定しない場合、usersテーブルのカラムをすべて取得する。  

`user_shipping_address` 項目が配列になっている。(withで指定したテーブルで取得したいカラムは指定できない？)
　→カラム指定はできなさそうだが、返戻データ(JSON)としてカラムを指定することができる。([APIリソース](https://readouble.com/laravel/10.x/ja/eloquent-resources.html))

```php
array (
  0 => 
  array (
    'id' => 1,
    'name' => '山田太郎',
    'user_shipping_address' => 
    array (
      0 => 
      array (
        'id' => 1,
        'user_id' => 1,
        'zip_code' => '530-0001',
        'prefecture' => '大阪府',
        'city' => '大阪市',
        'address' => '北区梅田',
        'created_at' => '2023-03-27T06:36:57.000000Z',
        'updated_at' => '2023-03-27T06:36:57.000000Z',
      ),
      1 => 
      array (
        'id' => 2,
        'user_id' => 1,
        'zip_code' => '160-0023',
        'prefecture' => '東京都',
        'city' => '新宿区',
        'address' => '西新宿',
        'created_at' => '2023-03-27T06:36:57.000000Z',
        'updated_at' => '2023-03-27T06:36:57.000000Z',
      ),
      2 => 
      array (
        'id' => 3,
        'user_id' => 1,
        'zip_code' => '450-0002',
        'prefecture' => '愛知県',
        'city' => '名古屋市',
        'address' => '中村区名駅',
        'created_at' => '2023-03-27T06:36:57.000000Z',
        'updated_at' => '2023-03-27T06:36:57.000000Z',
      ),
    ),
  ),
)
```

### with() でのリレーションの深堀り

```php
class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    public function resource(Request $request)
    {
        $userData = User::with([
            // 親モデルである 「user」をドットでつなげると、
            // usersテーブルの中身も取得する。
            'userShippingAddress.user'
        ])
        ->get([
            'users.id',
            'users.name',
        ]);
    }
```

●結果
user_shipping_address オブジェクトの中に、 user オブジェクトが含まれている。
```php
stdClass Object
(
    [data] => stdClass Object
        (
            [0] => stdClass Object
                (
                    [id] => 1
                    [name] => 山田太郎
                    [user_shipping_address] => Array
                        (
                            [0] => stdClass Object
                                (
                                    [id] => 1
                                    [user_id] => 1
                                    [zip_code] => 530-0001
                                    [prefecture] => 大阪府
                                    [city] => 大阪市
                                    [address] => 北区梅田
                                    [created_at] => 2023-03-27T06:36:57.000000Z
                                    [updated_at] => 2023-03-27T06:36:57.000000Z
                                    [user] => stdClass Object
                                        (
                                            [id] => 1
                                            [name] => 山田太郎
                                            [email] => tarou.yamada@xxxxxxxx
                                            [email_verified_at] => 2023-03-27T06:26:30.000000Z
                                            [created_at] => 2023-03-27T06:26:30.000000Z
                                            [updated_at] => 2023-03-27T06:26:30.000000Z
                                        )
                                )
```

## 論理削除関係

### 物理削除 `delete()` メソッド、`destroy()`メソッド

```php
$user = App\User::find(1);
$user->delete();

// モデルクラスを取得しなくても　destroy() メソッドで削除できる
App\User::destroy(1);
App\User::destroy([1, 2, 3]); // 複数レコード削除
```

### 論理削除

- テーブルに `deleted_at` カラムが必要。
- モデルに `SoftDeletes` クラスが必要。
- `find()` などのデータ取得時も、 deleted_at カラムがNULLのレコードは対象外となる。

```php
<?php
namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Profile extends Model
{
    use SoftDeletes;
}
```

- 取得したデータ(モデルクラス)が削除されているか確認
```php
if ($user->trashed()) {
    // 処理を書く
}
```

- 論理削除あれたデータも対象とする場合
```php
$users = App\User::withTrashed()->get();
```

- 論理削除だけを取得する場合
```php
$users = App\User::onlyTrashed()->get();
```

参考URL
[Laravelのソフトデリートについてメモ](https://qiita.com/4roro4/items/017b47b0b88835e689ec)


### トランザクション

トランザクション内で処理したい内容を `DB::transaction()` の無名関数の中に書いておけばいいのが便利ですね。

※メモなので、「$userのリレーションがよくわからんぞ」というツッコミはご容赦。

```php
namespace App\UseCase\Http\Controller\User;

use App\Models\User\User;
use DB;
use Illuminate\Support\Collection;

class StoreUserContent
{
    public function handle(User $user, array $contentData): string
    {
        $content = DB::transaction(function () use ($user, $contentData) {
            return $user->userContents()->create($value);
        });

        return $content;
    }
}
```

手動も出来ます。

```PHP
$user = User::find($id);

DB::beginTransaction();
try {
  $user->name = '変更太郎';
  $user->save();  
  DB::commit();
} catch (\Exception $e) {
  DB::rollback();
}

```


# ■view

## Blade


`/resources/views`フォルダ配下に`●●●.blade.php`というファイルを作成する。

```php
// ルートの場合は welcome.blade.php というファイルを表示させる例
// ".blade.php" は記述しないこと。
Route::get('/', function() {
    return view('welcome');
});
```

第2引数に連想配列を指定すると、viewの中に`$name`と`$faborite`という変数が生成される。

```php
return View('hello', [
    'name' => '山田',
    'favorite' => 'Laravel10',
]);
```

第1引数のビューファイル指定は、`resources/views`を起点にしている。
ビューファイルをサブディレクトリで管理したい場合は、ディレクトリの区切りをドット(.)で記述する。

```php
// resources/views/admin/profile.blade.php を使用する場合
return View('admin.progile', $data);
```

### HTMLエスケープ

`{{$title}}`のように、`{{}}`で囲むと、`htmlspecialchars($title, ENT_QUOTES)`と同様のエスケープしてくれる。

```php
// 文字列の連結
<p>{{$title . $name}}</p>

// あえてエスケープさせない場合
<p>{!! $title !!}</p>
```

### コメント

`{{-- $title --}}` のように、ハイフン２つで囲む。

### PHPディレクティブ

```php
@php
  $title = '素晴らしいタイトル';
@endphp
```
↓↓これと同じ
```php
<?php
  $title = '素晴らしいタイトル';
?>
```

### ifディレクティブ

```php
@if (count($records) === 1)
    <div>レコードが1件あります。</div>
@elseif (count($record) > 1)
    <div>レコードが複数件あります。</div>
@else
    <div>ありません。</div>
@endif
```

### 繰り返し

ループさせる `@while`、`@for`、`@foreach` があるが、Bladeには`@forelse`ディレクティブというものがある。

```php
@forelse ($users as $user)
    <p>{{$user->name}}</p>
@empty
    <p>ユーザーはいません</p>
@endforelse
```

↓↓foreach文とif文の組み合わせのようなもの

```php
@if (!empty($users))
    @foreach ($users as $user)
        <p>{{$user->name}}</p>
    @endforeach
@else
    <p>ユーザーはいません</p>
@endif
```

### ループのディレクティブ

ループを続けたり脱出する`@continue`や`@break`以外に、`$loop`変数が使用できる。

| ディレクティブ | 機能 |
| --- | --- |
| $loop->index| 現在のインデックスを参照できる。|
| $loop->iteration | 現在のイテレータの値を参照できる。（初期値１） |
| $loop->remaining | 反復の残り数 |
| $loop->count | 反復している配列の総アイテム数 |
| $loop->first | ループの最初か判定する |
| $loop->last | ループの最後か判定する |
| $loop->even | 現在のループが偶数回か判定する |
| $loop->odd | 現在のループが奇数回か判定する |
| $loop->depth | 現在のループのネストレベル |
| $loop->parent | ループがネストしている場合、親のループ変数 |



## vite
### `Vite manifest not found at` が表示される場合

- `npm run dev&`を実行する。
- または、`npm run build`を実行しておく。



# おわりに

初学者は、とにかく試してみるっきゃないですね。