転職したらスマレジだった件

スマレジのエンジニアやまてのテックブログです。マジレス大歓迎です。

諸事情で Zend Framework を理解する 2022 - ⑤編集・削除機能の作成

こんにちは!

スマレジの テックファーム(SES 部門)のWebエンジニア やまて(@r_yamate) と申します。

はじめに

前回の記事(④登録機能の作成)で、商品データを Products テーブルに登録する、登録機能を作成しました。

本連載の目次

本連載では以下の順序で進めています。

目次

今回は、登録済みの商品データを編集・削除する機能を作成します。

これで、読込、登録、編集、削除のCRUD機能が揃います。

Zend Framework公式ドキュメントのチュートリアルを参考に作成しました。(ところどころアレンジしています)

本記事完了時点のソースコード

本記事完了時点のソースコードGitHub上に公開しています。

github.com

以下は変更差分です。

github.com

商品管理ページの構成

ページ 説明
商品一覧 商品の一覧を表示。商品の新規登録や編集、削除のためのリンクボタン表示。
商品登録 新規商品を登録するためのフォーム。
商品編集⭐️ 商品を編集するためのフォーム。
商品削除⭐️ 商品を削除することを確認し、削除するページ。

1. 編集機能の作成

商品データを編集する機能を作成します。

1-1. コントローラの編集( editAction() メソッドの追記)

フォームからデータを取得し、対象の商品データの行を編集し、商品一覧にリダイレクトするメソッドを追記します。

編集:module/Product/src/Controller/ProductController.php

<?php

namespace Product\Controller;

use Product\Form\ProductForm;
use Product\Model\Product;
use Product\Model\ProductTable;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;

/**
 * 商品コントローラークラス。
 */
class ProductController extends AbstractActionController
{
        // 略

    /**
     * 商品を編集する。
     * フォームからデータを取得し、対象の商品の行を編集し、商品一覧にリダイレクトする。
     *
     * @return Response|array
     * @throws Exception\DomainException
     */
    public function editAction()
    {
        // マッチしたルートからパラメータを取得(module/Product/config/module.config.php 内に作成したルートから id を取得)する。
        $id = (int) $this->params()->fromRoute('id', 0);

        // id が 0 なら、編集フォームにリダイレクトする。
        if (0 === $id) {
            return $this->redirect()->toRoute('product', ['action' => 'add']);
        }

        // 指定されたidの商品を取得する。商品が見つからない場合は例外(リダイレクトされる)。
        try {
            $product = $this->table->getProduct($id);
        } catch (\Exception $e) {
            return $this->redirect()->toRoute('product', ['action' => 'index']);
        }

        $form = new ProductForm();
        $form->bind($product); // bind()メソッドは、モデルをフォームにアタッチする。
        $form->get('submit')->setAttribute('value', '編集');

        $request = $this->getRequest();
        $viewData = ['id' => $id, 'form' => $form];

        if (!$request->isPost()) {
            return $viewData;
        }

        $form->setInputFilter($product->getInputFilter());
        $form->setData($request->getPost());

        // バリデーション失敗の場合、フォームを再表示する(バリデーション失敗の内容を、ビューレイヤーに伝える)。
        if (!$form->isValid()) {
            return $viewData;
        }

        // フォームからデータを取得し、対象の商品の行を編集する。
        $this->table->editProduct($product);

        // 商品一覧にリダイレクトする。
        return $this->redirect()->toRoute('product', ['action' => 'index']);
    }
}

1-2. モデルの編集①(モデルをフォームにアタッチ)

Product ファイルを編集します。

編集:/module/Product/src/Model/Product.php

<?php

// 略

/**
 * 商品モデルクラス。
 * zend-db の TableGateway クラスで動作させるために、 exchangeArray() メソッドを実装する。
 */
class Product implements InputFilterAwareInterface
{
        // 略

    /**
     * エンティティのプロパティを配列としてコピーして取得する。
     *
     * モデルをフォームにアタッチし、以下2つに使われる。
     *  - フォームを表示するときの各要素の初期値をモデルから抽出、
     *  - isValid()でバリデーションに成功すると、フォームのデータをモデルに戻す
     *
     * @return array エンティティのプロパティを配列にコピーしたもの
     */
    public function getArrayCopy()
    {
        return [
            'id' => $this->id,
            'item_name' => $this->itemName,
            'price' => $this->price,
            'image' => $this->image,
        ];
    }

