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

スポンサーリンク

引き続き、Zend Framework3のtutorials内にある、Getting Started with Zend Frameworkをプレイ(英語を適当に翻訳)したのでメモを載せます。
おそらくこのパートでほぼ終わると思われます。

その1
その2
その3
その4
の続きになります。
必ずその1の注意事項を読んで、了承したのちに使用してください。
前回のパートは10000文字を超えていて、個人的に脅威でした。
あと、その4あたりからですが、私に疲れが出ています。ご了承ください。

Forms and actions

Adding new albums

新しいアルバムを追加する機能のコードを書きましょう。
2つの小さな部品があります

  • 詳細を提供するためにフォームを表示する
  • フォームの送信を行い、データベースに保存する

zend-formを使用します。zend-formはそれらのバリデーションと同様に様々なフォーム入力を管理します。
Zend\Form\Formを拡張した、Album\Form\AlbumFormクラスを新しく作成しましょう。
module/Album/src/Form/AlbumForm.phpを作成し、以下を記述します。

namespace Album\Form;

use Zend\Form\Form;

class AlbumForm extends Form
{
    public function __construct($name = null)
    {
        // We will ignore the name provided to the constructor
        parent::__construct('album');

        $this->add([
            'name' => 'id',
            'type' => 'hidden',
        ]);
        $this->add([
            'name' => 'title',
            'type' => 'text',
            'options' => [
                'label' => 'Title',
            ],
        ]);
        $this->add([
            'name' => 'artist',
            'type' => 'text',
            'options' => [
                'label' => 'Artist',
            ],
        ]);
        $this->add([
            'name' => 'submit',
            'type' => 'submit',
            'attributes' => [
                'value' => 'Go',
                'id'    => 'submitbutton',
            ],
        ]);
    }
}

AlbumFormの構造内で、いくつかのことをしています。
まず、親のコンストラクターを呼ぶので、フォームの名前を設定しています。
で、4つのフォーム要素を作ります。それは、id,title,artist,submitボタンです。
それぞれのアイテムで、表示されるラベルを含めた、様々な属性やオプションを設定しています。

Form method

HTMLフォームはPOSTとGETを用いて送信されます。
zend-formのデフォルトはPOSTですので、もしGETにしたいなら、コンストラクターで以下のように設定してください。

$this->setAttribute('method', 'GET');

 

 

このフォームのためのバリデーションを作成する必要があります。
zend-inputfilterが、入力バリデーションのための一般的な機能を提供しています。
また、zend-inputfilterは、InputFileterAwareInterfaceというインターフェースを提供しており、それは与えられたフォームにインプットのフィルターを結びつけるために使用します。では、module/Album/src/Model/Album.phpのAlbumクラスにその能力を追加しましょう。

// module/Album/src/Model/Album.php:
namespace Album\Model;

// Add the following import statements:
use DomainException;
use Zend\Filter\StringTrim;
use Zend\Filter\StripTags;
use Zend\Filter\ToInt;
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
use Zend\Validator\StringLength;

class Album implements InputFilterAwareInterface
{
    public $id;
    public $artist;
    public $title;

    // Add this property:
    private $inputFilter;

    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;
    }

    /* Add the following methods: */

    public function setInputFilter(InputFilterInterface $inputFilter)
    {
        throw new DomainException(sprintf(
            '%s does not allow injection of an alternate input filter',
            __CLASS__
        ));
    }

    public function getInputFilter()
    {
        if ($this->inputFilter) {
            return $this->inputFilter;
        }

        $inputFilter = new InputFilter();

        $inputFilter->add([
            'name' => 'id',
            'required' => true,
            'filters' => [
                ['name' => ToInt::class],
            ],
        ]);

        $inputFilter->add([
            'name' => 'artist',
            'required' => true,
            'filters' => [
                ['name' => StripTags::class],
                ['name' => StringTrim::class],
            ],
            'validators' => [
                [
                    'name' => StringLength::class,
                    'options' => [
                        'encoding' => 'UTF-8',
                        'min' => 1,
                        'max' => 100,
                    ],
                ],
            ],
        ]);

        $inputFilter->add([
            'name' => 'title',
            'required' => true,
            'filters' => [
                ['name' => StripTags::class],
                ['name' => StringTrim::class],
            ],
            'validators' => [
                [
                    'name' => StringLength::class,
                    'options' => [
                        'encoding' => 'UTF-8',
                        'min' => 1,
                        'max' => 100,
                    ],
                ],
            ],
        ]);

        $this->inputFilter = $inputFilter;
        return $this->inputFilter;
    }
}

