[CakePHP3] レコードの複製機能をつける

久々のCakePHP3記事です。

ある程度プログラムが組めてデータを登録していく段になると、レコードを複製したい欲求が出てきます。たくさんある項目のうち、1~2箇所だけを変えたレコードを作りたいとか、以前書いた記事を修正しながら書きたいとか。

というわけで、複製機能、いわゆるduplicator的な機能をつけてみました。


スポンサーリンク

1.改変するコード

複製機能をどこに持たせるか、という話です。

たとえばduplicateという単独のアクションを作るのもひとつです。が、以前addアクションとeditアクションをまとめる、という記事を書きました。

[CakePHP3] addアクションとeditアクションをまとめてしまう

同じようなフォームを使って同じような機能を持ったアクションを量産するくらいなら、ひとつにしてしまえ!が信条なので、今回このコードに複製機能を持たせたいと思います。

// formアクション

    /**
  * Form method
  *
  * @param string|null $id Color id.
  * @return \Cake\Network\Response|void Redirects on successful form, renders view otherwise.
  */
  public function form($id = null)
  {
             if (!is_null($id)){
                 //idが渡っていればエンティティをget(edit)
                 $color = $this->Colors->get($id, [
                 'contain' => []
                  ]);
             } else { //idがnullならば空エンティティを作成(add)
                 $color = $this->Colors->newEntity();
             }
             if ($this->request->is(['patch', 'post', 'put'])) {
                 $color = $this->Colors->patchEntity($color, $this->request->data);
                 if ($this->Colors->save($color)) {
                     $this->Flash->success(__('The color has been saved.'));

                     return $this->redirect(['action' => 'index']);
                 } else {
                     $this->Flash->error(__('The color could not be saved. Please, try again.'));
                 }
             }
             $this->set(compact('color'));
             $this->set('_serialize', ['color']);
  }

2.レコードの複製に必要なこと

CakePHP3ではざっくり言うと、エンティティをsaveするときにidがあるかないかで編集するか新規作成するかを決めています。

なので、複製したいレコードのエンティティを呼び出して、idを取り去ってやれば「中身はそのままで、idのない(新規と判断される)エンティティ」ができそうですね。

また、ユニーク制約がある場合、単純に複製して先に保存するということができません。複製するにあたってidと不要な情報(たとえば更新日付など)を削った上でフォームへ送り込み、編集後保存する、というフローがよさそうです。


スポンサーリンク

3.複製機能のコード

というわけで、書いてみました。

// formアクション

    /**
  * Form method
  *
  * @param string|null $id Color id.
  * @return \Cake\Network\Response|void Redirects on successful form, renders view otherwise.
  */
  public function form($id = null)
  {
             if (!is_null($id)){
                 //idが渡っていればエンティティをget(edit)
                 $color = $this->Colors->get($id, [
                 'contain' => []
                  ]);

                //クエリ文字列にduplicateがセットされていれば複製したエンティティを作成
                $query = $this->request->query;
                if (array_key_exists('duplicate', $query)) {
                    //エンティティを配列にする
                    $duplicator = $color->toArray();
                    //idを消す
                    unset($duplicator['id']);
                    /*
                     * 他に消しておきたい項目はこのタイミングでunsetする
                     */ 
                    //配列からEntityを作る
                    $tmpEntity = $this->Colors->newEntity();
                    $color = $this->Colors->patchEntity($tmpEntity, $duplicator);                
                }
                  
             } else { //idがnullならば空エンティティを作成(add)
                 $color = $this->Colors->newEntity();
             }
             if ($this->request->is(['patch', 'post', 'put'])) {
                 $color = $this->Colors->patchEntity($color, $this->request->data);
                 if ($this->Colors->save($color)) {
                     $this->Flash->success(__('The color has been saved.'));

                     return $this->redirect(['action' => 'index']);
                 } else {
                     $this->Flash->error(__('The color could not be saved. Please, try again.'));
                 }
             }
             $this->set(compact('color'));
             $this->set('_serialize', ['color']);
  }

ミソは複製元のエンティティを一旦toArray()で配列にして、編集後新しいエンティティとして作っているところです。

ぶっちゃけエンティティのままで項目削除する方法がわからなかったからなんですが、まあ配列の方が使い勝手いいですしね。

また、複製するかどうかの判断は、クエリパラメータを使うことにしました。

$query = $this->request->query;
if (array_key_exists('duplicate', $query)) {

と書きましたが、CakePHP3.4.0以降をお使いであれば、

if (!is_null($this->request->getQuery('duplicate')))

としたほうが簡単です。

呼び出すときは、クエリパラメータduplicateにtrueをセットしておく感じです。

<?= $this->Html->link('複製', ['controller' => 'Colors', 'action' => 'form', $color->id, 'duplicate' => true]) ?>

以上です!

この投稿の投稿者は おさみ です。ブックマーク用 パーマリンク

スポンサーリンク