        // 略

}

上記のPHPDocにも記述していますが、モデルをフォームにアタッチして、使用するのは「フォームを表示するときの各要素の初期値をモデルから抽出」「isValid()でバリデーションに成功すると、フォームのデータをモデルに戻す」時です。

1-3. モデルの編集②(商品データのテーブルからの取得、編集)

products テーブルからのデータ取得、テーブルへの編集処理をする ProductTable ファイルを編集します。

編集:module/Product/src/Model/ProductTable.php

<?php

namespace Product\Model;

use Zend\Db\TableGateway\TableGatewayInterface;

/**
 * 商品のデータベース・テーブルに対する操作を実行するクラス。
 */
class ProductTable
{
        // 略

    /**
     * 一行を Product オブジェクトとして取得する。
     *
     * @param string $id 商品ID
     * @return
     */
    public function getProduct(string $id)
    {
        return $this->tableGateway->select(['id' => $id])->current();
    }

    /**
     * データベースの既に存在する行を更新する。
     *
     * @param Product $product 既に存在する商品のカラム
     * @return void
     */
    public function editProduct(Product $product)
    {
        $data = [
            'item_name' => $product->itemName,
            'price' => $product->price,
            'image' => $product->image,
        ];

        $product = $this->getProduct($product->id);
        if (isset($product)) {
            $this->tableGateway->update($data, ['id' => $product->id]);
        }
    }
}

1-4. ビューの編集(編集用のフォーム画面の作成)

商品一覧画面にフォーム画面へ遷移する「編集」ボタンを設置します。「削除」ボタンも併せて設置しておきます。

編集:module/Product/view/product/product/index.phtml

<!-- 略 -->

<table class="table">
    <tr>
        <th>ID</th>
        <th>商品名</th>
        <th>商品単価</th>
        <th>&nbsp;</th>
    </tr>
    <!-- コントローラアクションから割り当てた$productsを繰り返し処理 -->
    <?php foreach ($products as $product) : ?>
        <tr>
            <!-- 各アルバムのタイトルとアーティストを表示するテーブルを作成 -->
            <!-- ビュースクリプトに渡す変数と内部で作成した変数を区別するために、 $this->{variable name} を使用してアクセスする -->
            <td><?= $this->escapeHtml($product->id) ?></td>
            <td><?= $this->escapeHtml($product->itemName) ?></td>
            <td><?= $this->escapeHtml(number_format($product->price)) ?></td>
            <!-- レコードの編集と削除を可能にするためのリンクを提供 -->
            <td>
                <!-- url() ビューヘルパーの使用 -->
                <a href="<?= $this->url('product', ['action' => 'edit', 'id' => $product->id]) ?>">編集</a>
                <a href="<?= $this->url('product', ['action' => 'delete', 'id' => $product->id]) ?>">削除</a>
            </td>
        </tr>
    <?php endforeach; ?>
</table>

続いて、商品データの編集するためのフォーム画面(ビュースクリプト)を作成します。

作成・編集:module/Product/view/product/product/edit.phtml

<?php
$title = '商品の編集';
$this->headTitle($title);
?>
<h1><?= $this->escapeHtml($title) ?></h1>
<?php
// 要素にデフォルトの CSS クラスとプレースホルダーテキストを提供する。
$itemName = $form->get('item_name');
$itemName->setAttribute('class', 'form-control');
$itemName->setAttribute('placeholder', '商品名');

$price = $form->get('price');
$price->setAttribute('class', 'form-control');
$price->setAttribute('placeholder', '商品単価');

// 送信ボタン用の CSS クラスを提供する。
$submit = $form->get('submit');
$submit->setAttribute('class', 'btn btn-primary');

$form->setAttribute('action', $this->url('product', [
    'action' => 'edit',
    'id'     => $id,
]));
$form->prepare();

echo $this->form()->openTag($form);
?>
<!-- フォームグループの div で要素をラップし、その中でラベル、要素、エラーを別々にレンダリングする。 -->
<div class="form-group">
    <?= $this->formLabel($itemName) ?>
    <?= $this->formElement($itemName) ?>
    <span class="text-danger"><?= $this->formElementErrors()->render($itemName, ['class' => 'help-block']) ?></span>
