Zend Framework3 超入門

# 前書き
これはZendFramework3を5%理解した人が、
全く触ったことがない人にZendFramework3を3%くらい理解させる目的で書いた記事です。

## バージョン
Zend Framework 3.1.3

# プロジェクト作成＆起動

## プロジェクト作成

```
$ cd C:\xampp\htdocs\zendRensyu （お好きなところにプロジェクトフォルダを作って移動する。）
$ composer create-project zendframework/skeleton-application .
```
「Do you want a minimal install (no optional packages)? Y/n」と、聞かれるのでnを押す。
後は不要そうなら拒否しておく。よくわからないなら許可すればおｋ

## 起動

```
$ cd C:\xampp\htdocs\zendRensyu
$ php -S 0.0.0.0:80 -t public
```
http\://localhostにアクセスするとWelcomeページが出る。
http\://localhost/application/indexでも同じ。

apacheのhtdocsフォルダの配下にプロジェクトフォルダを作成した場合は
http\://localhost/（プロジェクト名）/publicでアクセスできる。

# Controller

## ルーティング設定

編集するファイル
（プロジェクトフォルダ）\module\Application\config\module.config.php

下記の設定は
http://（ドメイン名）/（コンテキストパス）/hogeと、
HogeControllerを紐づけている。

```php
return [
    'router' => [
        'routes' => [
            ・・・,
            'hoge' => [
                'type'    => Segment::class,
                'options' => [
                    'route'    => '/hoge[/:action]',
                    'defaults' => [
                        'controller' => Controller\HogeController::class,
                        'action'     => 'index',
                    ],
                ],
            ],
        ],
    ],
    'controllers' => [
        'factories' => [
            Controller\IndexController::class => InvokableFactory::class,
            Controller\HogeController::class => InvokableFactory::class, // ← 付け足す。
        ],
    ],
    ・・・
```

## 基本

（プロジェクトフォルダ）\module\Application\src\Controller\HogeController.php

```php
<?php

namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;

class HogeController extends AbstractActionController
{
    public function indexAction()
    {
        $vm = new ViewModel();
        return $vm;
    }
}
```

（プロジェクトフォルダ）\module\Application\view\application\hoge\index.phtml

```html
<div>
	Hogehoge
</div>
```

HogeController#indexActionにリクエストが来た際に、自動的にhoge\index.phtmlを元にhtmlが生成されレスポンスされる。
HogeController#hogeActionならhoge\hoge.phtmlが紐づけられる。

## レンダリングに使うviewを明示的に指定する

（プロジェクトフォルダ）\module\Application\view\application\moga\moga.phtmlを指定する場合

```php
public function indexAction()
{
    $vm = new ViewModel();
    $vm->setTemplate('application\moga\moga');
    return $vm;
}
```

## Get送信からパラメータを受け取る

```php
public function indexAction()
{
    // パラメータを連想配列で受け取る
    $params = $this->getRequest()->getQuery();

    return new ViewModel();
}
```

## Post送信からパラメータを受け取る

```php
public function indexAction()
{
    // パラメータを連想配列で受け取る
    $params = $this->getRequest()->getPost();

    return new ViewModel();
}
```

## Viewに値を渡す

**Controller**

```php
class HogeController extends AbstractActionController
{
    public function indexAction()
    {
        // 方法1
        $vm = new ViewModel(['param1' => 'ほげほげ']);

        // 方法2
        $vm->setVariable('param2', 'ふがふが');

        // 方法3
        $vm->setVariables([
            'param3' => 'ぴよぴよ',
            'param4' => 'もがもが'
        ]);

        // もちろんオブジェクトも渡せる
        $vm->setVariable('param5', new Nagonago('なごなご'));

        return $vm;
    }
}

class Nagonago {
    public $nago = null;
    function __construct($nago) { $this->nago = $nago; }
}
```

**View**

```html
<div>
	Hogehoge<br>
    <hr>

    <?= $this->param1; ?><br>
    <?= $this->param2; ?><br>
    <?= $this->param3; ?><br>
    <?= $this->param4; ?><br>
    <?= $this->param5->nago; ?><br>
</div>
```

**レスポンス**
<img width="600" alt="zend.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649673/7951221c-3c5f-bcf8-2ff2-88ea6e7772fa.png">

## レスポンスでJSONを返す

（プロジェクトフォルダ）\module\Application\config\module.config.php

```php
return [
    ・・・,
    'view_manager' => [
        ・・・,
        // ↓ 追加
        'strategies' => [
            'ViewJsonStrategy',
        ],
    ],
];

```

**Controller**

```php
namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\View\Model\JsonModel;

class HogeController extends AbstractActionController
{
    public function indexAction()
    {
        return new JsonModel([
            'name' => 'mogamoga',
            'age' => 24,
            'messages' => [
                'hello',
                'good, morning'
            ],
            'data' => [
                'key1' => 'value1',
                'key2' => 'value2'
            ]
        ]);
    }
}
```

