Edited at

私家版 Slim Framework チュートリアル (4) 〜 編集と削除、ついでにパーシャルビュー編


この記事について

PHP のマイクロフレームワークのひとつである、Slim Framework ですが、チュートリアルがいまいちイケてないので、お題を流用しつつ、初学者にももう少し分かりやすくなるよう、アレンジしてみようという試みです。

本記事は第4回で、前回はこちら。

私家版 Slim Framework チュートリアル (3) 〜 表示編 - Qiita


概要

今回はチケットの編集と削除の機能を実装していきます。

その前に、重複した HTML 要素(ヘッダー、フッター)を共通ファイル化するための Tips をご紹介します。


7. PHP-View でパーシャルビュー

すでにお気づきの方もいらっしゃると思いますが、これまで templates 以下に 3 つの View ファイルをつくりました。

その都度、 <!DOCTYPE html> から書いていて、ヘッダーとフッターが重複しています。

多くのテンプレートエンジンに他のテンプレートをインクルードする機能が提供されていますが、 PHP-View にもありますので、いまさら感もありますが、共通部分をパーシャルビュー化していきます。

まず、 templates/header.phtml, templates/footer.phtml の2つのファイルを作成してください。


header.phtml

<!DOCTYPE html>

<html lang="ja">
<head>
<meta charset="utf-8"/>
<title>チケット管理</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="container">
<h1>チケット管理</h1>


footer.phtml

</div>

</body>
</html>

で、 create.phtml, index.phtml, show.phtml それぞれのヘッダーフッター部分を以下のように書き換えます。

(以下、 create.phtml を例に取ります)

<?= $this->fetch('header.phtml') ?>

<form method="POST" action="/tickets">
<!-- ~中略~ -->
</form>
<?= $this->fetch('footer.phtml') ?>

fetch メソッドは、PHP-View のメインクラス(ていうかこれだけしかない)である PhpRenderer クラスのメソッドで、第2引数に連想配列を渡せば、パーシャルビューで使用する変数を定義できます。

例)

<?= $this->fetch('partial.phtml', ['name' => $user->name]) ?>

ヘッダー・フッター以外にも、複数のページに表示するパーツがあれば、パーシャルビューを使って一元化することを検討してください。

ここまでの変更のすべては以下から閲覧できます。

https://github.com/nunulk/Tutorial-First-Application/tree/chapter7


8. 編集用フォームの表示


8.1. View ファイルの作成

templates/tickets/edit.phtml というファイルを作成し、以下のように記述してください。

<?= $this->fetch('header.phtml') ?>

<form method="POST" action="/tickets/<?= e($ticket['id']) ?>">
<input type="hidden" name="_METHOD" value="put">
<div class="card">
<div class="card-body">
<h2 class="card-title">チケット編集</h2>
<div class="card-text">
<div class="form-group">
<label for="subject">件名</label>
<input type="text" name="subject" id="subject" class="form-control" value="<?= e($ticket['subject']) ?>">
</div>
</div>
</div>
<div class="card-body">
<button class="btn btn-primary">更新</button>
</div>
</div>
</form>
<?= $this->fetch('footer.phtml') ?>

新規作成用フォームと似ていますが、ところどころ違いますので、注意して記述してください。

違いのうち、いくつかピックアップします。


  1. form 要素の action が違う

  2. hidden input 要素が追加されている

  3. 件名フィールドに value 属性がある

更新用のルーティングは

// 更新