InputFileterAwareInterfaceは2個の関数を定義しています。
setInputFilterとgetInputFilterです。今回はgetInputFilter()を実装するだけでいいので、setInputFileter()では例外を返すようにしました。

getInputFilterでは、InputFilterを初期化し、私たちが必要な入力を追加しています。
それぞれの入力にフィルターもしくはバリデートを追加しています。idフィールドには整数のみがほしいので、intフィルターを付けています。text要素には、StripTagsとStringTrimを追加しています。これは求めていないHTMLと不必要な空白を除去しています。また、StringLengthバリデータを適用しており、データベースに保存できない長さの文字列を入力させないようにしています。

さて、これで表示するためのフォームを取得し、それを提出するところまで処理する必要が出てきました。これはAlbumController::addAction()で達成できます。
module/Album/src/Controller/AlbumController.phpの一部を編集しましょう。

// module/Album/src/Controller/AlbumController.php:

// Add the following import statements at the top of the file:
use Album\Form\AlbumForm;
use Album\Model\Album;

class AlbumController extends AbstractActionController
{
    /* ... */

    /* Update the following method to read as follows: */
    public function addAction()
    {
        $form = new AlbumForm();
        $form->get('submit')->setValue('Add');

        $request = $this->getRequest();

        if (! $request->isPost()) {
            return ['form' => $form];
        }

        $album = new Album();
        $form->setInputFilter($album->getInputFilter());
        $form->setData($request->getPost());

        if (! $form->isValid()) {
            return ['form' => $form];
        }

        $album->exchangeArray($form->getData());
        $this->table->saveAlbum($album);
        return $this->redirect()->toRoute('album');
    }

    /* ... */
}

AlbumとAlbumFormクラスをインポート文で追加した後、addAction()を実装しています。
もう少し細かくaddAction()コードを見てみましょう。

$form = new AlbumForm();
$form->get('submit')->setValue('Add');

ここではAlbumFormを初期化し、submitボタンのラベルをAddにしています。
アルバムを編集したり、違うラベルを使いたい時に、このAlbumFormを再利用できるように、ここでsubmitのラベル名を変更するようにしています。

$request = $this->getRequest();

if (! $request->isPost()) {
    return ['form' => $form];
}

もし、POSTリクエストでなければ、フォームは何にも送信されず、フォームを表示することを必要としています。zend-mvcでは、もし望むのであれば、ビューモデルの代わりに、データの配列を返すことができます。もしそうしたならば、その配列は新しいビューモデルを作成するのに使われます。

$album = new Album();
$form->setInputFilter($album->getInputFilter());
$form->setData($request->getPost());

この部分では、フォームの送信をしています。
Albumインスタンスを作成して、フォーム上のインプットフィルターを通しています。
その後、リクエストのインスタンスから、formへとデータを送信しています。

if (! $form->isValid()) {
    return ['form' => $form];
}

もし、フォームのバリデーションが失敗したら、フォーム画面を再表示する用にしています。この時点で、フォームには、どのフィールドが、なぜバリデーションに失敗したかを保有しており、その情報はビューレイヤーに受け渡されます。

$album->exchangeArray($form->getData());
$this->table->saveAlbum($album);

もし、フォームがバリデートを通過したら、フォームからデータを取得することができ、saveAlbum()を用いてモデルでデータを保存しています。

return $this->redirect()->toRoute('album');

データを保存したのち、Redirectコントローラープラグインを用いてアルバム一覧にリダイレクトします。

さて、add.phtml内でフォームを表示する必要があります。
ビュースクリプトを書きましょう。
module/Album/view/album/album/add.phtmlを作成し、以下のように書きましょう。

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

$title = 'Add new album';
$this->headTitle($title);
?>
<h1><?= $this->escapeHtml($title) ?></h1>
<?php
$form->setAttribute('action', $this->url('album', ['action' => 'add']));
$form->prepare();

echo $this->form()->openTag($form);
echo $this->formHidden($form->get('id'));
echo $this->formRow($form->get('title'));
echo $this->formRow($form->get('artist'));
echo $this->formSubmit($form->get('submit'));
echo $this->form()->closeTag();

