Zend Framework3のチュートリアル日本語化メモ-4

スポンサーリンク

また、Zend Framework3のtutorials内にある、Getting Started with Zend Frameworkをプレイ(英語を適当に翻訳)したのでメモを載せます。

 

その1の注意を了承したうえで読み進めてください。
このパートは長い気がするぞ…

Database and models

 

The database

モデルを作りましょう。
覚えておいてほしいことは、モデルとは、アプリケーションの中心となる目的を処理する部分を指します(いわゆる、ビジネスルール)。今回では、データベースを処理する部分を指します。データベースの操作には、Zend\\Db\\TableGateway\\TableGatewayを利用します。
私たちは、SqliteをPHPのPDO driverを用いて使用します。
こんなテキストファイルを作成しましょう。
このチュートリアルでは、sqliteを使用していますが、私はMySQLを使用しました。適宜sql文を置き換えて実行してください。

CREATE TABLE album(id INTEGER PRIMARY KEY AUTOINCREMENT, artist varchar(100) NOT NULL, title varchar(100) NOT NULL);
INSERT INTO album(artist,title) VALUES('The Military Wives','In My Dreams');
INSERT INTO album(artist,title) VALUES('Adele','21');
INSERT INTO album(artist,title) VALUES('Bruce Springsteen','Wrecking Ball(Deluxe)');
INSERT INTO album(artist,title) VALUES('Lana Del Rey','Born To Die');
INSERT INTO album(artist,title) VALUES('Gotye','making Mirrors');

これでデータベースを作成しましょう。

sqlite data/zftutorial.db < data/schema.sql

Using PHP to create the databaseは割☆愛

これで、データベースには簡単なデータが入りました。

The model files

Zend Frameworkではzend-modelコンポーネントを提供していません。なぜなら、それはあなたのビジネスロジックであり、どのように動かすかなどはあなたに依存しているからです。あなたの要求に適した多くのコンポーネントが存在しています。一つのアプローチとしては、あなたのアプリケーションにのそれぞれのエンティティ(実体、データ)に代表されるモデルクラスを持ち、そしてエンティティを保存、読み込みを行うmapper objectsを使用することです。そのほかには、Object-Relational-Mapping技術を使用することです。
このチュートリアルでは、Zend\Db\TableGateway\TableGatewayを使用するAlbumTableクラスを作成していきます、そしてそれぞれのアルバムはAlbumオブジェクトとして表現されることになります。これは、データベースのテーブル内のデータのインターフェースをすることを許す、Table Data Gatewayデザインパターンの実装を指します。しかし、注意すべき点として、Table Data Gatewayパターンはより大きなシステムにおいてはリミットをうけることがあります。また、コントローラーのアクションメソッド内にデータベースへの接続コードを置く、という誘惑が存在します。絶対にそんなことはしないように!
actionメソッド内で、sql文を直接書くな、ということですかねきっと

module/Album/src/Model/Album.phpファイルを作成し、下記のコードを入力しましょう。

<php
namespace Album\Model;
class Album{
    public $id;
    public $artist;
    public $title;

    public function exchangeArray(array $data){
        $this->id     = !empty($data['id']) ? $data['id'] : null;
        $this->artist = !empty($data['artist']) ? $data['artist'] : null;
        $this->title  = !empty($data['title']) ? $data['title'] : null;
    }
}

私たちのAlbum実体オブジェクトというのはphpのクラスです。TableGatewayクラスを使用するためには、このexchangeArray()関数を実装する必要があります。:この関数は、供給された配列から我々の実体のプロパティにデータをコピーする役割があります。
私たちは、挿入された値が正しいものなのかを確かめる入力フィルターを追加することでしょう。

次にmodule/Album/src/Model/AlbumTable.phpファイルを作成しましょう。以下の文を書きます。

namespace Album\Model;
use RuntimeException;
use Zend\Db\TableGateway\TableGatewayInterface;

class AlbumTable{
    private $tableGateway;

    public function __construct(TableGatewayInterface $tableGateway){
        $this->tableGateway = $tableGateway;
    }

    public function fetchAll(){
        return $this->tableGateway->select();
    }

    public function getAlbum($id){
        $id = (int) $id;
        $rowset = $this->tableGateway->select(['id' => $id]);
        $row = $rowset->current();
        if (! $row) {
            throw new RuntimeException(sprintf(
                'Could not find row with identifier %d',
                $id
            ));
        }
        return $row;
    }

