【Laminas】blog記事を編集できるようにする

Laminas

どーも!marusukeです!

前回の記事までで、blog記事を一覧表示・追加・詳細表示までが完成しているので、今回は編集できるようにします!

ユーザーから見たときの表示や挙動

ユーザーから見たときの表示内容や挙動について、以下の条件を満たす「blog編集ページ」を作成します。

  1. blog記事の編集ページのURLは、http://localhost:8080/blog/edit/{任意のid}
  2. ユーザーは、1のURLを開くと、そのidのPostデータがあれば、入力フォームに入った状態で表示する。(idのPostデータがなければ、blog一覧ページに遷移する。)
  3. ユーザーは、2の入力フォームに含まれるデータを変更し、更新するボタンをクリックすると、更新した内容のblog詳細ページに遷移する。

画像は、完成したblog編集ページです。

blog編集ページ

上記のblog編集ページで入力内容を更新して「Update post」ボタンを押すと、

内容が更新された後のblog詳細ページ

このように更新した内容が反映され、blog詳細ページに遷移します。

編集できるようにするために手を加えるファイル一覧

以下のファイルに手を加えていきます!

  1. WriteControllerクラスにeditActionメソッドを追加する
  2. WriteControllerFactoryクラスを変更する
  3. edit用のルートを作成する
  4. editページのviewを作成する
  5. LaminasDbSqlCommandクラス(リポジトリクラス)を編集する

では上から順に始めましょう!

WriteControllerクラスにeditActionメソッドを追加する

まずは、簡単にeditActionメソッドのロジックから説明します。

実は、addActionメソッドのロジックに「データベースからidが一致するpostデータを取得し、そのpostデータをPostFormオブジェクトに入れる」という部分を追加するのみです。

editActionメソッドのロジック

以下のようなロジックになります。番号がついている部分は、ロジック画像の下部にあるコード部分に対応しています。

editActionメソッドのロジック

WriteControllerに追加するコード

コードは以下のようになります。追加するプロパティと①〜⑤を簡単に解説していきます。

module/Blog/src/Controller/WriteController.php

<?php
// In module/Blog/src/Controller/WriteController.php:

namespace Blog\Controller;

use Blog\Form\PostForm;
use Blog\Model\Post;
use Blog\Model\PostCommandInterface;
use Blog\Model\PostRepositoryInterface;
use InvalidArgumentException;
use Laminas\Mvc\Controller\AbstractActionController;
use Laminas\View\Model\ViewModel;

class WriteController extends AbstractActionController
{
    
    private $command;

    private $form;

    // 追加するプロパティ
    private $repository;

    public function __construct(
        PostCommandInterface $command,
        PostForm $form,
        PostRepositoryInterface $repository
    ) {
        $this->command = $command;
        $this->form = $form;
        $this->repository = $repository;
    }

    public function addAction()
    {
        // addActionメソッドは割愛します。
    }

    public function editAction()
    {
        //
        $id = $this->params()->fromRoute('id');
        if (! $id) {
            return $this->redirect()->toRoute('blog');
        }

        //
        try {
            $post = $this->repository->findPost($id);
        } catch (InvalidArgumentException $ex) {
            return $this->redirect()->toRoute('blog');
        }
        $this->form->bind($post);

        //
        $viewModel = new ViewModel(['form' => $this->form]);
        $request = $this->getRequest();
        if (! $request->isPost()) {
            return $viewModel;
        }

        //
        $this->form->setData($request->getPost());
        if (! $this->form->isValid()) {
            return $viewModel;
        }

        //
        $post = $this->command->updatePost($post);
        return $this->redirect()->toRoute(
            'blog/detail',
            ['id' => $post->getId()]
        );
    } 

追加するプロパティについて

// 追加するプロパティ
private $repository;

public function __construct(
   PostCommandInterface $command,
   PostForm $form,
   PostRepositoryInterface $repository
) {
   $this->command = $command;
   $this->form = $form;
   $this->repository = $repository;
}

プロパティ$repositoryを追加し、LaminasDbSqlRepositoryをWriteControllerに注入します。

理由は、editActionメソッドで、データベースから保存されているpostデータを取り出す必要があるためです。LaminasDbSqlRepositoryクラスのupdatePostメソッドを使用します。(後ほど実装します。)

①について

//
$id = $this->params()->fromRoute('id');
if (! $id) {
    return $this->redirect()->toRoute('blog');
}

この部分では、簡単に説明すると、「編集するpostデータのidをURLのパラメータから取得する」ということを実施しています。

処理の流れを文章で書くと以下の通りです。