まずタイトルを表示し、次にフォームを描画しています。zend-formではビューを少し簡単に書くためのいくつかのビューヘルパーを提供しています。
form()ビューヘルパーはフォームを開閉するための、openTag()とcloseTag()関数を持っています。そしてラベル付きのそれぞれの要素のために、ラベル、入力部分その他バリデーションエラーのメッセージを表示するためのformRow()という関数を作成することができます。バリデートのルールを持たなかったり、独立している二つの要素には、formHidden()とformSubmit()関数を使用しています。

あるいは、formCollectionビューヘルパーを使用することによって、フォーム描画の手続きを簡単にすることができます。例えば、フォーム描画をしているecho文をいかに変更します。

//openTag closeTagは必要みたいです。
//echo $this->form()->openTag($form);
echo $this->formCollection($form);
//echo $this->form()->closeTag();

この文は、適切なラベル、要素、エラービューヘルパーを呼び出しながら、フォーム構造を反復処理します。しかしformCollection($form)をopen、closeのformタグで囲まなければなりません。この助けは、フォームのデフォルトのHTML描画が許容できる状況において、あなたのビュースクリプトの複雑さを減らしてくれます。

これであなたは、アルバムレコードを追加するために、作成したアプリのホームページ上にあるAdd new albumリンクを使用することができるようになります。
押してみましょう。こんな感じになります。
本家サイトの画像のようになります。

うーん。グレートじゃない!。理由はブートストラップ、つまりはスケルトンで使用されているCSS foundationなのだが、がフォームの表示に特化していないからだ!ビュースクリプトに

  • 要素周りを強調する
  • ラベル、要素、エラーメッセージを分けて描画
  • 要素に属性を追加する。

add.phtmlを以下のようにアップデートしましょう。

<?php
$title = 'Add new album';
$this->headTitle($title);
?>
<h1><?= $this->escapeHtml($title) ?></h1>
<?php
// This provides a default CSS class and placeholder text for the title element:
$album = $form->get('title');
$album->setAttribute('class', 'form-control');
$album->setAttribute('placeholder', 'Album title');

// This provides a default CSS class and placeholder text for the artist element:
$artist = $form->get('artist');
$artist->setAttribute('class', 'form-control');
$artist->setAttribute('placeholder', 'Artist');

// This provides CSS classes for the submit button:
$submit = $form->get('submit');
$submit->setAttribute('class', 'btn btn-primary');

$form->setAttribute('action', $this->url('album', ['action' => 'add']));
$form->prepare();

echo $this->form()->openTag($form);
?>
<?php // Wrap the elements in divs marked as form groups, and render the
      // label, element, and errors separately within ?>
<div class="form-group">
    <?= $this->formLabel($album) ?>
    <?= $this->formElement($album) ?>
    <?= $this->formElementErrors()->render($album, ['class' => 'help-block']) ?>
</div>

<div class="form-group">
    <?= $this->formLabel($artist) ?>
    <?= $this->formElement($artist) ?>
    <?= $this->formElementErrors()->render($artist, ['class' => 'help-block']) ?>
</div>

<?php
echo $this->formSubmit($submit);
echo $this->formHidden($form->get('id'));
echo $this->form()->closeTag();

この結果はよりよくなります。
公式画像の通りです。

上述は、デフォルトのフォームの特徴の使いやすさと、フォーム描画時にある程度のカスタマイズが可能であるということを意味している。あなたのサイトに合わせて必要な協調を生成することができるはずです。

Editing an album

アルバムの編集はほとんど同じで、そのコードはとても似ています。
今回、私たちはAlbumControllerのeditAction()を使います。

// module/Album/src/Controller/AlbumController.php:
// ...

    public function editAction()
    {
        $id = (int) $this->params()->fromRoute('id', 0);

        if (0 === $id) {
            return $this->redirect()->toRoute('album', ['action' => 'add']);
        }

        // Retrieve the album with the specified id. Doing so raises
        // an exception if the album is not found, which should result
        // in redirecting to the landing page.
        try {
            $album = $this->table->getAlbum($id);
        } catch (\Exception $e) {
            return $this->redirect()->toRoute('album', ['action' => 'index']);
        }

        $form = new AlbumForm();
        $form->bind($album);
        $form->get('submit')->setAttribute('value', 'Edit');

        $request = $this->getRequest();
        $viewData = ['id' => $id, 'form' => $form];

        if (! $request->isPost()) {
            return $viewData;
        }

        $form->setInputFilter($album->getInputFilter());
        $form->setData($request->getPost());

        if (! $form->isValid()) {
            return $viewData;
        }

        $this->table->saveAlbum($album);

        // Redirect to album list
        return $this->redirect()->toRoute('album', ['action' => 'index']);
    }