    public function saveAlbum(Album $album){
        $data = [
            'artist' => $album->artist,
            'title'  => $album->title,
        ];

        $id = (int) $album->id;

        if ($id === 0) {
            $this->tableGateway->insert($data);
            return;
        }

        if (! $this->getAlbum($id)) {
            throw new RuntimeException(sprintf(
                'Cannot update album with identifier %d; does not exist',
                $id
            ));
        }

        $this->tableGateway->update($data, ['id' => $id]);
    }

    public function deleteAlbum($id){
        $this->tableGateway->delete(['id' => (int) $id]);
    }
}

たくさんのものがあります。まず、TableGatewayInterfaceにヒントを受けて、コンストラクターを通し、protectedな変数$tableGatewayをTableGatewayインスタンスに設定します。(TableGatewayInterfaceは、私たちが、テスト中の疑似インスタンスを含め、代わりの実装を簡単に供給することを許可します)私たちは私たちのアルバムのために、データベース上で操作をするためにこれを利用します。

そして、私たちのアプリケーションがテーブルゲートウェイとインターフェースを取って使用する、いくつかのヘルパー関数を作成しています。fetchAll()関数はすべてのアルバム行をResultSetとして検索します。getAlbum()は、データベースから1行分を、アルバムオブジェクトとして検索します。saveAlbum()はデータベースに新しい行を作成する、もしくは既に存在する1行をアップデートすることのどちらかを実行します。そして、deleteAlbum()はその行を完全に消去します。まぁ、わかりやすいですよね。

データベースに格納されているデータはエンティティと呼ばれます。(実体)
ここでは、1行ぶんのエンティティをクラス(というかオブジェクト)として扱うということを意味しています。
Albumクラスがそれにあたります。
AlbumTable.phpでは、データベースの操作を記述しており、操作は、Albumクラス単位で行います。
これは私の考えですが、
データベースの基本操作は
検索、新規作成、編集、消去の4つです。
(select,insert,update,delete)
この4つの操作を複雑に組み合わせることによって、現在では様々なサービスが運用されています。
このあたりの自分の考えもいつかまとめようかな

 

Using ServiceManager to configure the table gateway and inject into the AlbumTable

いつでも同じAlbumTableのインスタンスを使うために、それをどのように作成するかを定めたServiceManagerを使用します。(この次の行ちょっと読めなかったです。要するにgetserviceConfigが自動的に呼び出される。と言っているみたい)

 

ServiceManagerを設定するために、ServiceManagerがそれを必要とするときに、私たちはインスタンス化されたクラスの名前もしくはオブジェクトを初期化しているファクトリー(closure,callback,or class name of a factory class)を供給します。
まずはAlbumTableを作成するファクトリーを供給するgetServiceConfigの実装から始めましょう。
module/Album/src/Module.phpファイルに以下を追加します。

namespace Album;

// Add these import statements:
use Zend\Db\Adapter\AdapterInterface;
use Zend\Db\ResultSet\ResultSet;
use Zend\Db\TableGateway\TableGateway;
use Zend\ModuleManager\Feature\ConfigProviderInterface;

class Module implements ConfigProviderInterface{
    // getConfig() method is here

    // Add this method:
    public function getServiceConfig(){
        return [
            'factories' => [
                Model\AlbumTable::class => function($container) {
                    $tableGateway = $container->get(Model\AlbumTableGateway::class);
                    return new Model\AlbumTable($tableGateway);
                },
                Model\AlbumTableGateway::class => function ($container) {
                    $dbAdapter = $container->get(AdapterInterface::class);
                    $resultSetPrototype = new ResultSet();
                    $resultSetPrototype->setArrayObjectPrototype(new Model\Album());
                    return new TableGateway('album', $dbAdapter, null, $resultSetPrototype);
                },
            ],
        ];
    }
}

この関数はServiceManagerに通る前に、ModuleManagerによってすべてマージされたfactoriesの配列を返します。Album\Model\AlbumTableのファクトリーは、そのコンストラクタを通るためのTableGatewayを表している、Album\Model\AlbumTableGatewayというサービスを作成するため、ServiceManagerを使用します。
また、私たちはServiceManagerに、AlbumTableGatewayサービスはZend\Db\Adapter\AdapterInterfaceの実装(それもまたServiceManagerからなのだが。)にフェッチすることによって作成され、TableGatewayオブジェクトを作成するために使用される、ということを伝えます。そのTableGatewayはそれが新たな結果の行を作成するときはいつでも、Albumオブジェクトを使用するということを伝えられています。
TableGatewayクラスたちは結果の集合や実体の作成のために、プロトタイプのパターンを使用します。これは、必要となったとき、そのインスタンスの作成の代わりに、システムは以前インスタンス化されたオブジェクトを複製する、ということを意味します。

