実装を読んで理解するLaravelのsaveメソッドの思想と仕組み

>100 Views

September 16, 25

スライド概要

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

ダウンロード

関連スライド

各ページのテキスト
1.

実装を読んで理解する Laravelのsaveメソッドの 思想と仕組み 1

2.

目次 1. saveとinsertの使い分け 2. 設定値からみるsave()の思想 3. save()の実装コードを読む 4. Eventsでsave()に対応する 2

3.

概要 Laravelのデータ保存メソッドの代表格 save() $book = new \App\Models\Book(); $book->title = '新しい本'; $book->save(); 1. 似た役割を持つ insert() 2. save()関連の2つの設定値 ( Global Scopes , Events ) 実装コードを読みながら、この2つに着目し、 その思想と使いどころを理解していきましょう。 3

4.

プロフィール 発表者 ma@me 所属 直近の登壇 PHP実行環境の歴史 PHP-FPMからFrankenPHPの誕生へ https://www.docswell.com/s/1313108/ZP2QQ1-2025-03-21-123847 どこまで違う?!PHP実行環境パフォーマンス対決 - mod_php vs php-fpm vs Swoole vs FrankenPHP https://www.docswell.com/s/1313108/ZGGREJ-2025-07-17-000927 4

5.

宣伝 2025年11月開催の「PHPカンファレンス福岡2025」に登壇します 同時間帯の他のプレゼンが豪華すぎて、既に不安です 5

6.

save とinsertの使い分け 6

7.

insert の役割 新しいレコードをデータベースに追加するために使用されます。 例えば、次のように使用します。 DB::table('books')->insert([ ['title' => '本1', 'author' => '著者1'], ['title' => '本2', 'author' => '著者2'] ]); 7

8.