このコードは親しみやすいはずです。アルバムの追加との違いを見ていきましょう。
最初、私たちはマッチしたルート内にあるidを探し、編集されるアルバムデータをロードするために使用します。

$id = (int) $this->params()->fromRoute('id', 0);

if (0 === $id) {
    return $this->redirect()->toRoute('album', ['action' => 'add']);
}

// Retrieve the album with the specified id. Doing so raises
// an exception if the album is not found, which should result
// in redirecting to the landing page.
try {
    $album = $this->table->getAlbum($id);
} catch (\Exception $e) {
    return $this->redirect()->toRoute('album', ['action' => 'index']);
}

paramsはマッチされたルートからパラメータを検索する便利な方法を提供してくれるコントローラのプラグインです。アルバムモジュールのmodule.config.php内で作成した経路からidを検索するために使用します。もしidが0なら、addアクションにリダイレクトし、そうでないなら、データベースからアルバムのエンティティを取得して処理を続けます。

 

特定のidを持つアルバムデータがちゃんと発見されうることを確認しなければなりません。もし見つけられなかったとき、データ接続メソッドは例外を出力します。それをcatchし、ユーザーをリストページに飛ばしましょう。

$form = new AlbumForm();
$form->bind($album);
$form->get('submit')->setAttribute('value', 'Edit');

フォームのbind()関数はモデルをフォームに貼り付けます。これは2つの方法で使用されます。

  • フォームを表示するとき、それぞれの要素の初期値はモデルから抽出されます。
  • isValid()関数でのバリデーションに成功したのち、フォームから受け取ったデータはモデル内に格納されます。

これらの操作はhydratorオブジェクトを使うことによって達成できます。
多くのhydratorがありますが、デフォルトのものは、モデル内の二つの関数、getArrayCopu()とexchangeArray()、を見つけることを期待しているZend\Hydrator\ArraySerializableです。
AlbumエンティティですでにexchangeArray()を記入しているので、getArrayCopy()を書く必要があります。

module/Album/src/Model/Album.phpファイルを編集しましょう。getArrayCopy()メソッドのみを追加してください。

// module/Album/src/Model/Album.php:
// ...

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

    // Add the following method:
    public function getArrayCopy()
    {
        return [
            'id'     => $this->id,
            'artist' => $this->artist,
            'title'  => $this->title,
        ];
    }

// ...

hydratorのbind()を使った結果として、私たちは、hydratorが既に実行しているので、フォームのデータを$albumに取り込む必要がありません。なので私たちは、その変更をデータベースに保存するためにマッパーのsaveAlbum()関数を呼び出すだけです。

ビューテンプレート、module/Album/view/album/album/edit.phtmlを追加します。

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

$title = 'Edit album';
$this->headTitle($title);
?>
<h1><?= $this->escapeHtml($title) ?></h1>
<?php
$album = $form->get('title');
$album->setAttribute('class', 'form-control');
$album->setAttribute('placeholder', 'Album title');

$artist = $form->get('artist');
$artist->setAttribute('class', 'form-control');
$artist->setAttribute('placeholder', 'Artist');

$submit = $form->get('submit');
$submit->setAttribute('class', 'btn btn-primary');

$form->setAttribute('action', $this->url('album', [
    'action' => 'edit',
    'id'     => $id,
]));
$form->prepare();

echo $this->form()->openTag($form);
?>
<div class="form-group">
    <?= $this->formLabel($album) ?>
    <?= $this->formElement($album) ?>
    <?= $this->formElementErrors()->render($album, ['class' => 'help-block']) ?>
</div>

<div class="form-group">
    <?= $this->formLabel($artist) ?>
    <?= $this->formElement($artist) ?>
    <?= $this->formElementErrors()->render($artist, ['class' => 'help-block']) ?>
</div>

<?php
echo $this->formSubmit($submit);
echo $this->formHidden($form->get('id'));
echo $this->form()->closeTag();