## フォワード

### 基本

```php
class HogeController extends AbstractActionController
{
    public function indexAction()
    {
        // FugaControllerのindexActionにフォワードさせる。
        return $this->forward()->dispatch(FugaController::class, ['action' => 'index']);
    }
}
```

### パラメータを渡す

**投げる側**

```php
class HogeController extends AbstractActionController
{
    public function indexAction()
    {
        return $this->forward()->dispatch(
            FugaController::class,
            [
                'action' => 'index',
                'human' => [
                    'name' => 'mogamoga',
                    'age' => 24
                ]
            ]
        );
    }
}
```

**受け取る側**

```php
class FugaController extends AbstractActionController
{
    // こうやって受け取る
    $human = $this->params('human');

    return new ViewModel();
}
```

## リダイレクト

### 基本

```php
class HogeController extends AbstractActionController
{
    public function indexAction()
    {
        // toRouteメソッドの第一引数には、
        // （プロジェクトフォルダ）\module\Application\config\module.config.phpの
        // 'routes'の値に設定している連想配列のキーを指定する。
        return $this->redirect()->toRoute('fuga', ['action' => 'index']);
    }
}
```

### パラメータを渡す

**投げる側**

```php
class HogeController extends AbstractActionController
{
    public function indexAction()
    {
        // Get送信扱いでリダイレクトさせる。Post送信扱いする方法は知らない。
        return $this->redirect()->toRoute(
            'fuga',
            ['action' => 'index'],
            [
                'query' => [
                    'name' => 'mogamoga',
                    'age' => 24
                ]
            ]
        );
    }
}
```

**受け取る側**

```php
class FugaController extends AbstractActionController
{
    public function indexAction()
    {
        // パラメータを連想配列で受け取る
        $params = $this->getRequest()->getQuery();

        return new ViewModel();
    }
}
```

### 外部にリダイレクトさせる

```php
class HogeController extends AbstractActionController
{
    public function indexAction()
    {
        return $this->redirect()->toUrl('https://qiita.com/');
    }
}
```

# View

（プロジェクトフォルダ）\module\Application\src\Controller\HogeController.php

```php
class HogeController extends AbstractActionController
{
    public function indexAction()
    {
        return new ViewModel([
            'hoge' => 'ほげほげ',
            'fuga' => '<script>alert("Hello, ZF3!")</script>',
            'flag' => 1,
            'mogas' => [
                'このフレームワークの',
                '勉強しているの',
                '2020年時点で',
                '俺だけ説'
            ]
        ]);
    }
}
```

（プロジェクトフォルダ）\module\Application\view\application\hoge\index.phtml

```html
<div>
    <h3>変数の表示</h3>
    <?= $this->hoge; ?>
    <br><hr>

    <h3>URL</h3>
    <!--
    urlメソッドの第一引数には、
    （プロジェクトフォルダ）\module\Application\config\module.config.phpの
    'routes'の値に設定している連想配列のキーを指定する。
    -->
    <?= $this->url('hoge', ['action' => 'hello']) ?>
    <br><hr>

    <h3>特殊文字のエスケープ</h3>
    <?= $this->escapeHtml($this->fuga); ?>
    <br><hr>

    <h3>if文</h3>
    <?php if ($this->flag === 0): ?>
        なごなご
    <?php elseif ($this->flag === 1): ?>
        ぴよぴよ
    <?php else: ?>
        もがもが
    <?php endif; ?>
    <br><hr>

    <h3>for文</h3>
    <?php foreach ($this->mogas as $moga): ?>
        <?= $moga ?><br>
    <?php endforeach; ?>
```

**レスポンス**
<img width="600" alt="zend2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649673/9ac9c995-3ee7-8922-8dde-cf5351b255ff.png">

# DBアクセス

## 前書き

ぶっちゃけ、分からん。
いろいろ方法があったり、設定が面倒だったり、おまじないが多かったりして
頭わるわるな自分には分からない。

とりあえず以下のサイトが参考になると思います。

