Slim3 Framework×slim-skeleton不使用×twigでプロジェクトを作成する（5. チケット管理システムを再現➀）

# はじめに
[Slim3 Framework×slim-skeleton不使用×twigでプロジェクトを作成する
（4.PDO使用）](https://qiita.com/erik_t/items/7571f214142be628afac)の続きです。

# 前提
下記記事で構築した環境を前提とします。

- Windows10にVagrantをを入れてCentOS7をインストールしよう([1](https://qiita.com/erik_t/items/d7f7abbf7deafa4c6fe4)、[2](https://qiita.com/erik_t/items/909efb63fd389f486ec3)、[3](https://qiita.com/erik_t/items/2e3c43cfe8731621b82d)、[4](https://qiita.com/erik_t/items/8d006b599815c8fbf38b)、[5](https://qiita.com/erik_t/items/9fbe7b5630b1f62893a1)、[6](https://qiita.com/erik_t/items/11f24633f4248e4f33f0)) 
- ローカルでLAMP環境を構築しよう（[0](https://qiita.com/erik_t/items/9c7c49db061a250cbc32)、[1](https://qiita.com/erik_t/items/e36235896b327f5cb461)、[2](https://qiita.com/erik_t/items/8dc3f2a5e8e8c9376e27)、[3](https://qiita.com/erik_t/items/617503557020a4a6ba82)、[4](https://qiita.com/erik_t/items/a898d0f547bff6b3d8ff)）
- [CentOS7にComposerをインストールしよう](https://qiita.com/erik_t/items/0b3492f38f6cd8605a4c)

- 私家版 Slim Framework チュートリアル ([1](https://qiita.com/nunulk/items/4b5c15f13ade660cafbc)、[2](https://qiita.com/nunulk/items/8492548678aac697d0aa#_reference-1dc558a7a78d9b00868b)、[3](https://qiita.com/nunulk/items/33adfbba55057a7d0c5c#_reference-8903d1023c2501e00d0b)、[4](https://qiita.com/nunulk/items/fb5ef759c1337e99d878#_reference-56dbec1889beef3f7f4d)、[5](https://qiita.com/nunulk/items/92d06bada7657ce212e7#_reference-1e4198f2501e274dbdd8)、[6](https://qiita.com/nunulk/items/99c12ac8e7b631bd8ed3#_reference-11f698acaecbfe32d5ff))
[@nunulk](https://qiita.com/nunulk)さんのチュートリアルを一通り。
ここで作成したDBを使いまわします。
- Slim3 Framework×slim-skeleton不使用×twigでプロジェクトを作成する([1](https://qiita.com/erik_t/items/14d0103e343e440f6dc5)、[2](https://qiita.com/erik_t/items/9d896867234c904409de)、[3](https://qiita.com/erik_t/items/308091f436e74e2a232e)、[4](https://qiita.com/erik_t/items/7571f214142be628afac)）

# 手順
~~1. 専用のユーザーを作成~~
~~2. プロジェクトディレクトリを作成・slim3インストール~~
~~3. 各種設定~~
~~4. DocumentRootを変更＆表示確認~~
~~5. Twigを使ってみよう~~
~~6. Loggingしてみよう~~
~~7. Controllerを作成しよう~~
~~8. PDOを使用してデータベースに接続しよう~~
**9. @nunulkさんのチュートリアルで作成したチケット管理システムをtwigを使って再現しよう**

# やってみよう
今回の記事では、手順9を行います。
## 9. @nunulkさんのチュートリアルで作成したチケット管理システムをtwigを使って再現しよう
### Repositoryを実装
Repositoryでは、データベースとの通信を行うための、データアクセスロジックを実装します。
下記のようにフォルダ・ファイルを作成してください。

```
.
├── src/             
    └── Domain/
        └── Ticket/
            └── TicketRepository.php
```
`src/Domain/Ticket/TicketRepository.php`には以下のように記述します。
CRUD機能を一通り用意します。

```:TicketRepository.php
<?php

namespace App\Domain\Ticket;

use DomainException;
use PDO;

final class TicketRepository
{
    private $PDO;

    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }

    public function getTicketById(int $id): array
    {
        $sql = 'SELECT * FROM tickets WHERE id = :id';
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute(['id' => $id]);
        $row = $stmt->fetch();

        if (empty($row)) {
            throw new DomainException(sprintf('Ticket not found: %s', $id));
        }

        return $row ?: [];
    }

    public function getTickets(): array
    {
        $sql = 'SELECT * FROM tickets';
        $stmt = $this->pdo->query($sql);
        $tickets = [];
        while($row =$stmt->fetch()) {
            $tickets[] = $row;
        }
        if (empty($tickets)) {
            throw new DomainException('Tickets not found');
        }
        return $tickets ?: [];
    }

    public function insertTicket($subject): bool
    {
        $sql = 'INSERT INTO tickets (subject) values (:subject)';
        $stmt = $this->pdo->prepare($sql);
        $result = $stmt->execute(['subject' => $subject]);
        if (!$result) {
            throw new DomainException('could not save the ticket');
        }
        return $result;
    }

    public function updateTicket($ticket): bool
    {
        $bool_flg = true;

        try {
            $selectTicket = $this->getTicketById($ticket['id']);
        } catch (DomainException $e) {
            $bool_flg = false;
        }

        if ($bool_flg) {

            $sql = 'UPDATE tickets SET subject = :subject WHERE id = :id';
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute($ticket);
        }

        return $bool_flg;
    }

    public function deleteTicket(int $id): bool
    {
        $bool_flg = true;
        try {
            $ticket = $this->getTicketById($id);
        } catch (DomainException $e) {
            $bool_flg = false;
        }

        if ($bool_flg) {

            $sql = 'DELETE FROM tickets WHERE id = :id';
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute(['id' => $ticket['id']]);
        }

        return $bool_flg;
    }
}
```
### Serviceクラスを実装
Serviceクラスにはビジネスロジックを実装します。
ServiceクラスはControllerから呼び出されます。
Serviceクラスでは、Repositoryで実装されたメソッドを使用します。
下記のようにフォルダ・ファイルを作成してください。

```
.
├── src/             
    └── Domain/
        └── Ticket/
            └── TicketRepository.php
            └── TicketService.php
```
`src/Domain/Ticket/TicketService.php`には以下のように記述します。

```:TicketService.php
<?php

namespace App\Domain\Ticket;

final class TicketService
{
    private $ticketRepository;

    public function __construct(TicketRepository $ticketRepository)
    {
        $this->ticketRepository = $ticketRepository;
    }

    public function getTicketById(int $id): array
    {
        $ticketRow = $this->ticketRepository->getTicketById($id);
        return $ticketRow;
    }

    public function getTickets(): array
    {
        $ticketRows = $this->ticketRepository->getTickets();
        return $ticketRows;
    }

    public function insertTicket($subject): bool
    {
        $result = $this->ticketRepository->insertTicket($subject);
        return $result;
    }

    public function updateTicket($ticket): bool
    {
        $result = $this->ticketRepository->updateTicket($ticket);
        return $result;
    }

    public function deleteTicket(int $id): bool
    {
        $result = $this->ticketRepository->deleteTicket($id);
        return $result;
    }
}
```

### Controllerクラスを実装
Controllerクラスを実装して、まずは前回実装したチケット一覧画面を表示してみましょう。
Controllerクラスですが、ネーミングはダニエルさんに従ってActionクラスです。
なぜですか。ダニエルさん。

下記のようにフォルダ・ファイルを作成してください。

```
.
├── src/             
    └── Domain/
        └── Ticket/
            └── TicketRepository.php
            └── TicketService.php
    └── Action/
        └── Action.php
        └── TicketAction.php
```
まずは抽象クラスの`src/Action/Action.php`に以下のように記述します。

```:Action.php
<?php

namespace App\Action;

use Psr\Container\ContainerInterface;
use Slim\Views\PhpRenderer;

abstract class Action
{
    protected $renderer;
    protected $db;
    protected $twig;

    public function __construct(ContainerInterface $container)
    {
        $this->renderer = $container['renderer'];
        $this->db = $container['db'];
        $this->twig = $container['twig'];
    }
}
```
`src/Action/Action.php`を継承した`src/Action/TicketAction.php`以下のように記述します。

```:TicketAction.php
<?php

namespace App\Action;

use App\Domain\Ticket\TicketRepository;
use App\Domain\Ticket\TicketService;
use Psr\Http\Message\ResponseInterface;
use Slim\Http\Request;
use Slim\Http\Response;

final class TicketAction extends Action
{
    public function index(Request $request, Response $response)
    {
        $repository = new TicketRepository($this->db);
        $service = new TicketService($repository);
        $tickets = $service->getTickets();
        $data = ['tickets' => $tickets];
        return $this->twig->render($response, 'tickets/index.twig', $data);
    }
}
```
### routes.php修正

`config/routes.php`で、[前回](https://qiita.com/erik_t/items/7571f214142be628afac#routesphp%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%88%E3%82%92%E4%BF%AE%E6%AD%A3)修正した箇所を削除し、以下の記述を追加します。

```:config/routes.php
$app->get('/tickets', \App\Action\TicketAction::class . ':index');
```
### container.php修正

`config/container.php`で、[以前](https://qiita.com/erik_t/items/9d896867234c904409de#containerphp%E3%81%AB%E8%A8%AD%E5%AE%9A%E3%82%92%E8%BF%BD%E5%8A%A0)追加した箇所を修正します。

```:config/container.php（修正前）
$container[Twig::class] = function (Container $container) {
```
```:config/container.php（修正後）
$container['twig'] = function (Container $container) {
```
### 動作確認
ブラウザで以下のURLを開いて、チケット一覧画面が表示されればOKです。

```
http://192.168.33.90/tickets
```

## 以前作成したページを表示するための修正
上記変更を行ったので、[time.twig](https://qiita.com/erik_t/items/9d896867234c904409de#5-twig%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86)と[home-index.twig](https://qiita.com/erik_t/items/308091f436e74e2a232e#homeindexactionphp%E4%BD%9C%E6%88%90)は表示できなくなりましたので、修正します。

#### time.twig
以下のように`src/Action/TimeAction.php`を追加します。

```:src/Action/TimeAction.php
<?php

namespace App\Action;

use Slim\Http\Request;
use Slim\Http\Response;

final class TimeAction extends Action
{
    public function __invoke(Request $request, Response $response)
    {
        $viewData = [
            'now' => date('Y-m-d H:i:s')
        ];
        return $this->twig->render($response, 'time.twig', $viewData);
    }
}
```
次にconfig/routes.phpを修正します。

```:config/routes.php（修正前）
$app->get('/time', function (Request $request, Response $response) {
    $viewData = [
        'now' => date('Y-m-d H:i:s')
    ];

    return $this->get(Twig::class)->render($response, 'time.twig', $viewData);
});
```
```:config/routes.php（修正後）
$app->get('/time', \App\Action\TimeAction::class);
```
#### home-index.twig
[home-twig.twig用に設定したcontainer.phpの内容](https://qiita.com/erik_t/items/308091f436e74e2a232e#containerphp%E3%81%AB%E8%A8%AD%E5%AE%9A%E3%82%92%E8%BF%BD%E5%8A%A0-1)を削除し、
`src/Action/HomeIndexAction.php`を以下のように変更します。

```:src/Action/HomeIndexAction.php
<?php

namespace App\Action;

use Psr\Http\Message\ResponseInterface;
use Slim\Http\Request;
use Slim\Http\Response;

final class HomeIndexAction extends Action
{
    /**
     * Action.
     *
     * @param Request $request the request
     * @param Response $response the response
     *
     * @return ResponseInterface the response
     */
    public function __invoke(Request $request, Response $response): ResponseInterface
    {
        $viewData = [
            'name' => 'World',
        ];

        return $this->twig->render($response, 'Home/home-index.twig', $viewData);
    }
}
```
Action.phpを継承することでControllerクラスにTwigの宣言が不要になりましたし、
container.phpに記述する設定も不要になり、実装がシンプルになりました。

# 参考サイト
[Creating your first Slim 3 Framework Application](https://odan.github.io/2017/11/30/creating-your-first-slim-framework-application.html)
[Creating your first Slim 3 Framework Application Part 2](https://odan.github.io/2019/03/18/creating-your-first-slim-framework-application-part-2.html)
私家版 Slim Framework チュートリアル ([1](https://qiita.com/nunulk/items/4b5c15f13ade660cafbc)、[2](https://qiita.com/nunulk/items/8492548678aac697d0aa#_reference-1dc558a7a78d9b00868b)、[3](https://qiita.com/nunulk/items/33adfbba55057a7d0c5c#_reference-8903d1023c2501e00d0b)、[4](https://qiita.com/nunulk/items/fb5ef759c1337e99d878#_reference-56dbec1889beef3f7f4d)、[5](https://qiita.com/nunulk/items/92d06bada7657ce212e7#_reference-1e4198f2501e274dbdd8)、[6](https://qiita.com/nunulk/items/99c12ac8e7b631bd8ed3#_reference-11f698acaecbfe32d5ff))

# 関連ページ
## Windows10にVagrantをを入れてCentOS7をインストールしよう
[1. VagrantインストールからVagrantfileを設置まで](https://qiita.com/erik_t/items/d7f7abbf7deafa4c6fe4)
[2. 仮想マシンの操作](https://qiita.com/erik_t/items/909efb63fd389f486ec3)
[3. WinSCP、Tera Termに秘密鍵でログイン ](https://qiita.com/erik_t/items/2e3c43cfe8731621b82d)
[4. WinSCP、Tera Termにrootユーザーでパスワードログイン](https://qiita.com/erik_t/items/8d006b599815c8fbf38b)
[5. zip/unzipをインストール](https://qiita.com/erik_t/items/9fbe7b5630b1f62893a1)
[6. Vagrantにて仮想環境を配布](https://qiita.com/erik_t/items/11f24633f4248e4f33f0)

## ローカルでLAMP環境を構築しよう
[0. 事前準備](https://qiita.com/erik_t/items/9c7c49db061a250cbc32)
[1. Apacheをインストール](https://qiita.com/erik_t/items/e36235896b327f5cb461)
[2. MySQLをインストール](https://qiita.com/erik_t/items/8dc3f2a5e8e8c9376e27)
[3. PHPをインストール](https://qiita.com/erik_t/items/617503557020a4a6ba82)
[4. ファイアウォールとか停止する](https://qiita.com/erik_t/items/a898d0f547bff6b3d8ff)

## Composerをインストール
[CentOS7にComposerをインストールしよう](https://qiita.com/erik_t/items/0b3492f38f6cd8605a4c)

## PHP Slim3フレームワークのサンプルアプリを作ろう
[2-1. First Application Walkthrough Getting Set Upまで](https://qiita.com/erik_t/items/9e9f6c93d2403ac90242)

## Apache
 [DocumentRootを変更しよう](https://qiita.com/erik_t/items/d8f4746d2dc2992f329e)

## Slim3 Framework×slim-skeleton不使用×twigでプロジェクトを作成する
[1. プロジェクト作成～各種設定](https://qiita.com/erik_t/items/14d0103e343e440f6dc5)
[2. DocumentRoot変更～Twigを使用](https://qiita.com/erik_t/items/9d896867234c904409de)
[3. LoggingとController](https://qiita.com/erik_t/items/308091f436e74e2a232e)
[4.PDO使用](https://qiita.com/erik_t/items/7571f214142be628afac)
5. チケット管理システムを再現➀
[6. チケット管理システムを再現➁](https://qiita.com/erik_t/items/28f2cabc460312ea5111)