$app->put('/tickets/{id}', function (Request $request, Response $response, array $args) {

ですので、 action も /tickets/{id} となっていなくてはいけません( id の部分には選択したチケットのIDが入ります)。

ポイントは下記で、

  <input type="hidden" name="_METHOD" value="put">

「3. ルーティング」のところで GET/POST 以外の HTTP メソッドについて触れましたが、ブラウザからフォームデータを送るためには GET か POST でなければいけません。

そこで、擬似的に PUT や DELETE を指定するために、上記のように隠し要素に HTTP メソッド名を入れています(この場合は "put")。

この辺の仕組みはフレームワークによって異なりますが、Slim の場合は、 "_METHOD" というフォームデータを送るか、HTTP ヘッダーに "X-Http-Method-Override" というヘッダー要素を入れるかして対応します。


8.2. ルーティング関数にチケットデータを取得する処理を書く

src/routes.php を開いて、編集用フォームの表示のルーティング関数に処理を追加していきます。

// 編集用フォームの表示

$app->get('/tickets/{id}/edit', function (Request $request, Response $response, array $args) {
$sql = 'SELECT * FROM tickets WHERE id = :id';
$stmt = $this->db->prepare($sql);
$stmt->execute(['id' => $args['id']]);
$ticket = $stmt->fetch();
if (!$ticket) {
return $response->withStatus(404)->write('not found');
}
$data = ['ticket' => $ticket];
return $this->renderer->render($response, 'tasks/edit.phtml', $data);
});

1件表示用のものとほぼ一緒です。コピペしちゃってください。

違うのは下記の部分(ファイル名)だけ、ですね。

    return $this->renderer->render($response, 'tasks/edit.phtml', $data);

では、ブラウザから http://localhost/tickets/1/edit を開いてみてください。

image.png

表示されましたでしょうか?

ここまでの変更のすべては以下から閲覧できます。

https://github.com/nunulk/Tutorial-First-Application/tree/chapter8


9. 更新


9.1. ルーティング関数にチケットデータを更新する処理を書く

続いて、POSTされたデータを元に、データベースに保存されたデータを更新してみます。

更新用のルーティング関数をを以下のように書き換えます。

// 更新

$app->put('/tickets/{id}', function (Request $request, Response $response, array $args) {
$sql = 'SELECT * FROM tickets WHERE id = :id';
$stmt = $this->db->prepare($sql);
$stmt->execute(['id' => $args['id']]);
$ticket = $stmt->fetch();
if (!$ticket) {
return $response->withStatus(404)->write('not found');
}
$ticket['subject'] = $request->getParsedBodyParam('subject');
$stmt = $this->db->prepare('UPDATE tickets SET subject = :subject WHERE id = :id');
$stmt->execute($ticket);
return $response->withRedirect("/tickets");
});

1件表示と新規作成のミックス、みたいなかんじですね。

処理の流れは、


  1. URL の名前付きプレースホルダーから id を取得

  2. その id を持つチケットのレコードを取得(SELECT)

  3. subject を POST されたデータで 2 のレコードを更新(UPDATE)

  4. 一覧ページへリダイレクト

となります。


9.2. 1件表示ページに編集ページへのリンクを張る

最後に仕上げです。

show.phtml に以下の HTML 要素を追加してください。

          <tfoot>

<tr>
<td colspan="2">
<a href="/tickets/<?= e($ticket['id']) ?>/edit" class="btn btn-primary">編集</a>
</td>
</tr>
</tfoot>

ここまでの変更のすべては以下から閲覧できます。

https://github.com/nunulk/Tutorial-First-Application/tree/chapter9


10. 削除

最後の機能は削除です。


10.1. View ファイルの編集

今回は、View の編集からやります。

show.phtml に以下の要素を追加してください。

削除ボタン


<a href="/tickets/<?= e($ticket['id']) ?>/edit" class="btn btn-primary">編集</a>
<button class="btn btn-danger" data-toggle="modal" data-target="#deleteModal">削除</button>

モーダルダイアログ

  <div class="modal fade" id="deleteModal" tabindex="-1" role="dialog">

<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">チケットの削除</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>本当に削除してよろしいですか?</p>
</div>
<form method="post" action="/tickets/<?= e($ticket['id']) ?>">
<input type="hidden" name="_METHOD" value="delete">
<div class="modal-footer">
<button type="submit" class="btn btn-danger">削除</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">キャンセル</button>
</div>
</form>
</div>
</div>
</div>
<?= $this->fetch('footer.phtml') ?>

ここでは "_METHOD" は "delete' です。

<input type="hidden" name="_METHOD" value="delete">

Bootstrap のモーダルダイアログを使いますので、 jquery と bootstrap の JS ファイルを読み込むようにします。

footer.phtml に入れてしまいましょう。

</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/js/bootstrap.min.js"></script>
</body>
</html>

image.png

削除ボタンをクリックすると、モーダルダイアログが表示され、さらに削除ボタンをクリックすると、 DELETE /tickets/1 が送信されます。


10.2 ルーティング関数にチケットデータを削除する処理を書く

// 削除

$app->delete('/tickets/{id}', function (Request $request, Response $response, array $args) {
$sql = 'SELECT * FROM tickets WHERE id = :id';
$stmt = $this->db->prepare($sql);
$stmt->execute(['id' => $args['id']]);
$ticket = $stmt->fetch();
if (!$ticket) {
return $response->withStatus(404)->write('not found');
}
$stmt = $this->db->prepare('DELETE FROM tickets WHERE id = :id');
$stmt->execute(['id' => $ticket['id']]);
return $response->withRedirect("/tickets");
});

今回も、これまで書いてきたことを流用して書いていきます。

ポイントは DELETE 文ですね。

    $stmt = $this->db->prepare('DELETE FROM tickets WHERE id = :id');

$stmt->execute(['id' => $ticket['id']]);

いちおう処理の流れを書いておくと、


  1. URL の名前付きプレースホルダーから id を取得

  2. その id を持つチケットのレコードを取得(SELECT)

  3. 2 のレコードを削除(DELETE)

  4. 一覧ページへリダイレクト

となります。

では、モーダルダイアログの「削除」ボタンをクリックしてみましょう。

image.png

はい、消えました。

ここまでの変更のすべては以下から閲覧できます。

https://github.com/nunulk/Tutorial-First-Application/tree/chapter10

次回は、肥大化した routes.php をスリムにするため、Controller クラスを作成してみます。

コピペしたコードもあちこちにあるので、ついでにそれらも共通化してしまいます。

私家版 Slim Framework チュートリアル (5) 〜 Controllerクラス編 - Qiita