[Zend Framework3のチュートリアル日本語化メモ-4](https://rakulack.net/archives/50)
[Zend Framework3の始め方4](https://rakulack.net/archives/532)
[zendFramework3でdbアダプタをグローバルで持ちたい](https://teratail.com/questions/147767)
[ZF2を使っていろいろなSELECT文を生成する](https://qiita.com/su_mi/items/ac2352598dd3edb68db8)
[Adapters - zend-db - Zend Framework Docs](https://docs.zendframework.com/zend-db/adapter/)
[SQL Abstraction - zend-db - Zend Framework Docs](https://docs.zendframework.com/zend-db/sql/)

## オレオレDBアクセスラッパークラス

多分これが一番楽だと思います。

以下をコピペして利用してください。

```php
<?php

namespace Application\Common;

use Zend\Db\Adapter\Adapter;
use Zend\Db\Sql\Sql;
use Zend\Db\ResultSet\ResultSet;

class DBManager
{
    private $adapter = null;
    private $connection = null;
    private $sql = null;

    function __construct() {
        // 自分の環境にあわせて変えてください
        $config = [
            'driver' => 'mysqli',
            'host' => 'localhost',
            'username' => '（ユーザー名）',
            'password' => '（パスワード）',
            'database' => '（データベース名）',
            'charset' => 'utf8',
        ];
        $this->adapter = new Adapter($config);
        $this->connection = $this->adapter->getDriver()->getConnection();
        $this->sql = new Sql($this->adapter);
    }

    public function beginTransaction()
    {
        $this->connection->beginTransaction();
    }

    public function commit()
    {
        $this->connection->commit();
    }

    public function rollback()
    {
        $this->connection->rollback();
    }

    public function query(
        $sql,
        $parametersOrQueryMode = Adapter::QUERY_MODE_EXECUTE
    ) {
        $result = $this->adapter->query($sql, $parametersOrQueryMode);
        return ($result instanceof ResultSet) ? $result->toArray() : $result;
    }

    public function select(callable $selectFunction)
    {
        $select = $this->sql->select();
        $selectFunction($select);
        $resultsItr = $this->sql->prepareStatementForSqlObject($select)->execute();

        $results = [];
        foreach ($resultsItr as $result) $results[] = $result;

        return $results;
    }

    public function insert(callable $insertFunction)
    {
        $insert = $this->sql->insert();
        $insertFunction($insert);
        $this->sql->prepareStatementForSqlObject($insert)->execute();
    }

    public function update(callable $updateFunction)
    {
        $update = $this->sql->update();
        $updateFunction($update);
        $this->sql->prepareStatementForSqlObject($update)->execute();
    }

    public function delete(callable $deleteFunction)
    {
        $delete = $this->sql->delete();
        $deleteFunction($delete);
        $this->sql->prepareStatementForSqlObject($delete)->execute();
    }
}
```

### トランザクション

以降もそうですが、コントローラ層で使用方法を説明しているのに特に意味はないです。
実際にはデータアクセス層にでも書いて利用してください。

```php
namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Application\Common\DBManager;

class IndexController extends AbstractActionController
{
    public function indexAction()
    {
        $db = new DBManager();

        // トランザクション開始
        $db->beginTransaction();

        // 略（いろんな処理とかSQLとか）

        // コミット
        $db->commit();
        // ロールバック
        //$db->rollback();

        return new ViewModel();
    }
}
```

### 生SQL

```php
namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Application\Common\DBManager;

class IndexController extends AbstractActionController
{
    public function indexAction()
    {
        $db = new DBManager();

        $db->beginTransaction();

        // delete
        $db->query("delete from characters where name = ?", ['ひで']);

        // insert
        $db->query('insert into characters(name, kana) values (?, ?)', ['野獣先輩', 'やじゅやじゅビースト']);
        $db->query('insert into characters(name, kana) values (?, ?)', ['MUR', 'みうら']);

        // update
        $db->query('update characters set kana = ? where name = ?', ['やじゅうせんぱい', '野獣先輩']);
        
        // select
        $characters = $db->query('select * from characters');
        // もちろん第二引数があってもいいですよ
        //characters = $db->query('select name, kana from characters where kana like ?', ['%や%']);

        echo '<pre>';
        echo var_dump($characters);
        echo '</pre>';

        $db->commit();

        return new ViewModel();
    }
}
```

**select結果**
<img width="600" alt="zend3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649673/342be551-a187-3e03-bbea-dba9eac06fef.png">

### クエリビルダー

```php
namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Application\Common\DBManager;

class IndexController extends AbstractActionController
{
    public function indexAction()
    {
        $db = new DBManager();

        $db->beginTransaction();

        // delete
        $db->delete(function($delete) {
            $delete->from('characters')
                ->where
                ->like('kana', '%や%');
        });

        // insert
        $db->insert(function($insert) {
            $insert->into('characters')
                ->columns(['name', 'kana'])
                ->values(['KMR', 'きむら']);
        });
        // 一回のinsertで複数行の挿入は無理っぽい
        // 参考：https://stackoverflow.com/questions/816910/how-do-i-add-more-than-one-row-with-zend-db
        $db->insert(function($insert) {
            $insert->into('characters')
                ->columns(['name', 'kana'])
                ->values(['TON', 'まずうち']);
        });
        
        // update
        $db->update(function($update) {
            $update->table('characters')
                ->set(['kana' => 'とおの'])
                ->where
                ->equalTo('name', 'TON');
        });
        
        // select
        $characters = $db->select(function($select) {
            $select->from('characters')
                ->where
                ->in('name', ['MUR', 'KMR', 'TON']);
        });
        
        echo '<pre>';
        echo var_dump($characters);
        echo '</pre>';

        $db->commit();

        return new ViewModel();
    }
}
```

**select結果**
<img width="450" alt="zend4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649673/9c66255d-02d7-76df-58cc-85d1717097bf.png">

# チラ裏

情報少なすぎて泣ける。