どーも!marusukeです!
前回の記事までで、blog記事を一覧表示・追加・詳細表示までが完成しているので、今回は編集できるようにします!
ユーザーから見たときの表示や挙動
ユーザーから見たときの表示内容や挙動について、以下の条件を満たす「blog編集ページ」を作成します。
- blog記事の編集ページのURLは、http://localhost:8080/blog/edit/{任意のid}
- ユーザーは、1のURLを開くと、そのidのPostデータがあれば、入力フォームに入った状態で表示する。(idのPostデータがなければ、blog一覧ページに遷移する。)
- ユーザーは、2の入力フォームに含まれるデータを変更し、更新するボタンをクリックすると、更新した内容のblog詳細ページに遷移する。
画像は、完成したblog編集ページです。
上記のblog編集ページで入力内容を更新して「Update post」ボタンを押すと、
このように更新した内容が反映され、blog詳細ページに遷移します。
編集できるようにするために手を加えるファイル一覧
以下のファイルに手を加えていきます!
- WriteControllerクラスにeditActionメソッドを追加する
- WriteControllerFactoryクラスを変更する
- edit用のルートを作成する
- editページのviewを作成する
- LaminasDbSqlCommandクラス(リポジトリクラス)を編集する
では上から順に始めましょう!
WriteControllerクラスにeditActionメソッドを追加する
まずは、簡単にeditActionメソッドのロジックから説明します。
実は、addActionメソッドのロジックに「データベースからidが一致するpostデータを取得し、そのpostデータをPostFormオブジェクトに入れる」という部分を追加するのみです。
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のページに遷移させる
ちなみにここで現れたメソッドは、Laminasコントローラーの既存のプラグインのメソッドです。以下の公式ドキュメントにメソッドについて記載があります。
- paramsメソッドとfromRouteメソッド => 【laminas-mvc】Controller Pluginsの「Params Plugin」
- redirectメソッドとtoRouteメソッド => 【laminas-mvc】Controller Pluginsの「Redirect Plugin」
その他にも以下のような組み込みプラグインがありますので、お時間がある時に読んでみてください
- Laminas\Mvc\Controller\Plugin\AcceptableViewModelSelector
- Laminas\Mvc\Controller\Plugin\Forward
- Laminas\Mvc\Controller\Plugin\Layout
- Laminas\Mvc\Controller\Plugin\Url
②について
//②
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オブジェクトに入れ込む。
③について
//③
$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側に渡す
④について
//④
$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側に渡す
⑤について
//⑤
$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を渡す
ここまでで、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の差は、
- ページ上部の見出し
- formタグの actionに使用されるURL
- 送信ボタンに使用されるラベル
です。
実際にコードを見ていきましょう!
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をデータベースに保存しています。以下の流れで処理しています。
- Laminas DB コンポーネントのUpdateクラスをインスタンス化する
- Updateインスタンスのsetメソッドで、Updateインスタンスに新たなTitleとTextをセットする
- データベース内からwhere句で一致するidを探すため、Updateインスタンスのwhereメソッドに、idを設定する
- Laminas DB コンポーネントのSqlクラスをインスタンス化する(引数はデータベース接続情報の配列が含まれています)
- SqlインスタンスのprepareStatementForSqlObjectメソッドで、Updateメソッドをprepareする(データベースの情報を更新する準備をする)
- Sqlインスタンスのexecuteメソッドで、データベースの情報を更新を実行する
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データの削除機能を作成します!
コメント