  • paramsメソッドとfromRouteメソッドで、リクエストのパラメータ{id}部分の値を取得し、変数$idに格納する
  • $idが空なら、redirectメソッドとtoRouteメソッドで、http://localhost:8080/blogのページに遷移させる
editActionメソッドのロジックの①部分

ちなみにここで現れたメソッドは、Laminasコントローラーの既存のプラグインのメソッドです。以下の公式ドキュメントにメソッドについて記載があります。

  • paramsメソッドとfromRouteメソッド => 【laminas-mvc】Controller Pluginsの「Params Plugin
  • redirectメソッドとtoRouteメソッド => 【laminas-mvc】Controller Pluginsの「Redirect Plugin

その他にも以下のような組み込みプラグインがありますので、お時間がある時に読んでみてください

②について

//
try {
    $post = $this->repository->findPost($id);
} catch (InvalidArgumentException $ex) {
    return $this->redirect()->toRoute('blog');
}
$this->form->bind($post);

この部分では、「データベースからidと一致するpostデータを取得して、PostFormオブジェクトに入れ込む」ということを実施しています。(ここでいうPostFormオブジェクトは、いわゆる「入力フォーム」のことです。)

処理の流れは以下の通りです。

  • try catch構文で、$idに一致するPostデータをデータベースから検索し、あればPostデータのオブジェクトを$postに格納する。なければ、blog一覧ページに遷移する。
  • bindメソッドで、postオブジェクトをpostFormオブジェクトに入れ込む。
editActionメソッドのロジックの②部分

③について

//
$viewModel = new ViewModel(['form' => $this->form]);
$request = $this->getRequest();
if (! $request->isPost()) {
    return $viewModel;
}

上記のコード部分では、簡単に説明すると「blog編集ページを表示する」という部分を担っています。

具体的な処理の流れは、

  • 変数$viewModelにPostFormオブジェクトを引数として渡したインスタンス化したviewModelを格納する(この時のPostFormオブジェクトは前段②で、postデータをバインドしたものを格納しています)
  • getRequestメソッドで、リクエストの内容を取得し、変数$requestに格納する
  • if文で、$requestがPOST通信でない場合は、$viewModel(PostFormオブジェクトを含むviewModelオブジェクト)をview側に渡す
editActionメソッドのロジック③部分

④について

//
$this->form->setData($request->getPost());
if (! $this->form->isValid()) {
    return $viewModel;
}

上記の部分を簡単に説明すると、「編集されたpostデータを取得して、入力内容に不備がないかチェックする」ということを実施しています。

  • getPostメソッドでリクエストのPOST通信で送信された、編集されたpostデータを取得し、PostFormオブジェクトのsetDataメソッドで、その取得したPostデータをPostFormオブジェクに入れる
  • if文のisValidメソッドで、Postデータに不備がないかを確認し、不備がある場合は、$viewModel(エラーメッセージを含むviewModelオブジェクト)をview側に渡す
editActionメソッドのロジック④部分

⑤について

//
$post = $this->command->updatePost($post);
return $this->redirect()->toRoute(
    'blog/detail',
    ['id' => $post->getId()]
);

上記の部分を簡単に説明すると、「編集されたpostデータをデータベースに保存し、その編集後のblog詳細ページへ遷移する」ということを実施しています。

  • updatePostメソッドで、データベースに$post(postデータ(Postオブジェクトです))を保存する(※この時の$postに格納されているpostデータは、編集後のデータとなります。)
  • redirectメソッドとtoRouteメソッドで、ルートをblog詳細ページのルートに設定し、idを渡す
editActionメソッドのロジック⑤部分

ここまでで、WriteControllerクラスにeditActionメソッドを追加しました!

お疲れ様でした!

少し休憩をしてから、次に進みましょう!

WriteControllerFactoryクラスを変更する

WriteControllerクラスにeditActionメソッドで、LaminasDbSqlrepositoryクラスのupdatePostメソッドを使用するので、PostRepositoryInterfaceクラスをWriteControllerクラスに注入します。(LaminasDbSqlrepositoryクラスは、PostRepositoryInterfaceクラスをimplementsしています。)

以下のようにWriteControllerFactoryクラスを変更します。赤アンダーライン部分が追加部分です。

module/Blog/src/Factory/WriteControllerFactory.php

<?php
namespace Blog\Factory;

use Blog\Controller\WriteController;
use Blog\Form\PostForm;
use Blog\Model\PostCommandInterface;
use Blog\Model\PostRepositoryInterface;
use Interop\Container\ContainerInterface;
use Laminas\ServiceManager\Factory\FactoryInterface;

class WriteControllerFactory implements FactoryInterface
{
   
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $formManager = $container->get('FormElementManager');
        return new WriteController(
            $container->get(PostCommandInterface::class),
            $formManager->get(PostForm::class),
            $container->get(PostRepositoryInterface::class)
        );
    }
}