このあたりピンとこないですね…わかり次第追記します。気分が乗れば

Factories

割愛

 

Zend\Db\Adapter\AdapterInterfaceサービスはzend-dbコンポーネントによって登録されています。あなたは早期に、config/modules.config.phpファイルにこんなエントリーが含まれていることに気づくでしょう

return [
    'Zend\Form',
    'Zend\Db',
    'Zend\Router',
    'Zend\Validator',
    /* ... */
],

zend-servicemanagerの設定を供給しているすべてのZend Frameworkのコンポーネントは、モジュール自身によって露出されています。最初のインストール中の、コンポーネントを登録する場所に関するプロンプトは、上述のエントリーがあなたのために作成されていることを確かめるために現れます。
最後の結果は、私たちはすでにZend\Db\Adapter\AdapterInterfaceサービスのためのファクトリーを持っていることに頼っているということです。さてそこで私たちは、それが私たちのためのアダプターを作成できるように設定を供給してやる必要があります。

Zend FrameworkのModuleManagerはそれぞれのモジュールのmodule.config.phpファイルから設定を統合し、config/autoload/(さいしょに*.global.phpファイル、そして*.local.phpファイル)に統合します。
データベースの設定情報をglobal.phpに追加しましょう。そしてそれはあなたのバージョン管理システムにコミットするべきです。あなたは、そうしたいのであれば、穴のデータベースのcredentials(資格情報、ログイン情報)を保存するために、local.php(VCSの外側)を使用することができます。config/autoload/global.php(Albumモジュール内ではなく、プロジェクトのルートディレクトリ内)を変更しましょう

return [
    'db' => [
        'driver' => 'Pdo',
        'dsn'    => sprintf('sqlite:%s/data/zftutorial.db', realpath(getcwd())),
    ],
];
/*私は上記の設定でできなかったので
  'db' => [
        'driver' => 'Pdo',
        'dsn'    => 'mysql:dbname=zftutorial;host=localhost;charset=utf8',
        'username' => '',//設定したユーザー名
        'password' => ''//設定したパスワード
    ],
としました
ユーザーとそのパスワードは、データベース側で作成し、DBを操れる許可を与えてやる必要があります。
*/

 

もしあなたが、credentials(資格情報)を要求されるデータベースに設定しているなら、あなたのconfig/autoload/global.phpファイル内に一般設定を置きます。(この文、もしかして英語の仮定法?もし仮定法なら、実際はそんなことないけど、と言っています。仮定法でないならそのままの意味です)
そしてDSNとcredentialsを含めた現在の環境のための設定を、config/autoload/local.phpに書き込みましょう。
これらはアプリが動くときに、あなたがすべての定義をしているかを確かめながらマージされます、しかしそれはあなたにバージョン管理の外に、credentials(資格情報)の入ったファイルを置いておくことを許可していることになります。

Back to the controller

モデルを持った今、それを使えるようになるためにコントローラに挿入しましょう。
最初にコンストラクタを追加します。
module/Album/src/Controller/AlbumController.php
に下記の一部を追加しましょう。

namespace Album\Controller;

// Add the following import:
use Album\Model\AlbumTable;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;

class AlbumController extends AbstractActionController{
    // Add this property:
    private $table;

    // Add this constructor:
    public function __construct(AlbumTable $table){
        $this->table = $table;
    }

    /* ... */
}

コントローラはAlbumTableに依存しました。なので、コントローラのためのファクトリーを作成する必要があります。モデルの時と似ていて、Moduleクラスに作成します。
下記のコメント部分を追加してください。

namespace Album;

use Zend\Db\Adapter\Adapter;
use Zend\Db\ResultSet\ResultSet;
use Zend\Db\TableGateway\TableGateway;
use Zend\ModuleManager\Feature\ConfigProviderInterface;

class Module implements ConfigProviderInterface
{
    // getConfig() and getServiceConfig methods are here

    // Add this method:
    public function getControllerConfig()
    {
        return [
            'factories' => [
                Controller\AlbumController::class => function($container) {
                    return new Controller\AlbumController(
                        $container->get(Model\AlbumTable::class)
                    );
                },
            ],
        ];
    }
}