変更点は、Edit Albumタイトルを使用し、現在のアルバムのアイデンティファーを用いてフォームのアクションをeditアクションにしていることのみです。
これであなたは、アルバムを編集してくることができるはずです。

 

Deleting an album

アプリを完成させるため、削除の機能が必要です。
私たちはDeleteのリンクをリストページのそれぞれのアルバムに持っています、そして、素朴なアプローチは、クリック時に削除することでしょう。それは悪いです。我々のHTTPの仕様からして、私たちはGETを用いた不可逆なアクションをすべきでなく、POSTを代わりに用いるべきです。

ユーザーが削除をクリックしたときに確認フォームを出しましょう。そしてもし彼らがyesを押したら、我々はその削除をしましょう。フォームが自明であるので、我々はビューに直接コードを打ちましょう。

module/Album/src/Album/Controller/AlbumController.phpのdeleteAction()を実装しましょう。

    public function deleteAction()
    {
        $id = (int) $this->params()->fromRoute('id', 0);
        if (!$id) {
            return $this->redirect()->toRoute('album');
        }

        $request = $this->getRequest();
        if ($request->isPost()) {
            $del = $request->getPost('del', 'No');

            if ($del == 'Yes') {
                $id = (int) $request->getPost('id');
                $this->table->deleteAlbum($id);
            }

            // Redirect to list of albums
            return $this->redirect()->toRoute('album');
        }

        return [
            'id'    => $id,
            'album' => $this->table->getAlbum($id),
        ];
    }

以前と同様、私たちは、マッチしたルートからidを受け取り、アルバムを消去するか、確認画面を表示するかを決定するためにrequestオブジェクトのisPost()を確認しています。
私たちは、deleteAlbum()関数を用いて行を削除するために、テーブルオブジェクトを使用します。そしてアルバムの一覧にリダイレクトしています。もし要求がPOSTでないならば、現在のデータベースの記録を検索し、idに加えてビューに割り当てます。

ビューのスクリプトはシンプルなフォームです。
module/Album/view/album/album/delete.phtmlを作成します。

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

$title = 'Delete album';
$url   = $this->url('album', ['action' => 'delete', 'id' => $id]);

$this->headTitle($title);
?>
<h1><?= $this->escapeHtml($title) ?></h1>

<p>
    Are you sure that you want to delete
    "<?= $this->escapeHtml($album->title) ?>" by
    "<?= $this->escapeHtml($album->artist) ?>"?
</p>

<form action="<?= $url ?>" method="post">
<div class="form-group">
    <input type="hidden" name="id" value="<?= (int) $album->id ?>" />
    <input type="submit" class="btn btn-danger" name="del" value="Yes" />
    <input type="submit" class="btn btn-success" name="del" value="No" />
</div>
</form>

このスクリプト内で、私たちはユーザーに確認メッセージを表示し、YESNOボタンのformを表示します。アクション内で、私たちは削除を実行しているときにYesの値を確認しています。

Ensuring that the home page displays the list of albums

最後の箇所、このとき,ホームページであるhttp://zf-tutorial.localhost/はアルバムのリストを表示してくれません。

これはApplicationモジュールのmodule.config.php内のルートセットアップのせいです。
module/Application/config.module.config.phpを開き、ホームルートを見つけてください。

'home' => [
    'type' => \Zend\Router\Http\Literal::class,
    'options' => [
        'route'    => '/',
        'defaults' => [
            'controller' => Controller\IndexController::class,
            'action'     => 'index',
        ],
    ],
],

まず、Album\Controller\AlbumController;をファイルの先頭に挿入してください

use Album\Controller\AlbumController;

そして、controllerのController\IndexController::classをAlbumController::class:に変更してください

'home' => [
    'type' => \Zend\Router\Http\Literal::class,
    'options' => [
        'route'    => '/',
        'defaults' => [
            'controller' => AlbumController::class, // <-- change here
            'action'     => 'index',
        ],
    ],
],

これで、完全なアプリケーションの完成です!

 

 

お疲れ様です!これで公式サイトチュートリアル内の、Getting started with Zend Frameworkは終了です。
結局この記事も17000文字ほど書いています。脅威です。疲れました。

体力を回復させたのち、このチュートリアルからわかる、
わかりやすいZend Framework3の書き方について、まとめていきたいと思っています。

いつになるかはわかりませんが、体力が回復するまでお待ちください。

 

コメント

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