これでWriteControllerFactoryクラスを変更ができました!

次は、editAction用のルートを作成します!

edit用のルートを作成する

ユーザーがブラウザからblog記事の編集ページのURLであるhttp://localhost:8080/blog/edit/{任意のid}をリクエストした時(編集ページを開いた時)の処理するコントローラとメソッドを設定していきます!

module/Blog/config/module.config.php

use Laminas\Router\Http\Segment;

return [
    'service_manager' => [ /* ... */ ],
    'controllers'     => [ /* ... */ ],
    'router'          => [
        'routes' => [
            'blog' => [
                /* 割愛します */

                'child_routes' => [
                    'detail' => [ /* 割愛します */ ]
                    'add' => [ /* 割愛します */ ]

                    // 以下を追記します
                    'edit' => [
                        'type' => Segment::class,
                        'options' => [
                            'route'    => '/edit/:id',
                            'defaults' => [
                                'controller' => Controller\WriteController::class,
                                'action'     => 'edit',
                            ],
                            'constraints' => [
                                'id' => '[1-9]\d*',
                            ],
                        ],
                    ], // edit部分ここまで
                ],
            ],
        ],
    ],
    'view_manager'    => [ /* ... */ ],
];

追加したedit部分について簡単に解説すると、

  • URLの構造は、https://localhost:8080/blog/edit/{id}
  • {id}部分に数値が入る
  • 上記のURLにブラウザからリクエストされると、WriteControllerのeditActionメソッドが実行される

ルートの書き方については、以下の記事をご覧ください。

これで、edit用のルートができました!

次は、editページのviewを作成します!

editページのviewを作成する

作成するviewスクリプトは以下の通りです。

  • form.phtml(共通部分)
  • add.phtml(blog記事追加ページ)
  • edit.phtml(blog記事編集ページ)

viewは、addページ(blog記事追加ページ)とeditページ(blog記事編集ページ)のUIは同じなので、UIの元となるviewスクリプトform.phtmlと、addページとeditページで差がある部分を分けるようにadd.phtmlとedit.phtmlを作成します。

少しわかりにくいと思うので、実際にコードを見ていきましょう!

form.phtmlについて

この部分は入力フォームのUIを表示するという役割を担っています。

module/Blog/view/blog/write/form.phtml

<?php
$form = $this->form;
$fieldset = $form->get('post');

$title = $fieldset->get('title');
$title->setAttribute('class', 'form-control');
$title->setAttribute('placeholder', 'Post title');

$text = $fieldset->get('text');
$text->setAttribute('class', 'form-control');
$text->setAttribute('placeholder', 'Post content');

$submit = $form->get('submit');
$submit->setValue($this->submitLabel);
$submit->setAttribute('class', 'btn btn-primary');

$form->prepare();

echo $this->form()->openTag($form);
?>

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

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

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

viewのヘルパーメソッドについては、以前のadd.phtml作成の時に説明しましたので、その記事をご覧ください

add.phtmlについて

add.phtmlとedit.phtmlの差は、

  1. ページ上部の見出し
  2. formタグの actionに使用されるURL
  3. 送信ボタンに使用されるラベル

です。

実際にコードを見ていきましょう!

module/Blog/view/blog/write/add.phtml

// ①
<h1>Add a blog post</h1>

<?php
// ②
$form = $this->form;

// ③
$form->setAttribute('action', $this->url());

// ④
echo $this->partial('blog/write/form', [
    'form' => $form,
    'submitLabel' => 'Insert new post',
]);

簡単に①〜④を説明すると、

  • ①は、見出し部分が「Add a blog post」となります。
  • ②は、WriteControllerのaddActionメソッドから受け取ったPostFormオブジェクトを変数$formに格納しています。
  • ③は、PostFormオブジェクトのformのaction属性に「/blog/add」を指定しています。
  • ④は、view ヘルパーメソッドのpartialメソッドは、第1引数でこのページのフォーマットとなるform.phtmlを指定し、第2引数で、②のPostFormオブジェクトとsubmitbuttonのlabel「Insert new post」をform.phtmlに渡しています。

partialメソッドについて公式ドキュメントはこちらです!

Components laminas-view Partial

edit.phtmlについて

早速、コードを見ていきましょう!

module/Blog/view/blog/write/edit.phtml

// ①
<h1>Edit blog post</h1>

<?php
// ②
$form = $this->form;

// ③
$form->setAttribute('action', $this->url('blog/edit', [], true));

// ④
echo $this->partial('blog/write/form', [
    'form' => $form,
    'submitLabel' => 'Update post',
]);

