😸

基礎から学ぶ Laravel P199修正

2023/07/10に公開

基礎から学ぶ Laravel P199修正

佐野大樹『基礎から学ぶLaravel』C&R研究所 2003のP199に誤りがあります。

誤 syncメソッドは、現在書籍に紐づいている著者IDをすべて削除してから、引数に指定されている著者IDを新しく関連付け直します。
正 syncメソッドは、引数に指定されている著者IDに関連付け直します。

試しに著者を1人追加すると、SQLは以下のようになっていました。

[2023-07-10 06:42:23] local.INFO: SQL Query Log: [{"query":"select * from `author_book` where `author_book`.`book_id` = ?","bindings":[10],"time":0.25},{"query":"insert into `author_book` (`author_id`, `book_id`, `created_at`, `updated_at`) values (?, ?, ?, ?)","bindings":[2,10,"2023-07-10 06:42:23","2023-07-10 06:42:23"],"time":0.66}] 

また、著者を1人削除すると、SQLは以下のようになっていました。

[2023-07-10 06:42:39] local.INFO: SQL Query Log: [{"query":"select * from `author_book` where `author_book`.`book_id` = ?","bindings":[10],"time":0.58},{"query":"delete from `author_book` where `author_book`.`book_id` = ? and `author_book`.`author_id` in (?)","bindings":[10,2],"time":1.28}]

現在書籍に紐づいている著者IDと、引数に指定されている著者IDの差分をとって、INSERTが必要な行だけINSERTし、DELETEが必要な行だけDELETEしています。

syncメソッドのソースコードは以下のようになっています。

    /**
     * Sync the intermediate tables with a list of IDs or collection of models.
     *
     * @param  \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array  $ids
     * @param  bool  $detaching
     * @return array
     */
    public function sync($ids, $detaching = true)
    {
        $changes = [
            'attached' => [], 'detached' => [], 'updated' => [],
        ];

        // First we need to attach any of the associated models that are not currently
        // in this joining table. We'll spin through the given IDs, checking to see
        // if they exist in the array of current ones, and if not we will insert.
        $current = $this->getCurrentlyAttachedPivots()
                        ->pluck($this->relatedPivotKey)->all();

        $records = $this->formatRecordsList($this->parseIds($ids));

        // Next, we will take the differences of the currents and given IDs and detach
        // all of the entities that exist in the "current" array but are not in the
        // array of the new IDs given to the method which will complete the sync.
        if ($detaching) {
            $detach = array_diff($current, array_keys($records));

            if (count($detach) > 0) {
                $this->detach($detach);

                $changes['detached'] = $this->castKeys($detach);
            }
        }

        // Now we are finally ready to attach the new records. Note that we'll disable
        // touching until after the entire operation is complete so we don't fire a
        // ton of touch operations until we are totally done syncing the records.
        $changes = array_merge(
            $changes, $this->attachNew($records, $current, false)
        );

        // Once we have finished attaching or detaching the records, we will see if we
        // have done any attaching or detaching, and if we have we will touch these
        // relationships if they are configured to touch on any database updates.
        if (count($changes['attached']) ||
            count($changes['updated']) ||
            count($changes['detached'])) {
            $this->touchIfTouching();
        }

        return $changes;
    }

attachNew()の実装は以下の通りです。

    /**
     * Attach all of the records that aren't in the given current records.
     *
     * @param  array  $records
     * @param  array  $current
     * @param  bool  $touch
     * @return array
     */
    protected function attachNew(array $records, array $current, $touch = true)
    {
        $changes = ['attached' => [], 'updated' => []];

        foreach ($records as $id => $attributes) {
            // If the ID is not in the list of existing pivot IDs, we will insert a new pivot
            // record, otherwise, we will just update this existing record on this joining
            // table, so that the developers will easily update these records pain free.
            if (! in_array($id, $current)) {
                $this->attach($id, $attributes, $touch);

                $changes['attached'][] = $this->castKey($id);
            }

            // Now we'll try to update an existing pivot record with the attributes that were
            // given to the method. If the model is actually updated we will add it to the
            // list of updated pivot records so we return them back out to the consumer.
            elseif (count($attributes) > 0 &&
                $this->updateExistingPivot($id, $attributes, $touch)) {
                $changes['updated'][] = $this->castKey($id);
            }
        }

        return $changes;
    }

現在書籍に紐づいている著者ID($current)と、引数に指定されている著者ID($ids)の差分をとって$changesに格納しています。
※しかしどこで$changesに格納した値を使ってSQLを実行しているのか分かりませんでした…

Discussion