Doc コメントから読み解く、insertの役割 は纏めて大量のデータをインサートするための バッチ対応向けのメソッド コメント抜粋 > Insert new record「s」 > すべての insert はバッチ挿入として扱われるため、~ insert() /* Insert new records into the database. */ public function insert(array $values) { // Since every insert gets treated like a batch insert, we will make sure the // bindings are structured in a way that is convenient when building these // inserts statements by verifying these elements are actually an array. (省略) 参照: framework/src/Illuminate/Database/Query/Builder.php#L2300 付近 8

9.

ここまでの簡易まとめ は大量データの一括挿入用途に作られている save() は単一モデルの保存用途に作られている insert() 9

10.

設定値からみるsave()の思想 10

11.
[beta]
該当の設定値
1. Global Scopes
2. Events

設定コードサンプル
#[ScopedBy([BookScope::class])]
class Book extends Model
{
protected static function boot() {
parent::boot();
static::saving(function ($book) {
$book->book_id = uuid();
});
・・・

11

12.

Global Scopes の役割 ・Modelを介して実行するSQLに、 ScopedBy で指定されたクラスに記載された スコープ条件を付与してくれる 使いどころ ・ログインユーザーIDなど、動的な値を加えたい場合に便利 ↓ ここがグローバルスコープの指定箇所 #[ScopedBy([BookScope::class])] class Book extends Model 12

13.

Global Scopes の動作例 一見bookテーブルの全件取得にみえる Book::all(); しかしグローバルスコープ内でログインユーザーに絞る whereを加えていた場合、生成・実行SQLは次のようになる。 生成されたSQL select * from `books` where `user_id` = 123 13

14.

Global Scopes 実装コード に期待した動作 $book = new \App\Models\Book(); $book->title = '新しい本'; $book->save(); 新規作成insertの期待動作 insert文なので global scope(where) を外してくれる INSERT INTO book (title) VALUES ('タイトル'); 更新updateの期待動作 update文なので、where句に global scope(where) を付与してくれる UPDATE book SET title = 'タイトル' WHERE book_id = 100 AND user_id = 123; 14

15.

Global Scopes 実装コード の「現実」 $book = new \App\Models\Book(); $book->title = '新しい本'; $book->save(); 新規作成insertの実動作 問答無用で外れる INSERT INTO book (title) VALUES ('タイトル'); 更新updateの実動作 update文はprimary_keyしか効かない UPDATE book SET title = 'タイトル' WHERE book_id = 100; 15

16.

save() の実装コードを読む 16

17.

save() の実装コードを読む 何故このような動作になるのか、実装コードを読んで理解していきましょう saveの実装コード抜粋 public function save(array $options = []) { $this->mergeAttributesFromCachedCasts(); $query = $this->newModelQuery(); if ($this->fireModelEvent('saving') === false) { return false; } (・・・省略・・・) 17

18.
[beta]
原因のメソッド、newModelQuery()
public function save(array $options = [])
{
↓ このメソッド
$query = $this->newModelQuery();
/* Get a new query builder that doesn't have any global scopes or eager loading. */
public function newModelQuery() {
return $this->newEloquentBuilder(
$this->newBaseQueryBuilder()
)->setModel($this);}

newModelQueryのDocコメント(意訳):
「グローバルスコープや eager loading を 一切含まない
新しいクエリビルダを返します」。

18

19.

ここまでの簡易纏め part2 ・新規か更新かに関わらず、 newModelQuery() を呼び出し、 グローバルスコープを一切含まないクエリビルダを取得している ・Laravelはprimary_keyでの設定と更新を推奨している 参考issue https://github.com/laravel/framework/issues/11989 19

20.

(update)時に 条件を加えたい場合はどうするか save グローバルスコープがsaveの時に付与されないのは分かった とはいえ更新時のwhere句にログインユーザーなど 動的な条件を加えたい場合はどうすればいいのか? public function save(array $options = []) { $this->mergeAttributesFromCachedCasts(); $query = $this->newModelQuery(); ↓↓↓ ここに注目 ↓↓↓ if ($this->fireModelEvent('saving') === false) { (・・・省略・・・) 20

21.

Events でsave()に対応する 21

22.
[beta]
dispatchesEvents

の概要

save()に対応させるには次の2つの方法があります
1. dispatchesEvents
2. Observers
時間の都合で dispatchesEvents のみ紹介します
dispatchesEvents, Observersの指定例
#[ObservedBy([BookObserver::class])]
class Book extends Model
{
protected $dispatchesEvents = [
'saving' => \App\Events\BookSaving::class,
];
}
22

23.

dispatchesEvents で対応させる dispatchesEventsにはCURD操作における CUDの部分のイベントが用意されています。 1. saving 2. saved 3. updating 4. updated 5. deleting 6. deleated etc... 23

24.
[beta]
dispatchesEvents

実装コード

の設定例

class Book extends Model
{
protected $dispatchesEvents = [
'saving' => \App\Events\BookSaving::class,
];
}

保存処理

$book = new \App\Models\Book();
$book->title = '新しい本';
$book->save();

24

25.

dispatchesEvents 保存処理 の動作 $book = new \App\Models\Book(); $book->title = '新しい本'; $book->save(); 新規作成insert insert文なのでwhere句はつかない INSERT INTO book (title) VALUES ('タイトル'); 更新updateの期待動作 update文なので、where句に saving で与えた条件を付与してくれる UPDATE book SET title = 'タイトル' WHERE book_id = 100 AND user_id = 123; 25

26.
[beta]
save()

の実装コードにも書いてある
系のメソッド内で
が呼び出されて実行されている

perform~
fireModelEvent('~')

public function save(array $options = [])
{
$query = $this->newModelQuery();
if ($this->fireModelEvent('saving') === false) {
return false;
}
(・・・省略・・・)
$this->performUpdate($query) : true;
(・・・省略・・・)
$saved = $this->performInsert($query);

26

27.

まとめ メソッドの使い分け insert()は大量データの一括挿入向け save()は単一モデルの保存向け Modelを介してのSQL生成・発行時に何かしら条件を加えたい場合 CURDのうち、CUDは dispatchesEvents を利用する CURDのうち、Rは Global Scope を利用する 27

28.

まとめ(続き) ドキュメントだけでなく、実装コードを読むことで、理解が深まりました。 迷ったときには実装コードも読んで見ようと思います 今はdeepwikiもあるので、実装コードを読むハードルも下がっています https://deepwiki.com/ ご清聴ありがとうございました 28