自分自身のファクトリーをここで定義したので、定義を取り除くためにmodule.config.phpを変更できます。module/Album/config/module.config.phpを開いて下記の部分を取り除きましょう。

<?php
namespace Album;

// Remove this:
use Zend\ServiceManager\Factory\InvokableFactory;

return [
    // And remove the entire "controllers" section here:
    'controllers' => [
        'factories' => [
            Controller\AlbumController::class => InvokableFactory::class,
        ],
    ],

    /* ... */
];

これでモデルにインタラクトする必要があるときはいつでも、コントローラ内の$table変数でアクセスできる。

Listing albums

アルバムをリスト表示するために、モデルから検索してビューに渡す必要があります。
それをするために、indexAction()を編集しましょう。
AlbumController::indexAction()を編集しましょう。

// module/Album/src/Controller/AlbumController.php:
// ...
    public function indexAction()
    {
        return new ViewModel([
            'albums' => $this->table->fetchAll(),
        ]);
    }
// ...

Zend Frameworkで、ビューに変数を設定するために、私たちはビューモデルのインスタンスを返します。ここで、コンストラクタの最初のパラメータは、表現したいデータを含む配列です。ビュースクリプトに自動的にパスされます。
ViewModel オブジェクトはまた私たちに使用されるビュースクリプトを変更することを許可しますが、基本的にmodule name/controller name / actionname.を使用します
では、module/Album/view/album/album/index.phtmlビュースクリプトを埋めていきましょう。

<?php
// module/Album/view/album/album/index.phtml:

$title = 'My albums';
$this->headTitle($title);
?>
<h1><?= $this->escapeHtml($title) ?></h1>
<p>
    <a href="<?= $this->url('album', ['action' => 'add']) ?>">Add new album</a>
</p>

<table class="table">
<tr>
    <th>Title</th>
    <th>Artist</th>
    <th>&nbsp;</th>
</tr>
<?php foreach ($albums as $album) : ?>
    <tr>
        <td><?= $this->escapeHtml($album->title) ?></td>
        <td><?= $this->escapeHtml($album->artist) ?></td>
        <td>
            <a href="<?= $this->url('album', ['action' => 'edit', 'id' => $album->id]) ?>">Edit</a>
            <a href="<?= $this->url('album', ['action' => 'delete', 'id' => $album->id]) ?>">Delete</a>
        </td>
    </tr>
<?php endforeach; ?>
</table>

最初にすることはタイトルを設定することで、ブラウザのタイトルバーに表示されるheadTitle()ビューヘルパーを用いて<head>セクションにせっていします。
そして、add a new albumというリンクを作成します。
url()ビューヘルパーはzend-mvcとzend-viewで提供され、望んでいるリンクを作成するために使用されます。
url()の第一引数には、私たちがurlの構造のために使いたいルート名です。
第二引数にはルートのプレースホルダー内に書き込む変数の配列を入れます。
今回は、albumルートで、2つのプレースホルダー、actionとidをセットします。

私たちはコントローラのアクションから割り当てられた$album変数を反復処理します。
zend-view は自動的に、それらの変数がビュースクリプトの範囲内に抽出されることを保証します。あなたは、ビュースクリプトに提供されている変数と、ビュー内で作成された変数との違いを見分けるために、$this->(variable name)を使うことでしょう。

そしてそれぞれのアルバムのタイトルとアーティスト、その行を編集、削除することを許可してくれるリンクを提供するテーブルを作成します。
ここから先はちょっと省略。foreachの使い方の説明ですが、見たらわかりますね

Escaping

私たちは常にescapeHtml()ビューヘルパーを、XSS脆弱性から守るために使用します。
XSSという脆弱性が存在します。
例えば、ユーザーが故意にjavascriptの文を保存したら、それが表示されたとき、大変なことが起こります。それを防ぐため、ユーザーが入力したものを表示するときは、時と場合によるとは思いますが、基本常にescapeHtml()を使用することをお勧めします。

 

http://zf-tutorial.localhost/albumにアクセスしてみましょう。myalbumsが表示されるはずです。
公式サイトの画像に近いものが表示されるはずです。

 

おわったー!
長かったこのパート!
でもまだきちんと理解しきれていないので復習が必要だなぁ
次も気分が良かったら上げます。

コメント

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