</div>

<div class="form-group">
    <?= $this->formLabel($price) ?>
    <?= $this->formElement($price) ?>
    <span class="text-danger"><?= $this->formElementErrors()->render($price, ['class' => 'help-block']) ?></span>
</div>

<?php
echo $this->formSubmit($submit);
echo $this->formHidden($form->get('id'));
echo $this->form()->closeTag();

1-5. 機能の確認(編集してみる)

下記URLにアクセスします。

http://localhost/

「日替わりランチC」を編集してみます。

http://localhost/product/edit/3

商品名、商品単価を変更して、編集ボタンを押します。

編集されました!

2. 削除機能の作成

商品データを削除する機能を作成します。

2-1. コントローラの編集( deleteAction() メソッドの追記)

編集:module/Product/src/Controller/ProductController.php

<?php

namespace Product\Controller;

use Product\Form\ProductForm;
use Product\Model\Product;
use Product\Model\ProductTable;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;

/**
 * 商品コントローラークラス。
 */
class ProductController extends AbstractActionController
{
        // 略

    /**
     * 商品を削除する。
     * ユーザーが削除をクリックしたときに確認フォームを表示し、「はい」をクリックしたら削除を実行する。
     *
     * @return Response|array
     * @throws Exception\DomainException
     */
    public function deleteAction()
    {
        // マッチしたルートからパラメータを取得(module/Product/config/module.config.php 内に作成したルートから id を取得)
        $id = (int) $this->params()->fromRoute('id', 0);
        if (!$id) {
            return $this->redirect()->toRoute('product');
        }

        $request = $this->getRequest();

        // isPost() をチェックして、確認ページを表示するか、アルバムを削除するかを決定する。
        if ($request->isPost()) {
            $del = $request->getPost('del', 'いいえ');

            if ($del === 'はい') {
                $id = (int) $request->getPost('id');
                $this->table->deleteProduct($id);
            }

            // 商品一覧へのリダイレクト
            return $this->redirect()->toRoute('product');
        }

        // リクエストが POST でない場合は、正しいデータベースレコードを取得し、id と共にビューに割り当てる。
        return [
            'id'    => $id,
            'product' => $this->table->getProduct($id),
        ];
    }
}

2-2. モデルの編集(商品データのテーブルからの削除)

products テーブルからのデータ取得、テーブルへの編集処理をする ProductTable ファイルを編集します。

編集:module/Product/src/Model/ProductTable.php

<?php

namespace Product\Model;

use Zend\Db\TableGateway\TableGatewayInterface;

/**
 * 商品のデータベース・テーブルに対する操作を実行するクラス。
 */
class ProductTable
{
        // 略

   /**
     * 渡された $id の行を完全に削除する。
     *
     * @param string $id 商品ID
     * @return void
     */
    public function deleteProduct(string $id)
    {
        $this->tableGateway->delete(['id' => (int) $id]);
    }
}

2-3. ビューの編集(削除用のフォーム画面の作成)

作成・編集:module/Product/view/product/product/delete.phtml

<?php
$title = '商品の削除';
$url   = $this->url('product', ['action' => 'delete', 'id' => $id]);

$this->headTitle($title);
?>
<h1><?= $this->escapeHtml($title) ?></h1>

<p>
    「商品名:<?= $this->escapeHtml($product->itemName) ?>」を削除してよろしいですか?
</p>

<form action="<?= $url ?>" method="post">
    <div class="form-group">
        <input type="hidden" name="id" value="<?= (int) $product->id ?>" />
        <input type="submit" class="btn btn-danger" name="del" value="はい" />
        <input type="submit" class="btn btn-success" name="del" value="いいえ" />
    </div>
</form>

2-4. 機能の確認(削除してみる)

http://localhost/

「日替わりランチB」を削除してみます。

http://localhost/product/delete/2

「はい」を押します。

※ ちなみに「いいえ」を押したら、商品一覧にリダイレクトしました。

「日替わりランチB」が削除されました!

おわりに

スペシャルランチ2000円がどんなランチか想像してお腹が空いてきたところで、今回は完了です!

次回、 ⑥ バリデーションメッセージの日本語化を投稿します。



独学エンジニアで学習された方が、スマレジにいつか入社されるといいなー。

dokugaku-engineer.com

corp.smaregi.jp