簡単に①〜④を説明すると、

  • ①は、見出し部分が「Edit blog post」となります。
  • ②は、WriteControllerのeditActionメソッドから受け取ったPostFormオブジェクトを変数$formに格納しています。
  • ③は、PostFormオブジェクトのformのaction属性を設定しています。第1引数で「/blog/edit」を指定しています。第2引数で空の配列を渡しています。第3引数でtrueにすることで、現在のURLのidをそのまま使用するという設定にしています。(詳しくはこちらを確認ください。Components laminas-view Url : Reusing Matched Parameters
  • ④は、view ヘルパーメソッドのpartialメソッドは、第1引数でこのページのフォーマットとなるform.phtmlを指定し、第2引数で、②のPostFormオブジェクトとsubmitbuttonのlabel「Update post」をform.phtmlに渡しています。

ここまででeditページのviewとaddページのviewが完成しました!

次は、LaminasDbSqlCommandクラス(リポジトリクラス)を編集していきます!

LaminasDbSqlCommandクラス(リポジトリクラス)を編集する

いよいよblog記事を編集する機能の最後の変更箇所です!

このままだと、WriteControllerのeditActionメソッド内のupdatePostメソッドが機能していないので、以下のコードを実装します。

module/Blog/src/Model/LaminasDbSqlCommand.php

// updatePostメソッド部分のみ記載

// 
public function updatePost(Post $post)
{
   // 
    if (! $post->getId()) {
        throw new RuntimeException('Cannot update post; missing identifier');
    }
   
    // 
    $update = new Update('posts');
    $update->set([
            'title' => $post->getTitle(),
            'text' => $post->getText(),
    ]);
    $update->where(['id = ?' => $post->getId()]);

    $sql = new Sql($this->db);
    $statement = $sql->prepareStatementForSqlObject($update);
    $result = $statement->execute();

   // 
    if (! $result instanceof ResultInterface) {
        throw new RuntimeException(
            'Database error occurred during blog post update operation'
        );
    }

    return $post;
}

簡単に①〜④まで説明します。

updatePostメソッド ①部分について

public function updatePost(Post $post)
{
...
}

updatePostメソッドの引数$postですが、「内容が編集されたPostオブジェクト」が入っています。

したがって、Postオブジェクトの型チェックをしています。

updatePostメソッド ②部分について

if (! $post->getId()) {
        throw new RuntimeException('Cannot update post; missing identifier');
}

ここでは、postオブジェクトのidがあるかどうかの確認をしています。idがない場合はエラーメッセージ「Cannot update post; missing identifier」を表示します。

updatePostメソッド ③部分について

// 1
$update = new Update('posts'); 
// 2      
$update->set([
        'title' => $post->getTitle(),
        'text' => $post->getText(),
]);  
// 3
$update->where(['id = ?' => $post->getId()]);
// 4
$sql = new Sql($this->db);
// 5
$statement = $sql->prepareStatementForSqlObject($update);
// 6
$result = $statement->execute();

ここでは、Updateインスタンスを活用して、編集ページで入力されたTitleとTextをデータベースに保存しています。以下の流れで処理しています。

  1. Laminas DB コンポーネントのUpdateクラスをインスタンス化する
  2. Updateインスタンスのsetメソッドで、Updateインスタンスに新たなTitleとTextをセットする
  3. データベース内からwhere句で一致するidを探すため、Updateインスタンスのwhereメソッドに、idを設定する
  4. Laminas DB コンポーネントのSqlクラスをインスタンス化する(引数はデータベース接続情報の配列が含まれています)
  5. SqlインスタンスのprepareStatementForSqlObjectメソッドで、Updateメソッドをprepareする(データベースの情報を更新する準備をする)
  6. Sqlインスタンスのexecuteメソッドで、データベースの情報を更新を実行する

Laminas DB コンポーネントのUpdateクラスについての公式ドキュメントはこちら

Components laminas-db:Update

updatePostメソッド ④部分について

if (! $result instanceof ResultInterface) {
        throw new RuntimeException(
            'Database error occurred during blog post update operation'
        );
}

return $post;

Laminas DB コンポーネントのSqlクラスのexecuteメソッドの戻り値は、ResultInterface型なので、型チェックをしています。ResultInterface型でない場合は、エラーメッセージ「Database error occurred during blog post update operation」を表示します。

Laminas DB コンポーネントの戻り値の型については、公式ドキュメントのこちらに記載があります。

Components laminas-db:Result Sets

型チェックを通過した場合、$post(編集後のPostオブジェクト)がこのクラスの戻り値となります。

これで、LaminasDbSqlCommandクラス(リポジトリクラス)が完成しました!

お疲れ様でした!

ここまで読んでいただき、ありがとうございました!

次は、Postデータの削除機能を作成します!

コメント

タイトルとURLをコピーしました